From 12ed7ff2bc9376542aa4146a90b6c44665938d0f Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Tue, 15 Jul 2025 16:37:40 +0200 Subject: [PATCH 01/12] update to lsp for TCL --- .vscode/launch.json | 11 +- client/src/extension.ts | 44 ++++++- client/tsconfig.json | 2 +- esbuild.js | 22 ++-- package-lock.json | 254 +++++++++++++++++++++------------------ package.json | 6 +- server/package-lock.json | 123 +++++++++++++++++++ server/package.json | 16 +++ server/src/parser.ts | 1 + server/src/server.ts | 69 +++++++++++ server/tsconfig.json | 14 +++ tsconfig.json | 2 +- 12 files changed, 423 insertions(+), 141 deletions(-) create mode 100644 server/package-lock.json create mode 100644 server/package.json create mode 100644 server/src/parser.ts create mode 100644 server/src/server.ts create mode 100644 server/tsconfig.json diff --git a/.vscode/launch.json b/.vscode/launch.json index 8aeb409..15c97d6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,8 +11,15 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": ["--extensionDevelopmentPath=${workspaceFolder}"], - "outFiles": ["${workspaceFolder}/client/out/**/*.js"], - "preLaunchTask": "npm: watch" + "outFiles": [ + "${workspaceFolder}out/client/**/*.js", + "${workspaceRoot}out/server/**/*.js" + ], + "autoAttachChildProcesses": true, + "preLaunchTask": { + "type": "npm", + "script": "watch" + } }, { "name": "Extension Tests", diff --git a/client/src/extension.ts b/client/src/extension.ts index 23fd4cb..ec5d8a1 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -3,7 +3,7 @@ import { LanguageClient, LanguageClientOptions, ServerOptions, - Executable + TransportKind } from "vscode-languageclient/node" import { formatCdlFile, @@ -14,10 +14,45 @@ import { diagnosticHandler, tclDocumentSymbolProvider } from "./common/handlers" +import * as path from "path" let client: LanguageClient export function activate(context: vscode.ExtensionContext) { + // The server is implemented in node + const serverModule = context.asAbsolutePath(path.join("out", "server", "src", "server.js")) + console.log(context.asAbsolutePath(path.join("server", "src", "server.js"))) + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + const serverOptions: ServerOptions = { + run: { module: serverModule, transport: TransportKind.ipc }, + debug: { + module: serverModule, + transport: TransportKind.ipc + } + } + // Options to control the language client + const clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: [{ scheme: "file", language: "tcl" }], + synchronize: { + // Notify the server about file changes to '.clientrc files contained in the workspace + fileEvents: vscode.workspace.createFileSystemWatcher("**/.clientrc") + } + } + + // Create the language client and start the client. + client = new LanguageClient( + "languageServerExample", + "Language Server Example", + serverOptions, + clientOptions + ) + + // Start the client. This will also launch the server + client.start() + + // const formatCdlProvider = vscode.languages.registerDocumentFormattingEditProvider( { scheme: "file", language: "cdl" }, { @@ -115,3 +150,10 @@ export function activate(context: vscode.ExtensionContext) { } }) } + +export function deactivate(): Thenable | undefined { + if (!client) { + return undefined + } + return client.stop() +} diff --git a/client/tsconfig.json b/client/tsconfig.json index c4c78f2..d7e4250 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -3,7 +3,7 @@ "module": "commonjs", "target": "es2019", "lib": ["ES2019"], - "outDir": "./dist", + "outDir": "./out", "rootDir": "src", "sourceMap": true }, diff --git a/esbuild.js b/esbuild.js index bf17142..540c2ad 100644 --- a/esbuild.js +++ b/esbuild.js @@ -5,19 +5,16 @@ const watch = process.argv.includes("--watch") async function main() { const ctx = await esbuild.context({ - entryPoints: [ - "client/src/extension.ts" - ], + entryPoints: ["client/src/extension.ts", "server/src/server.ts"], bundle: true, format: "cjs", minify: production, sourcemap: !production, sourcesContent: false, platform: "node", - outfile: "client/dist/extension.js", - external: [ - "vscode" - ], + outdir: "out", + // outfile: "client/out/extension.js", + external: ["vscode"], logLevel: "silent", plugins: [ /* add to the end of plugins array */ @@ -42,15 +39,10 @@ const esbuildProblemMatcherPlugin = { console.log("[watch] build started") }) build.onEnd((result) => { - result.errors.forEach(({ text, location - }) => { + result.errors.forEach(({ text, location }) => { console.error(`✘ [ERROR - ] ${text - }`) - console.error(` ${location.file - }:${location.line - }:${location.column - }:`) + ] ${text}`) + console.error(` ${location.file}:${location.line}:${location.column}:`) }) console.log("[watch] build finished") }) diff --git a/package-lock.json b/package-lock.json index 6e6a17d..3f3803a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,17 @@ { "name": "nx-post-support", - "version": "0.2.1", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "nx-post-support", - "version": "0.2.1", + "version": "0.3.0", "devDependencies": { "@types/vscode": "^1.96.0", "@vscode/vsce": "^3.2.1", "cross-env": "^7.0.3", - "esbuild": "^0.24.2", + "esbuild": "^0.25.6", "prettier": "^3.4.2", "typescript": "^5.7.2" }, @@ -191,9 +191,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", - "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", + "integrity": "sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==", "cpu": [ "ppc64" ], @@ -208,9 +208,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", - "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.6.tgz", + "integrity": "sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==", "cpu": [ "arm" ], @@ -225,9 +225,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", - "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.6.tgz", + "integrity": "sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==", "cpu": [ "arm64" ], @@ -242,9 +242,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", - "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.6.tgz", + "integrity": "sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==", "cpu": [ "x64" ], @@ -259,9 +259,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", - "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.6.tgz", + "integrity": "sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==", "cpu": [ "arm64" ], @@ -276,9 +276,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", - "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.6.tgz", + "integrity": "sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==", "cpu": [ "x64" ], @@ -293,9 +293,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", - "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.6.tgz", + "integrity": "sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==", "cpu": [ "arm64" ], @@ -310,9 +310,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", - "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.6.tgz", + "integrity": "sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==", "cpu": [ "x64" ], @@ -327,9 +327,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", - "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.6.tgz", + "integrity": "sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==", "cpu": [ "arm" ], @@ -344,9 +344,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", - "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.6.tgz", + "integrity": "sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==", "cpu": [ "arm64" ], @@ -361,9 +361,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", - "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.6.tgz", + "integrity": "sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==", "cpu": [ "ia32" ], @@ -378,9 +378,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", - "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.6.tgz", + "integrity": "sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==", "cpu": [ "loong64" ], @@ -395,9 +395,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", - "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.6.tgz", + "integrity": "sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==", "cpu": [ "mips64el" ], @@ -412,9 +412,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", - "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.6.tgz", + "integrity": "sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==", "cpu": [ "ppc64" ], @@ -429,9 +429,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", - "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.6.tgz", + "integrity": "sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==", "cpu": [ "riscv64" ], @@ -446,9 +446,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", - "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.6.tgz", + "integrity": "sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==", "cpu": [ "s390x" ], @@ -463,9 +463,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", - "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.6.tgz", + "integrity": "sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==", "cpu": [ "x64" ], @@ -480,9 +480,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", - "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.6.tgz", + "integrity": "sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", - "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.6.tgz", + "integrity": "sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", - "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.6.tgz", + "integrity": "sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", - "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.6.tgz", + "integrity": "sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==", "cpu": [ "x64" ], @@ -547,10 +547,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.6.tgz", + "integrity": "sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", - "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.6.tgz", + "integrity": "sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==", "cpu": [ "x64" ], @@ -565,9 +582,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", - "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.6.tgz", + "integrity": "sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==", "cpu": [ "arm64" ], @@ -582,9 +599,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", - "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.6.tgz", + "integrity": "sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==", "cpu": [ "ia32" ], @@ -599,9 +616,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", - "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.6.tgz", + "integrity": "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==", "cpu": [ "x64" ], @@ -938,9 +955,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1457,9 +1474,9 @@ } }, "node_modules/esbuild": { - "version": "0.24.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", - "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "version": "0.25.6", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.6.tgz", + "integrity": "sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1470,31 +1487,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.2", - "@esbuild/android-arm": "0.24.2", - "@esbuild/android-arm64": "0.24.2", - "@esbuild/android-x64": "0.24.2", - "@esbuild/darwin-arm64": "0.24.2", - "@esbuild/darwin-x64": "0.24.2", - "@esbuild/freebsd-arm64": "0.24.2", - "@esbuild/freebsd-x64": "0.24.2", - "@esbuild/linux-arm": "0.24.2", - "@esbuild/linux-arm64": "0.24.2", - "@esbuild/linux-ia32": "0.24.2", - "@esbuild/linux-loong64": "0.24.2", - "@esbuild/linux-mips64el": "0.24.2", - "@esbuild/linux-ppc64": "0.24.2", - "@esbuild/linux-riscv64": "0.24.2", - "@esbuild/linux-s390x": "0.24.2", - "@esbuild/linux-x64": "0.24.2", - "@esbuild/netbsd-arm64": "0.24.2", - "@esbuild/netbsd-x64": "0.24.2", - "@esbuild/openbsd-arm64": "0.24.2", - "@esbuild/openbsd-x64": "0.24.2", - "@esbuild/sunos-x64": "0.24.2", - "@esbuild/win32-arm64": "0.24.2", - "@esbuild/win32-ia32": "0.24.2", - "@esbuild/win32-x64": "0.24.2" + "@esbuild/aix-ppc64": "0.25.6", + "@esbuild/android-arm": "0.25.6", + "@esbuild/android-arm64": "0.25.6", + "@esbuild/android-x64": "0.25.6", + "@esbuild/darwin-arm64": "0.25.6", + "@esbuild/darwin-x64": "0.25.6", + "@esbuild/freebsd-arm64": "0.25.6", + "@esbuild/freebsd-x64": "0.25.6", + "@esbuild/linux-arm": "0.25.6", + "@esbuild/linux-arm64": "0.25.6", + "@esbuild/linux-ia32": "0.25.6", + "@esbuild/linux-loong64": "0.25.6", + "@esbuild/linux-mips64el": "0.25.6", + "@esbuild/linux-ppc64": "0.25.6", + "@esbuild/linux-riscv64": "0.25.6", + "@esbuild/linux-s390x": "0.25.6", + "@esbuild/linux-x64": "0.25.6", + "@esbuild/netbsd-arm64": "0.25.6", + "@esbuild/netbsd-x64": "0.25.6", + "@esbuild/openbsd-arm64": "0.25.6", + "@esbuild/openbsd-x64": "0.25.6", + "@esbuild/openharmony-arm64": "0.25.6", + "@esbuild/sunos-x64": "0.25.6", + "@esbuild/win32-arm64": "0.25.6", + "@esbuild/win32-ia32": "0.25.6", + "@esbuild/win32-x64": "0.25.6" } }, "node_modules/escape-string-regexp": { @@ -1660,9 +1678,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2904,9 +2922,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "dev": true, "license": "MIT", "optional": true, @@ -3017,9 +3035,9 @@ "license": "MIT" }, "node_modules/undici": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", - "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 2fa08a3..22776b7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "type": "git", "url": "https://git.cbsk-tech.de/Christoph/nx_post_support.git" }, - "main": "./client/dist/extension.js", + "main": "./out/client/src/extension.js", "keywords": [ "cdl", "NX CDL", @@ -83,8 +83,8 @@ "@types/vscode": "^1.96.0", "@vscode/vsce": "^3.2.1", "cross-env": "^7.0.3", - "esbuild": "^0.24.2", + "esbuild": "^0.25.6", "prettier": "^3.4.2", "typescript": "^5.7.2" } -} +} \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json new file mode 100644 index 0000000..69c1324 --- /dev/null +++ b/server/package-lock.json @@ -0,0 +1,123 @@ +{ + "name": "lsp-sample-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lsp-sample-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "chevrotain": "^11.0.3", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-textdocument": "^1.0.12" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + } + } +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..700c1e7 --- /dev/null +++ b/server/package.json @@ -0,0 +1,16 @@ +{ + "name": "lsp-sample-server", + "description": "Example implementation of a language server in node.", + "version": "1.0.0", + "author": "Microsoft Corporation", + "license": "MIT", + "engines": { + "node": "*" + }, + "dependencies": { + "chevrotain": "^11.0.3", + "vscode-languageserver": "^9.0.1", + "vscode-languageserver-textdocument": "^1.0.12" + }, + "scripts": {} +} diff --git a/server/src/parser.ts b/server/src/parser.ts new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/server/src/parser.ts @@ -0,0 +1 @@ + diff --git a/server/src/server.ts b/server/src/server.ts new file mode 100644 index 0000000..1be5387 --- /dev/null +++ b/server/src/server.ts @@ -0,0 +1,69 @@ +import { + createConnection, + TextDocuments, + ProposedFeatures, + TextDocumentSyncKind, + InitializeParams, + InitializeResult, + CompletionItemKind, + TextDocumentPositionParams, + CompletionItem, + TextEdit, + Range, + Position +} from "vscode-languageserver/node" +import { TextDocument } from "vscode-languageserver-textdocument" + +// Create a connection for the server, using Node's IPC as a transport. +// Also include all preview / proposed LSP features. +let connection = createConnection(ProposedFeatures.all) + +// Create a simple text document manager. +let documents: TextDocuments = new TextDocuments(TextDocument) + +connection.onInitialize((params: InitializeParams) => { + let capabilities = params.capabilities + + // Does the client support the `workspace/configuration` request? + // If not, we fall back using global settings. + const result: InitializeResult = { + capabilities: { + textDocumentSync: TextDocumentSyncKind.Incremental, + // Tell the client that this server supports code completion. + completionProvider: { + resolveProvider: false + } + } + } + return result +}) + +// This handler provides the initial list of the completion items. +connection.onCompletion((params: TextDocumentPositionParams): CompletionItem[] => { + const doc = documents.get(params.textDocument.uri) + if (!doc) return [] + + const text = doc.getText() + + return [] +}) + +connection.onDocumentFormatting(async (params, token) => { + const document = documents.get(params.textDocument.uri) + if (!document) return [] + + const originalText = document.getText() + + return [] +}) + +documents.onDidChangeContent(async (change) => { + connection.console.log("Document has changed") +}) + +// Make the text document manager listen on the connection +// for open, change and close text document events +documents.listen(connection) + +// Listen on the connection +connection.listen() diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..eb314f7 --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es2020", + "lib": ["es2020"], + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "strict": true, + "outDir": "out", + "rootDir": "src" + }, + "include": ["src"], + "exclude": ["node_modules", ".vscode-test"] +} diff --git a/tsconfig.json b/tsconfig.json index 61ca316..3dbd088 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "target": "es2023", "lib": ["es2023", "dom"], "module": "commonjs", - "outDir": "client/out", + "outDir": "out", "rootDir": "client", "strict": true, "esModuleInterop": true, From 9febcebddb078bc47b180ef000caf6104011bfde Mon Sep 17 00:00:00 2001 From: christoph_xd Date: Tue, 15 Jul 2025 22:11:41 +0200 Subject: [PATCH 02/12] python server --- client/package-lock.json | 59 +++++++++++++- client/package.json | 2 + client/src/common/constants.ts | 14 ++++ client/src/common/log/logging.ts | 60 ++++++++++++++ client/src/common/python.ts | 86 ++++++++++++++++++++ client/src/common/server.ts | 131 +++++++++++++++++++++++++++++++ client/src/common/settings.ts | 130 ++++++++++++++++++++++++++++++ client/src/common/setup.ts | 18 +++++ client/src/common/utilities.ts | 69 ++++++++++++++++ client/src/common/vscodeapi.ts | 52 ++++++++++++ client/src/extension.ts | 122 +++++++++++++++++++++------- server/.python-version | 1 + server/README.md | 0 server/noxfile.py | 26 ++++++ server/package-lock.json | 123 ----------------------------- server/package.json | 16 ---- server/pyproject.toml | 9 +++ server/src/_debug_server.py | 39 +++++++++ server/src/lsp_server.py | 32 ++++++++ server/src/parser.ts | 1 - server/src/server.ts | 69 ---------------- server/tsconfig.json | 14 ---- server/uv.lock | 83 ++++++++++++++++++++ 23 files changed, 900 insertions(+), 256 deletions(-) create mode 100644 client/src/common/constants.ts create mode 100644 client/src/common/log/logging.ts create mode 100644 client/src/common/python.ts create mode 100644 client/src/common/server.ts create mode 100644 client/src/common/settings.ts create mode 100644 client/src/common/setup.ts create mode 100644 client/src/common/utilities.ts create mode 100644 client/src/common/vscodeapi.ts create mode 100644 server/.python-version create mode 100644 server/README.md create mode 100644 server/noxfile.py delete mode 100644 server/package-lock.json delete mode 100644 server/package.json create mode 100644 server/pyproject.toml create mode 100644 server/src/_debug_server.py create mode 100644 server/src/lsp_server.py delete mode 100644 server/src/parser.ts delete mode 100644 server/src/server.ts delete mode 100644 server/tsconfig.json create mode 100644 server/uv.lock diff --git a/client/package-lock.json b/client/package-lock.json index 20c97f8..f2fa6f6 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -9,6 +9,8 @@ "version": "0.0.1", "license": "MIT", "dependencies": { + "@vscode/python-extension": "^1.0.5", + "fs-extra": "^11.3.0", "vscode-languageclient": "^9.0.1" }, "devDependencies": { @@ -36,6 +38,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@vscode/python-extension": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@vscode/python-extension/-/python-extension-1.0.5.tgz", + "integrity": "sha512-uYhXUrL/gn92mfqhjAwH2+yGOpjloBxj9ekoL4BhUsKcyJMpEg6WlNf3S3si+5x9zlbHHe7FYQNjZEbz1ymI9Q==", + "license": "MIT", + "engines": { + "node": ">=16.17.1", + "vscode": "^1.78.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -43,14 +55,46 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", @@ -82,6 +126,15 @@ "dev": true, "license": "MIT" }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", diff --git a/client/package.json b/client/package.json index ef34fc0..4db362e 100644 --- a/client/package.json +++ b/client/package.json @@ -7,6 +7,8 @@ "vscode": "^1.96.0" }, "dependencies": { + "@vscode/python-extension": "^1.0.5", + "fs-extra": "^11.3.0", "vscode-languageclient": "^9.0.1" }, "devDependencies": { diff --git a/client/src/common/constants.ts b/client/src/common/constants.ts new file mode 100644 index 0000000..10f812f --- /dev/null +++ b/client/src/common/constants.ts @@ -0,0 +1,14 @@ +import * as path from "path" + +const folderName = path.basename(__dirname) +export const EXTENSION_ROOT_DIR = + folderName === "common" + ? path.dirname(path.dirname(path.dirname(__dirname))) + : path.dirname(path.dirname(__dirname)) +export const BUNDLED_PYTHON_SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, "server") +export const SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, "src", `lsp_server.py`) +export const DEBUG_SERVER_SCRIPT_PATH = path.join( + BUNDLED_PYTHON_SCRIPTS_DIR, + "src", + `_debug_server.py` +) diff --git a/client/src/common/log/logging.ts b/client/src/common/log/logging.ts new file mode 100644 index 0000000..3885c9f --- /dev/null +++ b/client/src/common/log/logging.ts @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as util from "util" +import { Disposable, LogOutputChannel } from "vscode" + +type Arguments = unknown[] +class OutputChannelLogger { + constructor(private readonly channel: LogOutputChannel) {} + + public traceLog(...data: Arguments): void { + this.channel.appendLine(util.format(...data)) + } + + public traceError(...data: Arguments): void { + this.channel.error(util.format(...data)) + } + + public traceWarn(...data: Arguments): void { + this.channel.warn(util.format(...data)) + } + + public traceInfo(...data: Arguments): void { + this.channel.info(util.format(...data)) + } + + public traceVerbose(...data: Arguments): void { + this.channel.debug(util.format(...data)) + } +} + +let channel: OutputChannelLogger | undefined +export function registerLogger(logChannel: LogOutputChannel): Disposable { + channel = new OutputChannelLogger(logChannel) + return { + dispose: () => { + channel = undefined + } + } +} + +export function traceLog(...args: Arguments): void { + channel?.traceLog(...args) +} + +export function traceError(...args: Arguments): void { + channel?.traceError(...args) +} + +export function traceWarn(...args: Arguments): void { + channel?.traceWarn(...args) +} + +export function traceInfo(...args: Arguments): void { + channel?.traceInfo(...args) +} + +export function traceVerbose(...args: Arguments): void { + channel?.traceVerbose(...args) +} diff --git a/client/src/common/python.ts b/client/src/common/python.ts new file mode 100644 index 0000000..f300201 --- /dev/null +++ b/client/src/common/python.ts @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* eslint-disable @typescript-eslint/naming-convention */ +import { commands, Disposable, Event, EventEmitter, Uri } from "vscode" +import { traceError, traceLog } from "./log/logging" +import { PythonExtension, ResolvedEnvironment } from "@vscode/python-extension" + +export interface IInterpreterDetails { + path?: string[] + resource?: Uri +} + +const onDidChangePythonInterpreterEvent = new EventEmitter() +export const onDidChangePythonInterpreter: Event = + onDidChangePythonInterpreterEvent.event + +let _api: PythonExtension | undefined +async function getPythonExtensionAPI(): Promise { + if (_api) { + return _api + } + _api = await PythonExtension.api() + return _api +} + +export async function initializePython(disposables: Disposable[]): Promise { + try { + const api = await getPythonExtensionAPI() + + if (api) { + disposables.push( + api.environments.onDidChangeActiveEnvironmentPath((e) => { + onDidChangePythonInterpreterEvent.fire({ + path: [e.path], + resource: e.resource?.uri + }) + }) + ) + + traceLog("Waiting for interpreter from python extension.") + onDidChangePythonInterpreterEvent.fire(await getInterpreterDetails()) + } + } catch (error) { + traceError("Error initializing python: ", error) + } +} + +export async function resolveInterpreter( + interpreter: string[] +): Promise { + const api = await getPythonExtensionAPI() + return api?.environments.resolveEnvironment(interpreter[0]) +} + +export async function getInterpreterDetails(resource?: Uri): Promise { + const api = await getPythonExtensionAPI() + const environment = await api?.environments.resolveEnvironment( + api?.environments.getActiveEnvironmentPath(resource) + ) + if (environment?.executable.uri && checkVersion(environment)) { + return { path: [environment?.executable.uri.fsPath], resource } + } + return { path: undefined, resource } +} + +export async function getDebuggerPath(): Promise { + const api = await getPythonExtensionAPI() + return api?.debug.getDebuggerPackagePath() +} + +export async function runPythonExtensionCommand(command: string, ...rest: any[]) { + await getPythonExtensionAPI() + return await commands.executeCommand(command, ...rest) +} + +export function checkVersion(resolved: ResolvedEnvironment | undefined): boolean { + const version = resolved?.version + if (version?.major === 3 && version?.minor >= 11) { + return true + } + traceError(`Python version ${version?.major}.${version?.minor} is not supported.`) + traceError(`Selected python path: ${resolved?.executable.uri?.fsPath}`) + traceError("Supported versions are 3.11 and above.") + return false +} diff --git a/client/src/common/server.ts b/client/src/common/server.ts new file mode 100644 index 0000000..a3dffc6 --- /dev/null +++ b/client/src/common/server.ts @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as fsapi from "fs-extra" +import { Disposable, env, LogOutputChannel } from "vscode" +import { State } from "vscode-languageclient" +import { + LanguageClient, + LanguageClientOptions, + RevealOutputChannelOn, + ServerOptions +} from "vscode-languageclient/node" +import { DEBUG_SERVER_SCRIPT_PATH, SERVER_SCRIPT_PATH } from "./constants" +import { traceError, traceInfo, traceVerbose } from "./log/logging" +import { getDebuggerPath } from "./python" +import { + getExtensionSettings, + getGlobalSettings, + getWorkspaceSettings, + ISettings +} from "./settings" +import { getLSClientTraceLevel, getProjectRoot } from "./utilities" +import { isVirtualWorkspace } from "./vscodeapi" + +export type IInitOptions = { settings: ISettings[]; globalSettings: ISettings } + +async function createServer( + settings: ISettings, + serverId: string, + serverName: string, + outputChannel: LogOutputChannel, + initializationOptions: IInitOptions +): Promise { + const command = settings.interpreter[0] + const cwd = settings.cwd + + // Set debugger path needed for debugging python code. + const newEnv = { ...process.env } + const debuggerPath = await getDebuggerPath() + const isDebugScript = await fsapi.pathExists(DEBUG_SERVER_SCRIPT_PATH) + if (newEnv.USE_DEBUGPY && debuggerPath) { + newEnv.DEBUGPY_PATH = debuggerPath + } else { + newEnv.USE_DEBUGPY = "False" + } + + // Set import strategy + newEnv.LS_IMPORT_STRATEGY = settings.importStrategy + + // Set notification type + newEnv.LS_SHOW_NOTIFICATION = settings.showNotifications + + const args = + newEnv.USE_DEBUGPY === "False" || !isDebugScript + ? settings.interpreter.slice(1).concat([SERVER_SCRIPT_PATH]) + : settings.interpreter.slice(1).concat([DEBUG_SERVER_SCRIPT_PATH]) + traceInfo(`Server run command: ${[command, ...args].join(" ")}`) + + const serverOptions: ServerOptions = { + command, + args, + options: { cwd, env: newEnv } + } + + // Options to control the language client + const clientOptions: LanguageClientOptions = { + // Register the server for python documents + documentSelector: isVirtualWorkspace() + ? [{ language: "tcl" }] + : [ + { scheme: "file", language: "tcl" }, + { scheme: "untitled", language: "tcl" }, + { scheme: "vscode-notebook", language: "tcl" }, + { scheme: "vscode-notebook-cell", language: "tcl" } + ], + outputChannel: outputChannel, + traceOutputChannel: outputChannel, + revealOutputChannelOn: RevealOutputChannelOn.Never, + initializationOptions + } + + return new LanguageClient(serverId, serverName, serverOptions, clientOptions) +} + +let _disposables: Disposable[] = [] +export async function restartServer( + serverId: string, + serverName: string, + outputChannel: LogOutputChannel, + lsClient?: LanguageClient +): Promise { + if (lsClient) { + traceInfo(`Server: Stop requested`) + await lsClient.stop() + _disposables.forEach((d) => d.dispose()) + _disposables = [] + } + const projectRoot = await getProjectRoot() + const workspaceSetting = await getWorkspaceSettings(serverId, projectRoot, true) + + const newLSClient = await createServer(workspaceSetting, serverId, serverName, outputChannel, { + settings: await getExtensionSettings(serverId, true), + globalSettings: await getGlobalSettings(serverId, false) + }) + traceInfo(`Server: Start requested.`) + _disposables.push( + newLSClient.onDidChangeState((e) => { + switch (e.newState) { + case State.Stopped: + traceVerbose(`Server State: Stopped`) + break + case State.Starting: + traceVerbose(`Server State: Starting`) + break + case State.Running: + traceVerbose(`Server State: Running`) + break + } + }) + ) + try { + await newLSClient.start() + } catch (ex) { + traceError(`Server: Start failed: ${ex}`) + return undefined + } + + const level = getLSClientTraceLevel(outputChannel.logLevel, env.logLevel) + await newLSClient.setTrace(level) + return newLSClient +} diff --git a/client/src/common/settings.ts b/client/src/common/settings.ts new file mode 100644 index 0000000..b10e7ca --- /dev/null +++ b/client/src/common/settings.ts @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + ConfigurationChangeEvent, + ConfigurationScope, + WorkspaceConfiguration, + WorkspaceFolder +} from "vscode" +import { getInterpreterDetails } from "./python" +import { getConfiguration, getWorkspaceFolders } from "./vscodeapi" + +export interface ISettings { + cwd: string + workspace: string + args: string[] + path: string[] + interpreter: string[] + importStrategy: string + showNotifications: string +} + +export function getExtensionSettings( + namespace: string, + includeInterpreter?: boolean +): Promise { + return Promise.all( + getWorkspaceFolders().map((w) => getWorkspaceSettings(namespace, w, includeInterpreter)) + ) +} + +function resolveVariables(value: string[], workspace?: WorkspaceFolder): string[] { + const substitutions = new Map() + const home = process.env.HOME || process.env.USERPROFILE + if (home) { + substitutions.set("${userHome}", home) + } + if (workspace) { + substitutions.set("${workspaceFolder}", workspace.uri.fsPath) + } + substitutions.set("${cwd}", process.cwd()) + getWorkspaceFolders().forEach((w) => { + substitutions.set("${workspaceFolder:" + w.name + "}", w.uri.fsPath) + }) + + return value.map((s) => { + for (const [key, value] of substitutions) { + s = s.replace(key, value) + } + return s + }) +} + +export function getInterpreterFromSetting(namespace: string, scope?: ConfigurationScope) { + const config = getConfiguration(namespace, scope) + return config.get("interpreter") +} + +export async function getWorkspaceSettings( + namespace: string, + workspace: WorkspaceFolder, + includeInterpreter?: boolean +): Promise { + const config = getConfiguration(namespace, workspace.uri) + + let interpreter: string[] = [] + if (includeInterpreter) { + interpreter = getInterpreterFromSetting(namespace, workspace) ?? [] + if (interpreter.length === 0) { + interpreter = (await getInterpreterDetails(workspace.uri)).path ?? [] + } + } + + const workspaceSetting = { + cwd: workspace.uri.fsPath, + workspace: workspace.uri.toString(), + args: resolveVariables(config.get(`args`) ?? [], workspace), + path: resolveVariables(config.get(`path`) ?? [], workspace), + interpreter: resolveVariables(interpreter, workspace), + importStrategy: config.get(`importStrategy`) ?? "useBundled", + showNotifications: config.get(`showNotifications`) ?? "off" + } + return workspaceSetting +} + +function getGlobalValue(config: WorkspaceConfiguration, key: string, defaultValue: T): T { + const inspect = config.inspect(key) + return inspect?.globalValue ?? inspect?.defaultValue ?? defaultValue +} + +export async function getGlobalSettings( + namespace: string, + includeInterpreter?: boolean +): Promise { + const config = getConfiguration(namespace) + + let interpreter: string[] = [] + if (includeInterpreter) { + interpreter = getGlobalValue(config, "interpreter", []) + if (interpreter === undefined || interpreter.length === 0) { + interpreter = (await getInterpreterDetails()).path ?? [] + } + } + + const setting = { + cwd: process.cwd(), + workspace: process.cwd(), + args: getGlobalValue(config, "args", []), + path: getGlobalValue(config, "path", []), + interpreter: interpreter, + importStrategy: getGlobalValue(config, "importStrategy", "useBundled"), + showNotifications: getGlobalValue(config, "showNotifications", "off") + } + return setting +} + +export function checkIfConfigurationChanged( + e: ConfigurationChangeEvent, + namespace: string +): boolean { + const settings = [ + `${namespace}.args`, + `${namespace}.path`, + `${namespace}.interpreter`, + `${namespace}.importStrategy`, + `${namespace}.showNotifications` + ] + const changed = settings.map((s) => e.affectsConfiguration(s)) + return changed.includes(true) +} diff --git a/client/src/common/setup.ts b/client/src/common/setup.ts new file mode 100644 index 0000000..d6c32e8 --- /dev/null +++ b/client/src/common/setup.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from "path" +import * as fs from "fs-extra" +import { EXTENSION_ROOT_DIR } from "./constants" + +export interface IServerInfo { + name: string + module: string +} + +export function loadServerDefaults(): IServerInfo { + const packageJson = path.join(EXTENSION_ROOT_DIR, "package.json") + const content = fs.readFileSync(packageJson).toString() + const config = JSON.parse(content) + return config.serverInfo as IServerInfo +} diff --git a/client/src/common/utilities.ts b/client/src/common/utilities.ts new file mode 100644 index 0000000..74d4e37 --- /dev/null +++ b/client/src/common/utilities.ts @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as fs from "fs-extra" +import * as path from "path" +import { LogLevel, Uri, WorkspaceFolder } from "vscode" +import { Trace } from "vscode-jsonrpc/node" +import { getWorkspaceFolders } from "./vscodeapi" + +function logLevelToTrace(logLevel: LogLevel): Trace { + switch (logLevel) { + case LogLevel.Error: + case LogLevel.Warning: + case LogLevel.Info: + return Trace.Messages + + case LogLevel.Debug: + case LogLevel.Trace: + return Trace.Verbose + + case LogLevel.Off: + default: + return Trace.Off + } +} + +export function getLSClientTraceLevel(channelLogLevel: LogLevel, globalLogLevel: LogLevel): Trace { + if (channelLogLevel === LogLevel.Off) { + return logLevelToTrace(globalLogLevel) + } + if (globalLogLevel === LogLevel.Off) { + return logLevelToTrace(channelLogLevel) + } + const level = logLevelToTrace( + channelLogLevel <= globalLogLevel ? channelLogLevel : globalLogLevel + ) + return level +} + +export async function getProjectRoot(): Promise { + const workspaces: readonly WorkspaceFolder[] = getWorkspaceFolders() + if (workspaces.length === 0) { + return { + uri: Uri.file(process.cwd()), + name: path.basename(process.cwd()), + index: 0 + } + } else if (workspaces.length === 1) { + return workspaces[0] + } else { + let rootWorkspace = workspaces[0] + let root = undefined + for (const w of workspaces) { + if (await fs.pathExists(w.uri.fsPath)) { + root = w.uri.fsPath + rootWorkspace = w + break + } + } + + for (const w of workspaces) { + if (root && root.length > w.uri.fsPath.length && (await fs.pathExists(w.uri.fsPath))) { + root = w.uri.fsPath + rootWorkspace = w + } + } + return rootWorkspace + } +} diff --git a/client/src/common/vscodeapi.ts b/client/src/common/vscodeapi.ts new file mode 100644 index 0000000..8304022 --- /dev/null +++ b/client/src/common/vscodeapi.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + commands, + ConfigurationScope, + Disposable, + LogOutputChannel, + Uri, + window, + workspace, + WorkspaceConfiguration, + WorkspaceFolder +} from "vscode" + +export function createOutputChannel(name: string): LogOutputChannel { + return window.createOutputChannel(name, { log: true }) +} + +export function getConfiguration( + config: string, + scope?: ConfigurationScope +): WorkspaceConfiguration { + return workspace.getConfiguration(config, scope) +} + +export function registerCommand( + command: string, + callback: (...args: any[]) => any, + thisArg?: any +): Disposable { + return commands.registerCommand(command, callback, thisArg) +} + +export const { onDidChangeConfiguration } = workspace + +export function isVirtualWorkspace(): boolean { + const isVirtual = + workspace.workspaceFolders && + workspace.workspaceFolders.every((f) => f.uri.scheme !== "file") + return !!isVirtual +} + +export function getWorkspaceFolders(): readonly WorkspaceFolder[] { + return workspace.workspaceFolders ?? [] +} + +export function getWorkspaceFolder(uri: Uri): WorkspaceFolder | undefined { + return workspace.getWorkspaceFolder(uri) +} diff --git a/client/src/extension.ts b/client/src/extension.ts index ec5d8a1..0474159 100644 --- a/client/src/extension.ts +++ b/client/src/extension.ts @@ -14,43 +14,105 @@ import { diagnosticHandler, tclDocumentSymbolProvider } from "./common/handlers" -import * as path from "path" +import { registerLogger, traceError, traceLog, traceVerbose } from "./common/log/logging" +import { + checkVersion, + getInterpreterDetails, + initializePython, + onDidChangePythonInterpreter, + resolveInterpreter +} from "./common/python" +import { restartServer } from "./common/server" +import { checkIfConfigurationChanged, getInterpreterFromSetting } from "./common/settings" +import { loadServerDefaults } from "./common/setup" +import { getLSClientTraceLevel } from "./common/utilities" +import { createOutputChannel, onDidChangeConfiguration, registerCommand } from "./common/vscodeapi" let client: LanguageClient -export function activate(context: vscode.ExtensionContext) { - // The server is implemented in node - const serverModule = context.asAbsolutePath(path.join("out", "server", "src", "server.js")) - console.log(context.asAbsolutePath(path.join("server", "src", "server.js"))) - // If the extension is launched in debug mode then the debug server options are used - // Otherwise the run options are used - const serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc }, - debug: { - module: serverModule, - transport: TransportKind.ipc - } - } - // Options to control the language client - const clientOptions: LanguageClientOptions = { - // Register the server for plain text documents - documentSelector: [{ scheme: "file", language: "tcl" }], - synchronize: { - // Notify the server about file changes to '.clientrc files contained in the workspace - fileEvents: vscode.workspace.createFileSystemWatcher("**/.clientrc") - } +export async function activate(context: vscode.ExtensionContext) { + // This is required to get server name and module. This should be + // the first thing that we do in this extension. + const serverInfo = loadServerDefaults() + const serverName = serverInfo.name + const serverId = serverInfo.module + + // Setup logging + const outputChannel = createOutputChannel(serverName) + context.subscriptions.push(outputChannel, registerLogger(outputChannel)) + + const changeLogLevel = async (c: vscode.LogLevel, g: vscode.LogLevel) => { + const level = getLSClientTraceLevel(c, g) + await client?.setTrace(level) } - // Create the language client and start the client. - client = new LanguageClient( - "languageServerExample", - "Language Server Example", - serverOptions, - clientOptions + context.subscriptions.push( + outputChannel.onDidChangeLogLevel(async (e) => { + await changeLogLevel(e, vscode.env.logLevel) + }), + vscode.env.onDidChangeLogLevel(async (e) => { + await changeLogLevel(outputChannel.logLevel, e) + }) ) - // Start the client. This will also launch the server - client.start() + // Log Server information + traceLog(`Name: ${serverInfo.name}`) + traceLog(`Module: ${serverInfo.module}`) + traceVerbose(`Full Server Info: ${JSON.stringify(serverInfo)}`) + + const runServer = async () => { + const interpreter = getInterpreterFromSetting(serverId) + if (interpreter && interpreter.length > 0) { + if (checkVersion(await resolveInterpreter(interpreter))) { + traceVerbose( + `Using interpreter from ${serverInfo.module}.interpreter: ${interpreter.join(" ")}` + ) + client = await restartServer(serverId, serverName, outputChannel, client) + } + return + } + + const interpreterDetails = await getInterpreterDetails() + if (interpreterDetails.path) { + traceVerbose( + `Using interpreter from Python extension: ${interpreterDetails.path.join(" ")}` + ) + client = await restartServer(serverId, serverName, outputChannel, client) + return + } + + traceError( + "Python interpreter missing:\r\n" + + "[Option 1] Select python interpreter using the ms-python.python.\r\n" + + `[Option 2] Set an interpreter using "${serverId}.interpreter" setting.\r\n` + + "Please use Python 3.8 or greater." + ) + } + + context.subscriptions.push( + onDidChangePythonInterpreter(async () => { + await runServer() + }), + onDidChangeConfiguration(async (e: vscode.ConfigurationChangeEvent) => { + if (checkIfConfigurationChanged(e, serverId)) { + await runServer() + } + }), + registerCommand(`${serverId}.restart`, async () => { + await runServer() + }) + ) + + setImmediate(async () => { + const interpreter = getInterpreterFromSetting(serverId) + if (interpreter === undefined || interpreter.length === 0) { + traceLog(`Python extension loading`) + await initializePython(context.subscriptions) + traceLog(`Python extension loaded`) + } else { + await runServer() + } + }) // const formatCdlProvider = vscode.languages.registerDocumentFormattingEditProvider( diff --git a/server/.python-version b/server/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/server/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..e69de29 diff --git a/server/noxfile.py b/server/noxfile.py new file mode 100644 index 0000000..82f0645 --- /dev/null +++ b/server/noxfile.py @@ -0,0 +1,26 @@ +"""All the action we need during build""" + +import json +import os +import pathlib +import urllib.request as url_lib +from typing import List + +import nox # pylint: disable=import-error + + +@nox.session() +def setup(session: nox.Session) -> None: + """Sets up the template for development.""" + + session.install( + "-t", + "./bundled/libs", + "--no-cache-dir", + "--implementation", + "py", + "--no-deps", + "--upgrade", + "-r", + "./requirements.txt", + ) diff --git a/server/package-lock.json b/server/package-lock.json deleted file mode 100644 index 69c1324..0000000 --- a/server/package-lock.json +++ /dev/null @@ -1,123 +0,0 @@ -{ - "name": "lsp-sample-server", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "lsp-sample-server", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "chevrotain": "^11.0.3", - "vscode-languageserver": "^9.0.1", - "vscode-languageserver-textdocument": "^1.0.12" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", - "license": "Apache-2.0" - }, - "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - } - } -} diff --git a/server/package.json b/server/package.json deleted file mode 100644 index 700c1e7..0000000 --- a/server/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "lsp-sample-server", - "description": "Example implementation of a language server in node.", - "version": "1.0.0", - "author": "Microsoft Corporation", - "license": "MIT", - "engines": { - "node": "*" - }, - "dependencies": { - "chevrotain": "^11.0.3", - "vscode-languageserver": "^9.0.1", - "vscode-languageserver-textdocument": "^1.0.12" - }, - "scripts": {} -} diff --git a/server/pyproject.toml b/server/pyproject.toml new file mode 100644 index 0000000..b54b66b --- /dev/null +++ b/server/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "server" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "pygls","ply" +] diff --git a/server/src/_debug_server.py b/server/src/_debug_server.py new file mode 100644 index 0000000..c4c6852 --- /dev/null +++ b/server/src/_debug_server.py @@ -0,0 +1,39 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +"""Debugging support for LSP.""" + +import os +import pathlib +import runpy +import sys + + +def update_sys_path(path_to_add: str) -> None: + """Add given path to `sys.path`.""" + if path_to_add not in sys.path and os.path.isdir(path_to_add): + sys.path.append(path_to_add) + + +# Ensure debugger is loaded before we load anything else, to debug initialization. +debugger_path = os.getenv("DEBUGPY_PATH", None) +if debugger_path: + if debugger_path.endswith("debugpy"): + debugger_path = os.fspath(pathlib.Path(debugger_path).parent) + + update_sys_path(debugger_path) + + # pylint: disable=wrong-import-position,import-error + import debugpy + + # 5678 is the default port, If you need to change it update it here + # and in launch.json. + debugpy.connect(5678) + + # This will ensure that execution is paused as soon as the debugger + # connects to VS Code. If you don't want to pause here comment this + # line and set breakpoints as appropriate. + debugpy.breakpoint() + +SERVER_PATH = os.fspath(pathlib.Path(__file__).parent / "lsp_server.py") +# NOTE: Set breakpoint in `lsp_server.py` before continuing. +runpy.run_path(SERVER_PATH, run_name="__main__") diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py new file mode 100644 index 0000000..074d769 --- /dev/null +++ b/server/src/lsp_server.py @@ -0,0 +1,32 @@ +"""Implementation of tool support over LSP.""" + +from __future__ import annotations + +import copy +import json +import os +import pathlib +import re +import sys +import sysconfig +import traceback +from typing import Any, Optional, Sequence + + +# ********************************************************** +# Update sys.path before importing any bundled libraries. +# ********************************************************** +def update_sys_path(path_to_add: str, strategy: str) -> None: + """Add given path to `sys.path`.""" + if path_to_add not in sys.path and os.path.isdir(path_to_add): + if strategy == "useBundled": + sys.path.insert(0, path_to_add) + elif strategy == "fromEnvironment": + sys.path.append(path_to_add) + + +# Ensure that we can import LSP libraries, and other bundled libraries. +update_sys_path( + os.fspath(pathlib.Path(__file__).parent.parent / "libs"), + os.getenv("LS_IMPORT_STRATEGY", "useBundled"), +) diff --git a/server/src/parser.ts b/server/src/parser.ts deleted file mode 100644 index 8b13789..0000000 --- a/server/src/parser.ts +++ /dev/null @@ -1 +0,0 @@ - diff --git a/server/src/server.ts b/server/src/server.ts deleted file mode 100644 index 1be5387..0000000 --- a/server/src/server.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { - createConnection, - TextDocuments, - ProposedFeatures, - TextDocumentSyncKind, - InitializeParams, - InitializeResult, - CompletionItemKind, - TextDocumentPositionParams, - CompletionItem, - TextEdit, - Range, - Position -} from "vscode-languageserver/node" -import { TextDocument } from "vscode-languageserver-textdocument" - -// Create a connection for the server, using Node's IPC as a transport. -// Also include all preview / proposed LSP features. -let connection = createConnection(ProposedFeatures.all) - -// Create a simple text document manager. -let documents: TextDocuments = new TextDocuments(TextDocument) - -connection.onInitialize((params: InitializeParams) => { - let capabilities = params.capabilities - - // Does the client support the `workspace/configuration` request? - // If not, we fall back using global settings. - const result: InitializeResult = { - capabilities: { - textDocumentSync: TextDocumentSyncKind.Incremental, - // Tell the client that this server supports code completion. - completionProvider: { - resolveProvider: false - } - } - } - return result -}) - -// This handler provides the initial list of the completion items. -connection.onCompletion((params: TextDocumentPositionParams): CompletionItem[] => { - const doc = documents.get(params.textDocument.uri) - if (!doc) return [] - - const text = doc.getText() - - return [] -}) - -connection.onDocumentFormatting(async (params, token) => { - const document = documents.get(params.textDocument.uri) - if (!document) return [] - - const originalText = document.getText() - - return [] -}) - -documents.onDidChangeContent(async (change) => { - connection.console.log("Document has changed") -}) - -// Make the text document manager listen on the connection -// for open, change and close text document events -documents.listen(connection) - -// Listen on the connection -connection.listen() diff --git a/server/tsconfig.json b/server/tsconfig.json deleted file mode 100644 index eb314f7..0000000 --- a/server/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "lib": ["es2020"], - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "strict": true, - "outDir": "out", - "rootDir": "src" - }, - "include": ["src"], - "exclude": ["node_modules", ".vscode-test"] -} diff --git a/server/uv.lock b/server/uv.lock new file mode 100644 index 0000000..4af21f5 --- /dev/null +++ b/server/uv.lock @@ -0,0 +1,83 @@ +version = 1 +requires-python = ">=3.11" + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "cattrs" +version = "25.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/2b/561d78f488dcc303da4639e02021311728fb7fda8006dd2835550cddd9ed/cattrs-25.1.1.tar.gz", hash = "sha256:c914b734e0f2d59e5b720d145ee010f1fd9a13ee93900922a2f3f9d593b8382c", size = 435016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/b0/215274ef0d835bbc1056392a367646648b6084e39d489099959aefcca2af/cattrs-25.1.1-py3-none-any.whl", hash = "sha256:1b40b2d3402af7be79a7e7e097a9b4cd16d4c06e6d526644b0b26a063a1cc064", size = 69386 }, +] + +[[package]] +name = "lsprotocol" +version = "2023.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/f6/6e80484ec078d0b50699ceb1833597b792a6c695f90c645fbaf54b947e6f/lsprotocol-2023.0.1.tar.gz", hash = "sha256:cc5c15130d2403c18b734304339e51242d3018a05c4f7d0f198ad6e0cd21861d", size = 69434 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/37/2351e48cb3309673492d3a8c59d407b75fb6630e560eb27ecd4da03adc9a/lsprotocol-2023.0.1-py3-none-any.whl", hash = "sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2", size = 70826 }, +] + +[[package]] +name = "ply" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567 }, +] + +[[package]] +name = "pygls" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cattrs" }, + { name = "lsprotocol" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/b9/41d173dad9eaa9db9c785a85671fc3d68961f08d67706dc2e79011e10b5c/pygls-1.3.1.tar.gz", hash = "sha256:140edceefa0da0e9b3c533547c892a42a7d2fd9217ae848c330c53d266a55018", size = 45527 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/19/b74a10dd24548e96e8c80226cbacb28b021bc3a168a7d2709fb0d0185348/pygls-1.3.1-py3-none-any.whl", hash = "sha256:6e00f11efc56321bdeb6eac04f6d86131f654c7d49124344a9ebb968da3dd91e", size = 56031 }, +] + +[[package]] +name = "server" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "ply" }, + { name = "pygls" }, +] + +[package.metadata] +requires-dist = [ + { name = "ply" }, + { name = "pygls" }, +] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, +] From 99718eb856e5ac21da8882a8798e72f499ade4f1 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Wed, 16 Jul 2025 17:14:09 +0200 Subject: [PATCH 03/12] python lsp --- .gitignore | 5 +- .vscode/launch.json | 61 +- .vscode/settings.json | 7 + client/src/common/constants.ts | 2 +- esbuild.js | 6 +- package.json | 6 +- server/.python-version | 1 - server/libs/attr/__init__.py | 104 + server/libs/attr/__init__.pyi | 389 + server/libs/attr/_cmp.py | 160 + server/libs/attr/_cmp.pyi | 13 + server/libs/attr/_compat.py | 94 + server/libs/attr/_config.py | 31 + server/libs/attr/_funcs.py | 468 + server/libs/attr/_make.py | 3123 ++++ server/libs/attr/_next_gen.py | 623 + server/libs/attr/_typing_compat.pyi | 15 + server/libs/attr/_version_info.py | 86 + server/libs/attr/_version_info.pyi | 9 + server/libs/attr/converters.py | 162 + server/libs/attr/converters.pyi | 19 + server/libs/attr/exceptions.py | 95 + server/libs/attr/exceptions.pyi | 17 + server/libs/attr/filters.py | 72 + server/libs/attr/filters.pyi | 6 + server/{README.md => libs/attr/py.typed} | 0 server/libs/attr/setters.py | 79 + server/libs/attr/setters.pyi | 20 + server/libs/attr/validators.py | 710 + server/libs/attr/validators.pyi | 86 + server/libs/attrs-25.3.0.dist-info/INSTALLER | 1 + server/libs/attrs-25.3.0.dist-info/METADATA | 232 + server/libs/attrs-25.3.0.dist-info/RECORD | 56 + server/libs/attrs-25.3.0.dist-info/REQUESTED | 0 server/libs/attrs-25.3.0.dist-info/WHEEL | 4 + .../attrs-25.3.0.dist-info/licenses/LICENSE | 21 + server/libs/attrs/__init__.py | 69 + server/libs/attrs/__init__.pyi | 263 + server/libs/attrs/converters.py | 3 + server/libs/attrs/exceptions.py | 3 + server/libs/attrs/filters.py | 3 + server/libs/attrs/py.typed | 0 server/libs/attrs/setters.py | 3 + server/libs/attrs/validators.py | 3 + server/libs/cattr/__init__.py | 25 + server/libs/cattr/converters.py | 8 + server/libs/cattr/disambiguators.py | 3 + server/libs/cattr/dispatch.py | 3 + server/libs/cattr/errors.py | 15 + server/libs/cattr/gen.py | 21 + server/libs/cattr/preconf/__init__.py | 3 + server/libs/cattr/preconf/bson.py | 5 + server/libs/cattr/preconf/json.py | 5 + server/libs/cattr/preconf/msgpack.py | 5 + server/libs/cattr/preconf/orjson.py | 5 + server/libs/cattr/preconf/pyyaml.py | 5 + server/libs/cattr/preconf/tomlkit.py | 5 + server/libs/cattr/preconf/ujson.py | 5 + server/libs/cattr/py.typed | 0 server/libs/cattrs-25.1.1.dist-info/INSTALLER | 1 + server/libs/cattrs-25.1.1.dist-info/METADATA | 161 + server/libs/cattrs-25.1.1.dist-info/RECORD | 102 + server/libs/cattrs-25.1.1.dist-info/REQUESTED | 0 server/libs/cattrs-25.1.1.dist-info/WHEEL | 4 + .../cattrs-25.1.1.dist-info/licenses/LICENSE | 11 + server/libs/cattrs/__init__.py | 57 + server/libs/cattrs/_compat.py | 428 + server/libs/cattrs/_generics.py | 32 + server/libs/cattrs/cols.py | 309 + server/libs/cattrs/converters.py | 1429 ++ server/libs/cattrs/disambiguators.py | 206 + server/libs/cattrs/dispatch.py | 193 + server/libs/cattrs/errors.py | 132 + server/libs/cattrs/fns.py | 22 + server/libs/cattrs/gen/__init__.py | 1048 ++ server/libs/cattrs/gen/_consts.py | 19 + server/libs/cattrs/gen/_generics.py | 79 + server/libs/cattrs/gen/_lc.py | 28 + server/libs/cattrs/gen/_shared.py | 64 + server/libs/cattrs/gen/typeddicts.py | 582 + server/libs/cattrs/literals.py | 11 + server/libs/cattrs/preconf/__init__.py | 55 + server/libs/cattrs/preconf/bson.py | 121 + server/libs/cattrs/preconf/cbor2.py | 56 + server/libs/cattrs/preconf/json.py | 69 + server/libs/cattrs/preconf/msgpack.py | 65 + server/libs/cattrs/preconf/msgspec.py | 202 + server/libs/cattrs/preconf/orjson.py | 108 + server/libs/cattrs/preconf/pyyaml.py | 74 + server/libs/cattrs/preconf/tomlkit.py | 89 + server/libs/cattrs/preconf/ujson.py | 66 + server/libs/cattrs/py.typed | 0 server/libs/cattrs/strategies/__init__.py | 12 + .../libs/cattrs/strategies/_class_methods.py | 64 + server/libs/cattrs/strategies/_subclasses.py | 243 + server/libs/cattrs/strategies/_unions.py | 264 + server/libs/cattrs/typealiases.py | 57 + server/libs/cattrs/types.py | 12 + server/libs/cattrs/v.py | 106 + .../lsprotocol-2023.0.1.dist-info/INSTALLER | 1 + .../lsprotocol-2023.0.1.dist-info/LICENSE | 21 + .../lsprotocol-2023.0.1.dist-info/METADATA | 65 + .../libs/lsprotocol-2023.0.1.dist-info/RECORD | 17 + .../lsprotocol-2023.0.1.dist-info/REQUESTED | 0 .../libs/lsprotocol-2023.0.1.dist-info/WHEEL | 4 + server/libs/lsprotocol/__init__.py | 2 + server/libs/lsprotocol/_hooks.py | 1237 ++ server/libs/lsprotocol/converters.py | 17 + server/libs/lsprotocol/py.typed | 0 server/libs/lsprotocol/types.py | 12898 ++++++++++++++++ server/libs/lsprotocol/validators.py | 47 + .../libs/packaging-25.0.dist-info/INSTALLER | 1 + server/libs/packaging-25.0.dist-info/METADATA | 105 + server/libs/packaging-25.0.dist-info/RECORD | 41 + .../libs/packaging-25.0.dist-info/REQUESTED | 0 server/libs/packaging-25.0.dist-info/WHEEL | 4 + .../packaging-25.0.dist-info/licenses/LICENSE | 3 + .../licenses/LICENSE.APACHE | 177 + .../licenses/LICENSE.BSD | 23 + server/libs/packaging/__init__.py | 15 + server/libs/packaging/_elffile.py | 109 + server/libs/packaging/_manylinux.py | 262 + server/libs/packaging/_musllinux.py | 85 + server/libs/packaging/_parser.py | 353 + server/libs/packaging/_structures.py | 61 + server/libs/packaging/_tokenizer.py | 195 + server/libs/packaging/licenses/__init__.py | 145 + server/libs/packaging/licenses/_spdx.py | 759 + server/libs/packaging/markers.py | 362 + server/libs/packaging/metadata.py | 862 ++ server/libs/packaging/py.typed | 0 server/libs/packaging/requirements.py | 91 + server/libs/packaging/specifiers.py | 1019 ++ server/libs/packaging/tags.py | 656 + server/libs/packaging/utils.py | 163 + server/libs/packaging/version.py | 582 + .../libs/ply-3.11.dist-info/DESCRIPTION.rst | 12 + server/libs/ply-3.11.dist-info/INSTALLER | 1 + server/libs/ply-3.11.dist-info/METADATA | 25 + server/libs/ply-3.11.dist-info/RECORD | 20 + server/libs/ply-3.11.dist-info/REQUESTED | 0 server/libs/ply-3.11.dist-info/WHEEL | 6 + server/libs/ply-3.11.dist-info/metadata.json | 1 + server/libs/ply-3.11.dist-info/top_level.txt | 1 + server/libs/ply/__init__.py | 5 + server/libs/ply/cpp.py | 914 ++ server/libs/ply/ctokens.py | 127 + server/libs/ply/lex.py | 1098 ++ server/libs/ply/yacc.py | 3502 +++++ server/libs/ply/ygen.py | 69 + server/libs/pygls-1.3.1.dist-info/INSTALLER | 1 + server/libs/pygls-1.3.1.dist-info/LICENSE.txt | 201 + server/libs/pygls-1.3.1.dist-info/METADATA | 105 + server/libs/pygls-1.3.1.dist-info/RECORD | 45 + server/libs/pygls-1.3.1.dist-info/REQUESTED | 0 server/libs/pygls-1.3.1.dist-info/WHEEL | 4 + server/libs/pygls/__init__.py | 25 + server/libs/pygls/capabilities.py | 460 + server/libs/pygls/client.py | 176 + server/libs/pygls/constants.py | 26 + server/libs/pygls/exceptions.py | 215 + server/libs/pygls/feature_manager.py | 244 + server/libs/pygls/lsp/__init__.py | 139 + server/libs/pygls/lsp/client.py | 1961 +++ server/libs/pygls/progress.py | 79 + server/libs/pygls/protocol/__init__.py | 78 + server/libs/pygls/protocol/json_rpc.py | 560 + server/libs/pygls/protocol/language_server.py | 569 + server/libs/pygls/protocol/lsp_meta.py | 51 + server/libs/pygls/py.typed | 2 + server/libs/pygls/server.py | 616 + server/libs/pygls/uris.py | 184 + server/libs/pygls/workspace/__init__.py | 97 + server/libs/pygls/workspace/position_codec.py | 206 + server/libs/pygls/workspace/text_document.py | 238 + server/libs/pygls/workspace/workspace.py | 323 + .../INSTALLER | 1 + .../METADATA | 68 + .../typing_extensions-4.14.1.dist-info/RECORD | 8 + .../REQUESTED | 0 .../typing_extensions-4.14.1.dist-info/WHEEL | 4 + .../licenses/LICENSE | 279 + server/libs/typing_extensions.py | 4244 +++++ server/noxfile.py | 163 +- server/pyproject.toml | 9 - server/requirements.in | 16 + server/requirements.txt | 38 + server/src/__init__.py | 0 server/src/common/completion_list.json | 16 + server/src/lsp_jsonrpc.py | 254 + server/src/lsp_runner.py | 77 + server/src/lsp_server.py | 563 + server/src/lsp_utils.py | 207 + server/uv.lock | 83 - 194 files changed, 52506 insertions(+), 116 deletions(-) create mode 100644 .vscode/settings.json delete mode 100644 server/.python-version create mode 100644 server/libs/attr/__init__.py create mode 100644 server/libs/attr/__init__.pyi create mode 100644 server/libs/attr/_cmp.py create mode 100644 server/libs/attr/_cmp.pyi create mode 100644 server/libs/attr/_compat.py create mode 100644 server/libs/attr/_config.py create mode 100644 server/libs/attr/_funcs.py create mode 100644 server/libs/attr/_make.py create mode 100644 server/libs/attr/_next_gen.py create mode 100644 server/libs/attr/_typing_compat.pyi create mode 100644 server/libs/attr/_version_info.py create mode 100644 server/libs/attr/_version_info.pyi create mode 100644 server/libs/attr/converters.py create mode 100644 server/libs/attr/converters.pyi create mode 100644 server/libs/attr/exceptions.py create mode 100644 server/libs/attr/exceptions.pyi create mode 100644 server/libs/attr/filters.py create mode 100644 server/libs/attr/filters.pyi rename server/{README.md => libs/attr/py.typed} (100%) create mode 100644 server/libs/attr/setters.py create mode 100644 server/libs/attr/setters.pyi create mode 100644 server/libs/attr/validators.py create mode 100644 server/libs/attr/validators.pyi create mode 100644 server/libs/attrs-25.3.0.dist-info/INSTALLER create mode 100644 server/libs/attrs-25.3.0.dist-info/METADATA create mode 100644 server/libs/attrs-25.3.0.dist-info/RECORD create mode 100644 server/libs/attrs-25.3.0.dist-info/REQUESTED create mode 100644 server/libs/attrs-25.3.0.dist-info/WHEEL create mode 100644 server/libs/attrs-25.3.0.dist-info/licenses/LICENSE create mode 100644 server/libs/attrs/__init__.py create mode 100644 server/libs/attrs/__init__.pyi create mode 100644 server/libs/attrs/converters.py create mode 100644 server/libs/attrs/exceptions.py create mode 100644 server/libs/attrs/filters.py create mode 100644 server/libs/attrs/py.typed create mode 100644 server/libs/attrs/setters.py create mode 100644 server/libs/attrs/validators.py create mode 100644 server/libs/cattr/__init__.py create mode 100644 server/libs/cattr/converters.py create mode 100644 server/libs/cattr/disambiguators.py create mode 100644 server/libs/cattr/dispatch.py create mode 100644 server/libs/cattr/errors.py create mode 100644 server/libs/cattr/gen.py create mode 100644 server/libs/cattr/preconf/__init__.py create mode 100644 server/libs/cattr/preconf/bson.py create mode 100644 server/libs/cattr/preconf/json.py create mode 100644 server/libs/cattr/preconf/msgpack.py create mode 100644 server/libs/cattr/preconf/orjson.py create mode 100644 server/libs/cattr/preconf/pyyaml.py create mode 100644 server/libs/cattr/preconf/tomlkit.py create mode 100644 server/libs/cattr/preconf/ujson.py create mode 100644 server/libs/cattr/py.typed create mode 100644 server/libs/cattrs-25.1.1.dist-info/INSTALLER create mode 100644 server/libs/cattrs-25.1.1.dist-info/METADATA create mode 100644 server/libs/cattrs-25.1.1.dist-info/RECORD create mode 100644 server/libs/cattrs-25.1.1.dist-info/REQUESTED create mode 100644 server/libs/cattrs-25.1.1.dist-info/WHEEL create mode 100644 server/libs/cattrs-25.1.1.dist-info/licenses/LICENSE create mode 100644 server/libs/cattrs/__init__.py create mode 100644 server/libs/cattrs/_compat.py create mode 100644 server/libs/cattrs/_generics.py create mode 100644 server/libs/cattrs/cols.py create mode 100644 server/libs/cattrs/converters.py create mode 100644 server/libs/cattrs/disambiguators.py create mode 100644 server/libs/cattrs/dispatch.py create mode 100644 server/libs/cattrs/errors.py create mode 100644 server/libs/cattrs/fns.py create mode 100644 server/libs/cattrs/gen/__init__.py create mode 100644 server/libs/cattrs/gen/_consts.py create mode 100644 server/libs/cattrs/gen/_generics.py create mode 100644 server/libs/cattrs/gen/_lc.py create mode 100644 server/libs/cattrs/gen/_shared.py create mode 100644 server/libs/cattrs/gen/typeddicts.py create mode 100644 server/libs/cattrs/literals.py create mode 100644 server/libs/cattrs/preconf/__init__.py create mode 100644 server/libs/cattrs/preconf/bson.py create mode 100644 server/libs/cattrs/preconf/cbor2.py create mode 100644 server/libs/cattrs/preconf/json.py create mode 100644 server/libs/cattrs/preconf/msgpack.py create mode 100644 server/libs/cattrs/preconf/msgspec.py create mode 100644 server/libs/cattrs/preconf/orjson.py create mode 100644 server/libs/cattrs/preconf/pyyaml.py create mode 100644 server/libs/cattrs/preconf/tomlkit.py create mode 100644 server/libs/cattrs/preconf/ujson.py create mode 100644 server/libs/cattrs/py.typed create mode 100644 server/libs/cattrs/strategies/__init__.py create mode 100644 server/libs/cattrs/strategies/_class_methods.py create mode 100644 server/libs/cattrs/strategies/_subclasses.py create mode 100644 server/libs/cattrs/strategies/_unions.py create mode 100644 server/libs/cattrs/typealiases.py create mode 100644 server/libs/cattrs/types.py create mode 100644 server/libs/cattrs/v.py create mode 100644 server/libs/lsprotocol-2023.0.1.dist-info/INSTALLER create mode 100644 server/libs/lsprotocol-2023.0.1.dist-info/LICENSE create mode 100644 server/libs/lsprotocol-2023.0.1.dist-info/METADATA create mode 100644 server/libs/lsprotocol-2023.0.1.dist-info/RECORD create mode 100644 server/libs/lsprotocol-2023.0.1.dist-info/REQUESTED create mode 100644 server/libs/lsprotocol-2023.0.1.dist-info/WHEEL create mode 100644 server/libs/lsprotocol/__init__.py create mode 100644 server/libs/lsprotocol/_hooks.py create mode 100644 server/libs/lsprotocol/converters.py create mode 100644 server/libs/lsprotocol/py.typed create mode 100644 server/libs/lsprotocol/types.py create mode 100644 server/libs/lsprotocol/validators.py create mode 100644 server/libs/packaging-25.0.dist-info/INSTALLER create mode 100644 server/libs/packaging-25.0.dist-info/METADATA create mode 100644 server/libs/packaging-25.0.dist-info/RECORD create mode 100644 server/libs/packaging-25.0.dist-info/REQUESTED create mode 100644 server/libs/packaging-25.0.dist-info/WHEEL create mode 100644 server/libs/packaging-25.0.dist-info/licenses/LICENSE create mode 100644 server/libs/packaging-25.0.dist-info/licenses/LICENSE.APACHE create mode 100644 server/libs/packaging-25.0.dist-info/licenses/LICENSE.BSD create mode 100644 server/libs/packaging/__init__.py create mode 100644 server/libs/packaging/_elffile.py create mode 100644 server/libs/packaging/_manylinux.py create mode 100644 server/libs/packaging/_musllinux.py create mode 100644 server/libs/packaging/_parser.py create mode 100644 server/libs/packaging/_structures.py create mode 100644 server/libs/packaging/_tokenizer.py create mode 100644 server/libs/packaging/licenses/__init__.py create mode 100644 server/libs/packaging/licenses/_spdx.py create mode 100644 server/libs/packaging/markers.py create mode 100644 server/libs/packaging/metadata.py create mode 100644 server/libs/packaging/py.typed create mode 100644 server/libs/packaging/requirements.py create mode 100644 server/libs/packaging/specifiers.py create mode 100644 server/libs/packaging/tags.py create mode 100644 server/libs/packaging/utils.py create mode 100644 server/libs/packaging/version.py create mode 100644 server/libs/ply-3.11.dist-info/DESCRIPTION.rst create mode 100644 server/libs/ply-3.11.dist-info/INSTALLER create mode 100644 server/libs/ply-3.11.dist-info/METADATA create mode 100644 server/libs/ply-3.11.dist-info/RECORD create mode 100644 server/libs/ply-3.11.dist-info/REQUESTED create mode 100644 server/libs/ply-3.11.dist-info/WHEEL create mode 100644 server/libs/ply-3.11.dist-info/metadata.json create mode 100644 server/libs/ply-3.11.dist-info/top_level.txt create mode 100644 server/libs/ply/__init__.py create mode 100644 server/libs/ply/cpp.py create mode 100644 server/libs/ply/ctokens.py create mode 100644 server/libs/ply/lex.py create mode 100644 server/libs/ply/yacc.py create mode 100644 server/libs/ply/ygen.py create mode 100644 server/libs/pygls-1.3.1.dist-info/INSTALLER create mode 100644 server/libs/pygls-1.3.1.dist-info/LICENSE.txt create mode 100644 server/libs/pygls-1.3.1.dist-info/METADATA create mode 100644 server/libs/pygls-1.3.1.dist-info/RECORD create mode 100644 server/libs/pygls-1.3.1.dist-info/REQUESTED create mode 100644 server/libs/pygls-1.3.1.dist-info/WHEEL create mode 100644 server/libs/pygls/__init__.py create mode 100644 server/libs/pygls/capabilities.py create mode 100644 server/libs/pygls/client.py create mode 100644 server/libs/pygls/constants.py create mode 100644 server/libs/pygls/exceptions.py create mode 100644 server/libs/pygls/feature_manager.py create mode 100644 server/libs/pygls/lsp/__init__.py create mode 100644 server/libs/pygls/lsp/client.py create mode 100644 server/libs/pygls/progress.py create mode 100644 server/libs/pygls/protocol/__init__.py create mode 100644 server/libs/pygls/protocol/json_rpc.py create mode 100644 server/libs/pygls/protocol/language_server.py create mode 100644 server/libs/pygls/protocol/lsp_meta.py create mode 100644 server/libs/pygls/py.typed create mode 100644 server/libs/pygls/server.py create mode 100644 server/libs/pygls/uris.py create mode 100644 server/libs/pygls/workspace/__init__.py create mode 100644 server/libs/pygls/workspace/position_codec.py create mode 100644 server/libs/pygls/workspace/text_document.py create mode 100644 server/libs/pygls/workspace/workspace.py create mode 100644 server/libs/typing_extensions-4.14.1.dist-info/INSTALLER create mode 100644 server/libs/typing_extensions-4.14.1.dist-info/METADATA create mode 100644 server/libs/typing_extensions-4.14.1.dist-info/RECORD create mode 100644 server/libs/typing_extensions-4.14.1.dist-info/REQUESTED create mode 100644 server/libs/typing_extensions-4.14.1.dist-info/WHEEL create mode 100644 server/libs/typing_extensions-4.14.1.dist-info/licenses/LICENSE create mode 100644 server/libs/typing_extensions.py delete mode 100644 server/pyproject.toml create mode 100644 server/requirements.in create mode 100644 server/requirements.txt create mode 100644 server/src/__init__.py create mode 100644 server/src/common/completion_list.json create mode 100644 server/src/lsp_jsonrpc.py create mode 100644 server/src/lsp_runner.py create mode 100644 server/src/lsp_utils.py delete mode 100644 server/uv.lock diff --git a/.gitignore b/.gitignore index 9e72bd2..b75d172 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,7 @@ node_modules out dist *.vsix -target \ No newline at end of file +target +.venv +__pycache__ +*.pyc \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 15c97d6..ccaf648 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,10 +11,7 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": ["--extensionDevelopmentPath=${workspaceFolder}"], - "outFiles": [ - "${workspaceFolder}out/client/**/*.js", - "${workspaceRoot}out/server/**/*.js" - ], + "outFiles": ["${workspaceFolder}/client/**/*.js"], "autoAttachChildProcesses": true, "preLaunchTask": { "type": "npm", @@ -22,16 +19,56 @@ } }, { - "name": "Extension Tests", + "name": "Python Attach", + "type": "debugpy", + "request": "attach", + "processId": "${command:pickProcess}", + "justMyCode": false, + "presentation": { + "hidden": false, + "group": "", + "order": 3 + } + }, + { + "name": "Debug Extension (hidden)", "type": "extensionHost", "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test" - ], - "outFiles": ["${workspaceFolder}/out/test/**/*.js"], - "preLaunchTask": "npm: watch" + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/client/**/*.js"], + "env": { + "USE_DEBUGPY": "True" + }, + "presentation": { + "hidden": true, + "group": "", + "order": 4 + } + }, + { + "name": "Python debug server (hidden)", + "type": "debugpy", + "request": "attach", + "listen": { "host": "localhost", "port": 5678 }, + "justMyCode": true, + "presentation": { + "hidden": true, + "group": "", + "order": 4 + } + } + ], + "compounds": [ + { + "name": "Debug Extension and Python", + "configurations": ["Python debug server (hidden)", "Debug Extension (hidden)"], + "stopAll": true, + "preLaunchTask": "npm: watch", + "presentation": { + "hidden": false, + "group": "", + "order": 1 + } } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e4943fd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "ruff.configuration": { + "lint": { + "extend-ignore": ["F821", "E402"] + } + } +} diff --git a/client/src/common/constants.ts b/client/src/common/constants.ts index 10f812f..35d809a 100644 --- a/client/src/common/constants.ts +++ b/client/src/common/constants.ts @@ -4,7 +4,7 @@ const folderName = path.basename(__dirname) export const EXTENSION_ROOT_DIR = folderName === "common" ? path.dirname(path.dirname(path.dirname(__dirname))) - : path.dirname(path.dirname(__dirname)) + : path.dirname(__dirname) export const BUNDLED_PYTHON_SCRIPTS_DIR = path.join(EXTENSION_ROOT_DIR, "server") export const SERVER_SCRIPT_PATH = path.join(BUNDLED_PYTHON_SCRIPTS_DIR, "src", `lsp_server.py`) export const DEBUG_SERVER_SCRIPT_PATH = path.join( diff --git a/esbuild.js b/esbuild.js index 540c2ad..5c96d50 100644 --- a/esbuild.js +++ b/esbuild.js @@ -5,15 +5,15 @@ const watch = process.argv.includes("--watch") async function main() { const ctx = await esbuild.context({ - entryPoints: ["client/src/extension.ts", "server/src/server.ts"], + entryPoints: ["client/src/extension.ts"], bundle: true, format: "cjs", minify: production, sourcemap: !production, sourcesContent: false, platform: "node", - outdir: "out", - // outfile: "client/out/extension.js", + // outdir: "out", + outfile: "./dist/extension.js", external: ["vscode"], logLevel: "silent", plugins: [ diff --git a/package.json b/package.json index 22776b7..af9377c 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,15 @@ "description": "", "version": "0.3.0", "publisher": "Christoph", + "serverInfo": { + "name": "NX Postprocessor Support", + "module": "nx-post-support" + }, "repository": { "type": "git", "url": "https://git.cbsk-tech.de/Christoph/nx_post_support.git" }, - "main": "./out/client/src/extension.js", + "main": "./dist/extension.js", "keywords": [ "cdl", "NX CDL", diff --git a/server/.python-version b/server/.python-version deleted file mode 100644 index 2c07333..0000000 --- a/server/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.11 diff --git a/server/libs/attr/__init__.py b/server/libs/attr/__init__.py new file mode 100644 index 0000000..5c6e065 --- /dev/null +++ b/server/libs/attr/__init__.py @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: MIT + +""" +Classes Without Boilerplate +""" + +from functools import partial +from typing import Callable, Literal, Protocol + +from . import converters, exceptions, filters, setters, validators +from ._cmp import cmp_using +from ._config import get_run_validators, set_run_validators +from ._funcs import asdict, assoc, astuple, has, resolve_types +from ._make import ( + NOTHING, + Attribute, + Converter, + Factory, + _Nothing, + attrib, + attrs, + evolve, + fields, + fields_dict, + make_class, + validate, +) +from ._next_gen import define, field, frozen, mutable +from ._version_info import VersionInfo + + +s = attributes = attrs +ib = attr = attrib +dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + + +class AttrsInstance(Protocol): + pass + + +NothingType = Literal[_Nothing.NOTHING] + +__all__ = [ + "NOTHING", + "Attribute", + "AttrsInstance", + "Converter", + "Factory", + "NothingType", + "asdict", + "assoc", + "astuple", + "attr", + "attrib", + "attributes", + "attrs", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "field", + "fields", + "fields_dict", + "filters", + "frozen", + "get_run_validators", + "has", + "ib", + "make_class", + "mutable", + "resolve_types", + "s", + "set_run_validators", + "setters", + "validate", + "validators", +] + + +def _make_getattr(mod_name: str) -> Callable: + """ + Create a metadata proxy for packaging information that uses *mod_name* in + its warnings and errors. + """ + + def __getattr__(name: str) -> str: + if name not in ("__version__", "__version_info__"): + msg = f"module {mod_name} has no attribute {name}" + raise AttributeError(msg) + + from importlib.metadata import metadata + + meta = metadata("attrs") + + if name == "__version_info__": + return VersionInfo._from_version_string(meta["version"]) + + return meta["version"] + + return __getattr__ + + +__getattr__ = _make_getattr(__name__) diff --git a/server/libs/attr/__init__.pyi b/server/libs/attr/__init__.pyi new file mode 100644 index 0000000..133e501 --- /dev/null +++ b/server/libs/attr/__init__.pyi @@ -0,0 +1,389 @@ +import enum +import sys + +from typing import ( + Any, + Callable, + Generic, + Literal, + Mapping, + Protocol, + Sequence, + TypeVar, + overload, +) + +# `import X as X` is required to make these public +from . import converters as converters +from . import exceptions as exceptions +from . import filters as filters +from . import setters as setters +from . import validators as validators +from ._cmp import cmp_using as cmp_using +from ._typing_compat import AttrsInstance_ +from ._version_info import VersionInfo +from attrs import ( + define as define, + field as field, + mutable as mutable, + frozen as frozen, + _EqOrderType, + _ValidatorType, + _ConverterType, + _ReprArgType, + _OnSetAttrType, + _OnSetAttrArgType, + _FieldTransformer, + _ValidatorArgType, +) + +if sys.version_info >= (3, 10): + from typing import TypeGuard, TypeAlias +else: + from typing_extensions import TypeGuard, TypeAlias + +if sys.version_info >= (3, 11): + from typing import dataclass_transform +else: + from typing_extensions import dataclass_transform + +__version__: str +__version_info__: VersionInfo +__title__: str +__description__: str +__url__: str +__uri__: str +__author__: str +__email__: str +__license__: str +__copyright__: str + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_FilterType = Callable[["Attribute[_T]", _T], bool] + +# We subclass this here to keep the protocol's qualified name clean. +class AttrsInstance(AttrsInstance_, Protocol): + pass + +_A = TypeVar("_A", bound=type[AttrsInstance]) + +class _Nothing(enum.Enum): + NOTHING = enum.auto() + +NOTHING = _Nothing.NOTHING +NothingType: TypeAlias = Literal[_Nothing.NOTHING] + +# NOTE: Factory lies about its return type to make this possible: +# `x: List[int] # = Factory(list)` +# Work around mypy issue #4554 in the common case by using an overload. + +@overload +def Factory(factory: Callable[[], _T]) -> _T: ... +@overload +def Factory( + factory: Callable[[Any], _T], + takes_self: Literal[True], +) -> _T: ... +@overload +def Factory( + factory: Callable[[], _T], + takes_self: Literal[False], +) -> _T: ... + +In = TypeVar("In") +Out = TypeVar("Out") + +class Converter(Generic[In, Out]): + @overload + def __init__(self, converter: Callable[[In], Out]) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, AttrsInstance, Attribute], Out], + *, + takes_self: Literal[True], + takes_field: Literal[True], + ) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, Attribute], Out], + *, + takes_field: Literal[True], + ) -> None: ... + @overload + def __init__( + self, + converter: Callable[[In, AttrsInstance], Out], + *, + takes_self: Literal[True], + ) -> None: ... + +class Attribute(Generic[_T]): + name: str + default: _T | None + validator: _ValidatorType[_T] | None + repr: _ReprArgType + cmp: _EqOrderType + eq: _EqOrderType + order: _EqOrderType + hash: bool | None + init: bool + converter: Converter | None + metadata: dict[Any, Any] + type: type[_T] | None + kw_only: bool + on_setattr: _OnSetAttrType + alias: str | None + + def evolve(self, **changes: Any) -> "Attribute[Any]": ... + +# NOTE: We had several choices for the annotation to use for type arg: +# 1) Type[_T] +# - Pros: Handles simple cases correctly +# - Cons: Might produce less informative errors in the case of conflicting +# TypeVars e.g. `attr.ib(default='bad', type=int)` +# 2) Callable[..., _T] +# - Pros: Better error messages than #1 for conflicting TypeVars +# - Cons: Terrible error messages for validator checks. +# e.g. attr.ib(type=int, validator=validate_str) +# -> error: Cannot infer function type argument +# 3) type (and do all of the work in the mypy plugin) +# - Pros: Simple here, and we could customize the plugin with our own errors. +# - Cons: Would need to write mypy plugin code to handle all the cases. +# We chose option #1. + +# `attr` lies about its return type to make the following possible: +# attr() -> Any +# attr(8) -> int +# attr(validator=) -> Whatever the callable expects. +# This makes this type of assignments possible: +# x: int = attr(8) +# +# This form catches explicit None or no default but with no other arguments +# returns Any. +@overload +def attrib( + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def attrib( + default: None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: type[_T] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def attrib( + default: _T, + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: type[_T] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def attrib( + default: _T | None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + type: object = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., +) -> Any: ... +@overload +@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) +def attrs( + maybe_cls: _C, + these: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., + unsafe_hash: bool | None = ..., +) -> _C: ... +@overload +@dataclass_transform(order_default=True, field_specifiers=(attrib, field)) +def attrs( + maybe_cls: None = ..., + these: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., + unsafe_hash: bool | None = ..., +) -> Callable[[_C], _C]: ... +def fields(cls: type[AttrsInstance]) -> Any: ... +def fields_dict(cls: type[AttrsInstance]) -> dict[str, Attribute[Any]]: ... +def validate(inst: AttrsInstance) -> None: ... +def resolve_types( + cls: _A, + globalns: dict[str, Any] | None = ..., + localns: dict[str, Any] | None = ..., + attribs: list[Attribute[Any]] | None = ..., + include_extras: bool = ..., +) -> _A: ... + +# TODO: add support for returning a proper attrs class from the mypy plugin +# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', +# [attr.ib()])` is valid +def make_class( + name: str, + attrs: list[str] | tuple[str, ...] | dict[str, Any], + bases: tuple[type, ...] = ..., + class_body: dict[str, Any] | None = ..., + repr_ns: str | None = ..., + repr: bool = ..., + cmp: _EqOrderType | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + collect_by_mro: bool = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., +) -> type: ... + +# _funcs -- + +# TODO: add support for returning TypedDict from the mypy plugin +# FIXME: asdict/astuple do not honor their factory args. Waiting on one of +# these: +# https://github.com/python/mypy/issues/4236 +# https://github.com/python/typing/issues/253 +# XXX: remember to fix attrs.asdict/astuple too! +def asdict( + inst: AttrsInstance, + recurse: bool = ..., + filter: _FilterType[Any] | None = ..., + dict_factory: type[Mapping[Any, Any]] = ..., + retain_collection_types: bool = ..., + value_serializer: Callable[[type, Attribute[Any], Any], Any] | None = ..., + tuple_keys: bool | None = ..., +) -> dict[str, Any]: ... + +# TODO: add support for returning NamedTuple from the mypy plugin +def astuple( + inst: AttrsInstance, + recurse: bool = ..., + filter: _FilterType[Any] | None = ..., + tuple_factory: type[Sequence[Any]] = ..., + retain_collection_types: bool = ..., +) -> tuple[Any, ...]: ... +def has(cls: type) -> TypeGuard[type[AttrsInstance]]: ... +def assoc(inst: _T, **changes: Any) -> _T: ... +def evolve(inst: _T, **changes: Any) -> _T: ... + +# _config -- + +def set_run_validators(run: bool) -> None: ... +def get_run_validators() -> bool: ... + +# aliases -- + +s = attributes = attrs +ib = attr = attrib +dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;) diff --git a/server/libs/attr/_cmp.py b/server/libs/attr/_cmp.py new file mode 100644 index 0000000..09bab49 --- /dev/null +++ b/server/libs/attr/_cmp.py @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: MIT + + +import functools +import types + +from ._make import __ne__ + + +_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} + + +def cmp_using( + eq=None, + lt=None, + le=None, + gt=None, + ge=None, + require_same_type=True, + class_name="Comparable", +): + """ + Create a class that can be passed into `attrs.field`'s ``eq``, ``order``, + and ``cmp`` arguments to customize field comparison. + + The resulting class will have a full set of ordering methods if at least + one of ``{lt, le, gt, ge}`` and ``eq`` are provided. + + Args: + eq (typing.Callable | None): + Callable used to evaluate equality of two objects. + + lt (typing.Callable | None): + Callable used to evaluate whether one object is less than another + object. + + le (typing.Callable | None): + Callable used to evaluate whether one object is less than or equal + to another object. + + gt (typing.Callable | None): + Callable used to evaluate whether one object is greater than + another object. + + ge (typing.Callable | None): + Callable used to evaluate whether one object is greater than or + equal to another object. + + require_same_type (bool): + When `True`, equality and ordering methods will return + `NotImplemented` if objects are not of the same type. + + class_name (str | None): Name of class. Defaults to "Comparable". + + See `comparison` for more details. + + .. versionadded:: 21.1.0 + """ + + body = { + "__slots__": ["value"], + "__init__": _make_init(), + "_requirements": [], + "_is_comparable_to": _is_comparable_to, + } + + # Add operations. + num_order_functions = 0 + has_eq_function = False + + if eq is not None: + has_eq_function = True + body["__eq__"] = _make_operator("eq", eq) + body["__ne__"] = __ne__ + + if lt is not None: + num_order_functions += 1 + body["__lt__"] = _make_operator("lt", lt) + + if le is not None: + num_order_functions += 1 + body["__le__"] = _make_operator("le", le) + + if gt is not None: + num_order_functions += 1 + body["__gt__"] = _make_operator("gt", gt) + + if ge is not None: + num_order_functions += 1 + body["__ge__"] = _make_operator("ge", ge) + + type_ = types.new_class( + class_name, (object,), {}, lambda ns: ns.update(body) + ) + + # Add same type requirement. + if require_same_type: + type_._requirements.append(_check_same_type) + + # Add total ordering if at least one operation was defined. + if 0 < num_order_functions < 4: + if not has_eq_function: + # functools.total_ordering requires __eq__ to be defined, + # so raise early error here to keep a nice stack. + msg = "eq must be define is order to complete ordering from lt, le, gt, ge." + raise ValueError(msg) + type_ = functools.total_ordering(type_) + + return type_ + + +def _make_init(): + """ + Create __init__ method. + """ + + def __init__(self, value): + """ + Initialize object with *value*. + """ + self.value = value + + return __init__ + + +def _make_operator(name, func): + """ + Create operator method. + """ + + def method(self, other): + if not self._is_comparable_to(other): + return NotImplemented + + result = func(self.value, other.value) + if result is NotImplemented: + return NotImplemented + + return result + + method.__name__ = f"__{name}__" + method.__doc__ = ( + f"Return a {_operation_names[name]} b. Computed by attrs." + ) + + return method + + +def _is_comparable_to(self, other): + """ + Check whether `other` is comparable to `self`. + """ + return all(func(self, other) for func in self._requirements) + + +def _check_same_type(self, other): + """ + Return True if *self* and *other* are of the same type, False otherwise. + """ + return other.value.__class__ is self.value.__class__ diff --git a/server/libs/attr/_cmp.pyi b/server/libs/attr/_cmp.pyi new file mode 100644 index 0000000..cc7893b --- /dev/null +++ b/server/libs/attr/_cmp.pyi @@ -0,0 +1,13 @@ +from typing import Any, Callable + +_CompareWithType = Callable[[Any, Any], bool] + +def cmp_using( + eq: _CompareWithType | None = ..., + lt: _CompareWithType | None = ..., + le: _CompareWithType | None = ..., + gt: _CompareWithType | None = ..., + ge: _CompareWithType | None = ..., + require_same_type: bool = ..., + class_name: str = ..., +) -> type: ... diff --git a/server/libs/attr/_compat.py b/server/libs/attr/_compat.py new file mode 100644 index 0000000..22fcd78 --- /dev/null +++ b/server/libs/attr/_compat.py @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: MIT + +import inspect +import platform +import sys +import threading + +from collections.abc import Mapping, Sequence # noqa: F401 +from typing import _GenericAlias + + +PYPY = platform.python_implementation() == "PyPy" +PY_3_9_PLUS = sys.version_info[:2] >= (3, 9) +PY_3_10_PLUS = sys.version_info[:2] >= (3, 10) +PY_3_11_PLUS = sys.version_info[:2] >= (3, 11) +PY_3_12_PLUS = sys.version_info[:2] >= (3, 12) +PY_3_13_PLUS = sys.version_info[:2] >= (3, 13) +PY_3_14_PLUS = sys.version_info[:2] >= (3, 14) + + +if PY_3_14_PLUS: # pragma: no cover + import annotationlib + + _get_annotations = annotationlib.get_annotations + +else: + + def _get_annotations(cls): + """ + Get annotations for *cls*. + """ + return cls.__dict__.get("__annotations__", {}) + + +class _AnnotationExtractor: + """ + Extract type annotations from a callable, returning None whenever there + is none. + """ + + __slots__ = ["sig"] + + def __init__(self, callable): + try: + self.sig = inspect.signature(callable) + except (ValueError, TypeError): # inspect failed + self.sig = None + + def get_first_param_type(self): + """ + Return the type annotation of the first argument if it's not empty. + """ + if not self.sig: + return None + + params = list(self.sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + return params[0].annotation + + return None + + def get_return_type(self): + """ + Return the return type if it's not empty. + """ + if ( + self.sig + and self.sig.return_annotation is not inspect.Signature.empty + ): + return self.sig.return_annotation + + return None + + +# Thread-local global to track attrs instances which are already being repr'd. +# This is needed because there is no other (thread-safe) way to pass info +# about the instances that are already being repr'd through the call stack +# in order to ensure we don't perform infinite recursion. +# +# For instance, if an instance contains a dict which contains that instance, +# we need to know that we're already repr'ing the outside instance from within +# the dict's repr() call. +# +# This lives here rather than in _make.py so that the functions in _make.py +# don't have a direct reference to the thread-local in their globals dict. +# If they have such a reference, it breaks cloudpickle. +repr_context = threading.local() + + +def get_generic_base(cl): + """If this is a generic class (A[str]), return the generic base for it.""" + if cl.__class__ is _GenericAlias: + return cl.__origin__ + return None diff --git a/server/libs/attr/_config.py b/server/libs/attr/_config.py new file mode 100644 index 0000000..4b25772 --- /dev/null +++ b/server/libs/attr/_config.py @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: MIT + +__all__ = ["get_run_validators", "set_run_validators"] + +_run_validators = True + + +def set_run_validators(run): + """ + Set whether or not validators are run. By default, they are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()` + instead. + """ + if not isinstance(run, bool): + msg = "'run' must be bool." + raise TypeError(msg) + global _run_validators + _run_validators = run + + +def get_run_validators(): + """ + Return whether or not validators are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()` + instead. + """ + return _run_validators diff --git a/server/libs/attr/_funcs.py b/server/libs/attr/_funcs.py new file mode 100644 index 0000000..c39fb8a --- /dev/null +++ b/server/libs/attr/_funcs.py @@ -0,0 +1,468 @@ +# SPDX-License-Identifier: MIT + + +import copy + +from ._compat import PY_3_9_PLUS, get_generic_base +from ._make import _OBJ_SETATTR, NOTHING, fields +from .exceptions import AttrsAttributeNotFoundError + + +def asdict( + inst, + recurse=True, + filter=None, + dict_factory=dict, + retain_collection_types=False, + value_serializer=None, +): + """ + Return the *attrs* attribute values of *inst* as a dict. + + Optionally recurse into other *attrs*-decorated classes. + + Args: + inst: Instance of an *attrs*-decorated class. + + recurse (bool): Recurse into classes that are also *attrs*-decorated. + + filter (~typing.Callable): + A callable whose return code determines whether an attribute or + element is included (`True`) or dropped (`False`). Is called with + the `attrs.Attribute` as the first argument and the value as the + second argument. + + dict_factory (~typing.Callable): + A callable to produce dictionaries from. For example, to produce + ordered dictionaries instead of normal Python dictionaries, pass in + ``collections.OrderedDict``. + + retain_collection_types (bool): + Do not convert to `list` when encountering an attribute whose type + is `tuple` or `set`. Only meaningful if *recurse* is `True`. + + value_serializer (typing.Callable | None): + A hook that is called for every attribute or dict key/value. It + receives the current instance, field and value and must return the + (updated) value. The hook is run *after* the optional *filter* has + been applied. + + Returns: + Return type of *dict_factory*. + + Raises: + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 16.0.0 *dict_factory* + .. versionadded:: 16.1.0 *retain_collection_types* + .. versionadded:: 20.3.0 *value_serializer* + .. versionadded:: 21.3.0 + If a dict has a collection for a key, it is serialized as a tuple. + """ + attrs = fields(inst.__class__) + rv = dict_factory() + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + + if value_serializer is not None: + v = value_serializer(inst, a, v) + + if recurse is True: + if has(v.__class__): + rv[a.name] = asdict( + v, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain_collection_types is True else list + items = [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in v + ] + try: + rv[a.name] = cf(items) + except TypeError: + if not issubclass(cf, tuple): + raise + # Workaround for TypeError: cf.__new__() missing 1 required + # positional argument (which appears, for a namedturle) + rv[a.name] = cf(*items) + elif isinstance(v, dict): + df = dict_factory + rv[a.name] = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in v.items() + ) + else: + rv[a.name] = v + else: + rv[a.name] = v + return rv + + +def _asdict_anything( + val, + is_key, + filter, + dict_factory, + retain_collection_types, + value_serializer, +): + """ + ``asdict`` only works on attrs instances, this works on anything. + """ + if getattr(val.__class__, "__attrs_attrs__", None) is not None: + # Attrs class. + rv = asdict( + val, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif isinstance(val, (tuple, list, set, frozenset)): + if retain_collection_types is True: + cf = val.__class__ + elif is_key: + cf = tuple + else: + cf = list + + rv = cf( + [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in val + ] + ) + elif isinstance(val, dict): + df = dict_factory + rv = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in val.items() + ) + else: + rv = val + if value_serializer is not None: + rv = value_serializer(None, None, rv) + + return rv + + +def astuple( + inst, + recurse=True, + filter=None, + tuple_factory=tuple, + retain_collection_types=False, +): + """ + Return the *attrs* attribute values of *inst* as a tuple. + + Optionally recurse into other *attrs*-decorated classes. + + Args: + inst: Instance of an *attrs*-decorated class. + + recurse (bool): + Recurse into classes that are also *attrs*-decorated. + + filter (~typing.Callable): + A callable whose return code determines whether an attribute or + element is included (`True`) or dropped (`False`). Is called with + the `attrs.Attribute` as the first argument and the value as the + second argument. + + tuple_factory (~typing.Callable): + A callable to produce tuples from. For example, to produce lists + instead of tuples. + + retain_collection_types (bool): + Do not convert to `list` or `dict` when encountering an attribute + which type is `tuple`, `dict` or `set`. Only meaningful if + *recurse* is `True`. + + Returns: + Return type of *tuple_factory* + + Raises: + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 16.2.0 + """ + attrs = fields(inst.__class__) + rv = [] + retain = retain_collection_types # Very long. :/ + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv.append( + astuple( + v, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain is True else list + items = [ + ( + astuple( + j, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(j.__class__) + else j + ) + for j in v + ] + try: + rv.append(cf(items)) + except TypeError: + if not issubclass(cf, tuple): + raise + # Workaround for TypeError: cf.__new__() missing 1 required + # positional argument (which appears, for a namedturle) + rv.append(cf(*items)) + elif isinstance(v, dict): + df = v.__class__ if retain is True else dict + rv.append( + df( + ( + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(kk.__class__) + else kk + ), + ( + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(vv.__class__) + else vv + ), + ) + for kk, vv in v.items() + ) + ) + else: + rv.append(v) + else: + rv.append(v) + + return rv if tuple_factory is list else tuple_factory(rv) + + +def has(cls): + """ + Check whether *cls* is a class with *attrs* attributes. + + Args: + cls (type): Class to introspect. + + Raises: + TypeError: If *cls* is not a class. + + Returns: + bool: + """ + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is not None: + return True + + # No attrs, maybe it's a specialized generic (A[str])? + generic_base = get_generic_base(cls) + if generic_base is not None: + generic_attrs = getattr(generic_base, "__attrs_attrs__", None) + if generic_attrs is not None: + # Stick it on here for speed next time. + cls.__attrs_attrs__ = generic_attrs + return generic_attrs is not None + return False + + +def assoc(inst, **changes): + """ + Copy *inst* and apply *changes*. + + This is different from `evolve` that applies the changes to the arguments + that create the new instance. + + `evolve`'s behavior is preferable, but there are `edge cases`_ where it + doesn't work. Therefore `assoc` is deprecated, but will not be removed. + + .. _`edge cases`: https://github.com/python-attrs/attrs/issues/251 + + Args: + inst: Instance of a class with *attrs* attributes. + + changes: Keyword changes in the new copy. + + Returns: + A copy of inst with *changes* incorporated. + + Raises: + attrs.exceptions.AttrsAttributeNotFoundError: + If *attr_name* couldn't be found on *cls*. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. deprecated:: 17.1.0 + Use `attrs.evolve` instead if you can. This function will not be + removed du to the slightly different approach compared to + `attrs.evolve`, though. + """ + new = copy.copy(inst) + attrs = fields(inst.__class__) + for k, v in changes.items(): + a = getattr(attrs, k, NOTHING) + if a is NOTHING: + msg = f"{k} is not an attrs attribute on {new.__class__}." + raise AttrsAttributeNotFoundError(msg) + _OBJ_SETATTR(new, k, v) + return new + + +def resolve_types( + cls, globalns=None, localns=None, attribs=None, include_extras=True +): + """ + Resolve any strings and forward annotations in type annotations. + + This is only required if you need concrete types in :class:`Attribute`'s + *type* field. In other words, you don't need to resolve your types if you + only use them for static type checking. + + With no arguments, names will be looked up in the module in which the class + was created. If this is not what you want, for example, if the name only + exists inside a method, you may pass *globalns* or *localns* to specify + other dictionaries in which to look up these names. See the docs of + `typing.get_type_hints` for more details. + + Args: + cls (type): Class to resolve. + + globalns (dict | None): Dictionary containing global variables. + + localns (dict | None): Dictionary containing local variables. + + attribs (list | None): + List of attribs for the given class. This is necessary when calling + from inside a ``field_transformer`` since *cls* is not an *attrs* + class yet. + + include_extras (bool): + Resolve more accurately, if possible. Pass ``include_extras`` to + ``typing.get_hints``, if supported by the typing module. On + supported Python versions (3.9+), this resolves the types more + accurately. + + Raises: + TypeError: If *cls* is not a class. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class and you didn't pass any attribs. + + NameError: If types cannot be resolved because of missing variables. + + Returns: + *cls* so you can use this function also as a class decorator. Please + note that you have to apply it **after** `attrs.define`. That means the + decorator has to come in the line **before** `attrs.define`. + + .. versionadded:: 20.1.0 + .. versionadded:: 21.1.0 *attribs* + .. versionadded:: 23.1.0 *include_extras* + """ + # Since calling get_type_hints is expensive we cache whether we've + # done it already. + if getattr(cls, "__attrs_types_resolved__", None) != cls: + import typing + + kwargs = {"globalns": globalns, "localns": localns} + + if PY_3_9_PLUS: + kwargs["include_extras"] = include_extras + + hints = typing.get_type_hints(cls, **kwargs) + for field in fields(cls) if attribs is None else attribs: + if field.name in hints: + # Since fields have been frozen we must work around it. + _OBJ_SETATTR(field, "type", hints[field.name]) + # We store the class we resolved so that subclasses know they haven't + # been resolved. + cls.__attrs_types_resolved__ = cls + + # Return the class so you can use it as a decorator too. + return cls diff --git a/server/libs/attr/_make.py b/server/libs/attr/_make.py new file mode 100644 index 0000000..e84d979 --- /dev/null +++ b/server/libs/attr/_make.py @@ -0,0 +1,3123 @@ +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +import abc +import contextlib +import copy +import enum +import inspect +import itertools +import linecache +import sys +import types +import unicodedata + +from collections.abc import Callable, Mapping +from functools import cached_property +from typing import Any, NamedTuple, TypeVar + +# We need to import _compat itself in addition to the _compat members to avoid +# having the thread-local in the globals here. +from . import _compat, _config, setters +from ._compat import ( + PY_3_10_PLUS, + PY_3_11_PLUS, + PY_3_13_PLUS, + _AnnotationExtractor, + _get_annotations, + get_generic_base, +) +from .exceptions import ( + DefaultAlreadySetError, + FrozenInstanceError, + NotAnAttrsClassError, + UnannotatedAttributeError, +) + + +# This is used at least twice, so cache it here. +_OBJ_SETATTR = object.__setattr__ +_INIT_FACTORY_PAT = "__attr_factory_%s" +_CLASSVAR_PREFIXES = ( + "typing.ClassVar", + "t.ClassVar", + "ClassVar", + "typing_extensions.ClassVar", +) +# we don't use a double-underscore prefix because that triggers +# name mangling when trying to create a slot for the field +# (when slots=True) +_HASH_CACHE_FIELD = "_attrs_cached_hash" + +_EMPTY_METADATA_SINGLETON = types.MappingProxyType({}) + +# Unique object for unequivocal getattr() defaults. +_SENTINEL = object() + +_DEFAULT_ON_SETATTR = setters.pipe(setters.convert, setters.validate) + + +class _Nothing(enum.Enum): + """ + Sentinel to indicate the lack of a value when `None` is ambiguous. + + If extending attrs, you can use ``typing.Literal[NOTHING]`` to show + that a value may be ``NOTHING``. + + .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. + .. versionchanged:: 22.2.0 ``NOTHING`` is now an ``enum.Enum`` variant. + """ + + NOTHING = enum.auto() + + def __repr__(self): + return "NOTHING" + + def __bool__(self): + return False + + +NOTHING = _Nothing.NOTHING +""" +Sentinel to indicate the lack of a value when `None` is ambiguous. + +When using in 3rd party code, use `attrs.NothingType` for type annotations. +""" + + +class _CacheHashWrapper(int): + """ + An integer subclass that pickles / copies as None + + This is used for non-slots classes with ``cache_hash=True``, to avoid + serializing a potentially (even likely) invalid hash value. Since `None` + is the default value for uncalculated hashes, whenever this is copied, + the copy's value for the hash should automatically reset. + + See GH #613 for more details. + """ + + def __reduce__(self, _none_constructor=type(None), _args=()): # noqa: B008 + return _none_constructor, _args + + +def attrib( + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, + alias=None, +): + """ + Create a new field / attribute on a class. + + Identical to `attrs.field`, except it's not keyword-only. + + Consider using `attrs.field` in new code (``attr.ib`` will *never* go away, + though). + + .. warning:: + + Does **nothing** unless the class is also decorated with + `attr.s` (or similar)! + + + .. versionadded:: 15.2.0 *convert* + .. versionadded:: 16.3.0 *metadata* + .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. + .. versionchanged:: 17.1.0 + *hash* is `None` and therefore mirrors *eq* by default. + .. versionadded:: 17.3.0 *type* + .. deprecated:: 17.4.0 *convert* + .. versionadded:: 17.4.0 + *converter* as a replacement for the deprecated *convert* to achieve + consistency with other noun-based arguments. + .. versionadded:: 18.1.0 + ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. + .. versionadded:: 18.2.0 *kw_only* + .. versionchanged:: 19.2.0 *convert* keyword argument removed. + .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 22.2.0 *alias* + """ + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq, order, True + ) + + if hash is not None and hash is not True and hash is not False: + msg = "Invalid value for hash. Must be True, False, or None." + raise TypeError(msg) + + if factory is not None: + if default is not NOTHING: + msg = ( + "The `default` and `factory` arguments are mutually exclusive." + ) + raise ValueError(msg) + if not callable(factory): + msg = "The `factory` argument must be a callable." + raise ValueError(msg) + default = Factory(factory) + + if metadata is None: + metadata = {} + + # Apply syntactic sugar by auto-wrapping. + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + if validator and isinstance(validator, (list, tuple)): + validator = and_(*validator) + + if converter and isinstance(converter, (list, tuple)): + converter = pipe(*converter) + + return _CountingAttr( + default=default, + validator=validator, + repr=repr, + cmp=None, + hash=hash, + init=init, + converter=converter, + metadata=metadata, + type=type, + kw_only=kw_only, + eq=eq, + eq_key=eq_key, + order=order, + order_key=order_key, + on_setattr=on_setattr, + alias=alias, + ) + + +def _compile_and_eval( + script: str, + globs: dict[str, Any] | None, + locs: Mapping[str, object] | None = None, + filename: str = "", +) -> None: + """ + Evaluate the script with the given global (globs) and local (locs) + variables. + """ + bytecode = compile(script, filename, "exec") + eval(bytecode, globs, locs) + + +def _linecache_and_compile( + script: str, + filename: str, + globs: dict[str, Any] | None, + locals: Mapping[str, object] | None = None, +) -> dict[str, Any]: + """ + Cache the script with _linecache_, compile it and return the _locals_. + """ + + locs = {} if locals is None else locals + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + count = 1 + base_filename = filename + while True: + linecache_tuple = ( + len(script), + None, + script.splitlines(True), + filename, + ) + old_val = linecache.cache.setdefault(filename, linecache_tuple) + if old_val == linecache_tuple: + break + + filename = f"{base_filename[:-1]}-{count}>" + count += 1 + + _compile_and_eval(script, globs, locs, filename) + + return locs + + +def _make_attr_tuple_class(cls_name: str, attr_names: list[str]) -> type: + """ + Create a tuple subclass to hold `Attribute`s for an `attrs` class. + + The subclass is a bare tuple with properties for names. + + class MyClassAttributes(tuple): + __slots__ = () + x = property(itemgetter(0)) + """ + attr_class_name = f"{cls_name}Attributes" + body = {} + for i, attr_name in enumerate(attr_names): + + def getter(self, i=i): + return self[i] + + body[attr_name] = property(getter) + return type(attr_class_name, (tuple,), body) + + +# Tuple class for extracted attributes from a class definition. +# `base_attrs` is a subset of `attrs`. +class _Attributes(NamedTuple): + attrs: type + base_attrs: list[Attribute] + base_attrs_map: dict[str, type] + + +def _is_class_var(annot): + """ + Check whether *annot* is a typing.ClassVar. + + The string comparison hack is used to avoid evaluating all string + annotations which would put attrs-based classes at a performance + disadvantage compared to plain old classes. + """ + annot = str(annot) + + # Annotation can be quoted. + if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): + annot = annot[1:-1] + + return annot.startswith(_CLASSVAR_PREFIXES) + + +def _has_own_attribute(cls, attrib_name): + """ + Check whether *cls* defines *attrib_name* (and doesn't just inherit it). + """ + return attrib_name in cls.__dict__ + + +def _collect_base_attrs( + cls, taken_attr_names +) -> tuple[list[Attribute], dict[str, type]]: + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in reversed(cls.__mro__[1:-1]): + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.inherited or a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) # noqa: PLW2901 + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + # For each name, only keep the freshest definition i.e. the furthest at the + # back. base_attr_map is fine because it gets overwritten with every new + # instance. + filtered = [] + seen = set() + for a in reversed(base_attrs): + if a.name in seen: + continue + filtered.insert(0, a) + seen.add(a.name) + + return filtered, base_attr_map + + +def _collect_base_attrs_broken(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + + N.B. *taken_attr_names* will be mutated. + + Adhere to the old incorrect behavior. + + Notably it collects from the front and considers inherited attributes which + leads to the buggy behavior reported in #428. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in cls.__mro__[1:-1]: + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) # noqa: PLW2901 + taken_attr_names.add(a.name) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + return base_attrs, base_attr_map + + +def _transform_attrs( + cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer +) -> _Attributes: + """ + Transform all `_CountingAttr`s on a class into `Attribute`s. + + If *these* is passed, use that and don't look for them on the class. + + If *collect_by_mro* is True, collect them in the correct MRO order, + otherwise use the old -- incorrect -- order. See #428. + + Return an `_Attributes`. + """ + cd = cls.__dict__ + anns = _get_annotations(cls) + + if these is not None: + ca_list = list(these.items()) + elif auto_attribs is True: + ca_names = { + name + for name, attr in cd.items() + if attr.__class__ is _CountingAttr + } + ca_list = [] + annot_names = set() + for attr_name, type in anns.items(): + if _is_class_var(type): + continue + annot_names.add(attr_name) + a = cd.get(attr_name, NOTHING) + + if a.__class__ is not _CountingAttr: + a = attrib(a) + ca_list.append((attr_name, a)) + + unannotated = ca_names - annot_names + if unannotated: + raise UnannotatedAttributeError( + "The following `attr.ib`s lack a type annotation: " + + ", ".join( + sorted(unannotated, key=lambda n: cd.get(n).counter) + ) + + "." + ) + else: + ca_list = sorted( + ( + (name, attr) + for name, attr in cd.items() + if attr.__class__ is _CountingAttr + ), + key=lambda e: e[1].counter, + ) + + fca = Attribute.from_counting_attr + own_attrs = [ + fca(attr_name, ca, anns.get(attr_name)) for attr_name, ca in ca_list + ] + + if collect_by_mro: + base_attrs, base_attr_map = _collect_base_attrs( + cls, {a.name for a in own_attrs} + ) + else: + base_attrs, base_attr_map = _collect_base_attrs_broken( + cls, {a.name for a in own_attrs} + ) + + if kw_only: + own_attrs = [a.evolve(kw_only=True) for a in own_attrs] + base_attrs = [a.evolve(kw_only=True) for a in base_attrs] + + attrs = base_attrs + own_attrs + + if field_transformer is not None: + attrs = tuple(field_transformer(cls, attrs)) + + # Check attr order after executing the field_transformer. + # Mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: + had_default = False + for a in (a for a in attrs if a.init is not False and a.kw_only is False): + if had_default is True and a.default is NOTHING: + msg = f"No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: {a!r}" + raise ValueError(msg) + + if had_default is False and a.default is not NOTHING: + had_default = True + + # Resolve default field alias after executing field_transformer. + # This allows field_transformer to differentiate between explicit vs + # default aliases and supply their own defaults. + for a in attrs: + if not a.alias: + # Evolve is very slow, so we hold our nose and do it dirty. + _OBJ_SETATTR.__get__(a)("alias", _default_init_alias_for(a.name)) + + # Create AttrsClass *after* applying the field_transformer since it may + # add or remove attributes! + attr_names = [a.name for a in attrs] + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + return _Attributes(AttrsClass(attrs), base_attrs, base_attr_map) + + +def _make_cached_property_getattr(cached_properties, original_getattr, cls): + lines = [ + # Wrapped to get `__class__` into closure cell for super() + # (It will be replaced with the newly constructed class after construction). + "def wrapper(_cls):", + " __class__ = _cls", + " def __getattr__(self, item, cached_properties=cached_properties, original_getattr=original_getattr, _cached_setattr_get=_cached_setattr_get):", + " func = cached_properties.get(item)", + " if func is not None:", + " result = func(self)", + " _setter = _cached_setattr_get(self)", + " _setter(item, result)", + " return result", + ] + if original_getattr is not None: + lines.append( + " return original_getattr(self, item)", + ) + else: + lines.extend( + [ + " try:", + " return super().__getattribute__(item)", + " except AttributeError:", + " if not hasattr(super(), '__getattr__'):", + " raise", + " return super().__getattr__(item)", + " original_error = f\"'{self.__class__.__name__}' object has no attribute '{item}'\"", + " raise AttributeError(original_error)", + ] + ) + + lines.extend( + [ + " return __getattr__", + "__getattr__ = wrapper(_cls)", + ] + ) + + unique_filename = _generate_unique_filename(cls, "getattr") + + glob = { + "cached_properties": cached_properties, + "_cached_setattr_get": _OBJ_SETATTR.__get__, + "original_getattr": original_getattr, + } + + return _linecache_and_compile( + "\n".join(lines), unique_filename, glob, locals={"_cls": cls} + )["__getattr__"] + + +def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + "__traceback__", + "__suppress_context__", + "__notes__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError + + +def _frozen_delattrs(self, name): + """ + Attached to frozen classes as __delattr__. + """ + if isinstance(self, BaseException) and name in ("__notes__",): + BaseException.__delattr__(self, name) + return + + raise FrozenInstanceError + + +def evolve(*args, **changes): + """ + Create a new instance, based on the first positional argument with + *changes* applied. + + .. tip:: + + On Python 3.13 and later, you can also use `copy.replace` instead. + + Args: + + inst: + Instance of a class with *attrs* attributes. *inst* must be passed + as a positional argument. + + changes: + Keyword changes in the new copy. + + Returns: + A copy of inst with *changes* incorporated. + + Raises: + TypeError: + If *attr_name* couldn't be found in the class ``__init__``. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + .. versionadded:: 17.1.0 + .. deprecated:: 23.1.0 + It is now deprecated to pass the instance using the keyword argument + *inst*. It will raise a warning until at least April 2024, after which + it will become an error. Always pass the instance as a positional + argument. + .. versionchanged:: 24.1.0 + *inst* can't be passed as a keyword argument anymore. + """ + try: + (inst,) = args + except ValueError: + msg = ( + f"evolve() takes 1 positional argument, but {len(args)} were given" + ) + raise TypeError(msg) from None + + cls = inst.__class__ + attrs = fields(cls) + for a in attrs: + if not a.init: + continue + attr_name = a.name # To deal with private attributes. + init_name = a.alias + if init_name not in changes: + changes[init_name] = getattr(inst, attr_name) + + return cls(**changes) + + +class _ClassBuilder: + """ + Iteratively build *one* class. + """ + + __slots__ = ( + "_add_method_dunders", + "_attr_names", + "_attrs", + "_base_attr_map", + "_base_names", + "_cache_hash", + "_cls", + "_cls_dict", + "_delete_attribs", + "_frozen", + "_has_custom_setattr", + "_has_post_init", + "_has_pre_init", + "_is_exc", + "_on_setattr", + "_pre_init_has_args", + "_repr_added", + "_script_snippets", + "_slots", + "_weakref_slot", + "_wrote_own_setattr", + ) + + def __init__( + self, + cls: type, + these, + slots, + frozen, + weakref_slot, + getstate_setstate, + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_custom_setattr, + field_transformer, + ): + attrs, base_attrs, base_map = _transform_attrs( + cls, + these, + auto_attribs, + kw_only, + collect_by_mro, + field_transformer, + ) + + self._cls = cls + self._cls_dict = dict(cls.__dict__) if slots else {} + self._attrs = attrs + self._base_names = {a.name for a in base_attrs} + self._base_attr_map = base_map + self._attr_names = tuple(a.name for a in attrs) + self._slots = slots + self._frozen = frozen + self._weakref_slot = weakref_slot + self._cache_hash = cache_hash + self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) + self._pre_init_has_args = False + if self._has_pre_init: + # Check if the pre init method has more arguments than just `self` + # We want to pass arguments if pre init expects arguments + pre_init_func = cls.__attrs_pre_init__ + pre_init_signature = inspect.signature(pre_init_func) + self._pre_init_has_args = len(pre_init_signature.parameters) > 1 + self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) + self._delete_attribs = not bool(these) + self._is_exc = is_exc + self._on_setattr = on_setattr + + self._has_custom_setattr = has_custom_setattr + self._wrote_own_setattr = False + + self._cls_dict["__attrs_attrs__"] = self._attrs + + if frozen: + self._cls_dict["__setattr__"] = _frozen_setattrs + self._cls_dict["__delattr__"] = _frozen_delattrs + + self._wrote_own_setattr = True + elif on_setattr in ( + _DEFAULT_ON_SETATTR, + setters.validate, + setters.convert, + ): + has_validator = has_converter = False + for a in attrs: + if a.validator is not None: + has_validator = True + if a.converter is not None: + has_converter = True + + if has_validator and has_converter: + break + if ( + ( + on_setattr == _DEFAULT_ON_SETATTR + and not (has_validator or has_converter) + ) + or (on_setattr == setters.validate and not has_validator) + or (on_setattr == setters.convert and not has_converter) + ): + # If class-level on_setattr is set to convert + validate, but + # there's no field to convert or validate, pretend like there's + # no on_setattr. + self._on_setattr = None + + if getstate_setstate: + ( + self._cls_dict["__getstate__"], + self._cls_dict["__setstate__"], + ) = self._make_getstate_setstate() + + # tuples of script, globs, hook + self._script_snippets: list[ + tuple[str, dict, Callable[[dict, dict], Any]] + ] = [] + self._repr_added = False + + # We want to only do this check once; in 99.9% of cases these + # exist. + if not hasattr(self._cls, "__module__") or not hasattr( + self._cls, "__qualname__" + ): + self._add_method_dunders = self._add_method_dunders_safe + else: + self._add_method_dunders = self._add_method_dunders_unsafe + + def __repr__(self): + return f"<_ClassBuilder(cls={self._cls.__name__})>" + + def _eval_snippets(self) -> None: + """ + Evaluate any registered snippets in one go. + """ + script = "\n".join([snippet[0] for snippet in self._script_snippets]) + globs = {} + for _, snippet_globs, _ in self._script_snippets: + globs.update(snippet_globs) + + locs = _linecache_and_compile( + script, + _generate_unique_filename(self._cls, "methods"), + globs, + ) + + for _, _, hook in self._script_snippets: + hook(self._cls_dict, locs) + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + self._eval_snippets() + if self._slots is True: + cls = self._create_slots_class() + else: + cls = self._patch_original_class() + if PY_3_10_PLUS: + cls = abc.update_abstractmethods(cls) + + # The method gets only called if it's not inherited from a base class. + # _has_own_attribute does NOT work properly for classmethods. + if ( + getattr(cls, "__attrs_init_subclass__", None) + and "__attrs_init_subclass__" not in cls.__dict__ + ): + cls.__attrs_init_subclass__() + + return cls + + def _patch_original_class(self): + """ + Apply accumulated methods and return the class. + """ + cls = self._cls + base_names = self._base_names + + # Clean class of attribute definitions (`attr.ib()`s). + if self._delete_attribs: + for name in self._attr_names: + if ( + name not in base_names + and getattr(cls, name, _SENTINEL) is not _SENTINEL + ): + # An AttributeError can happen if a base class defines a + # class variable and we want to set an attribute with the + # same name by using only a type annotation. + with contextlib.suppress(AttributeError): + delattr(cls, name) + + # Attach our dunder methods. + for name, value in self._cls_dict.items(): + setattr(cls, name, value) + + # If we've inherited an attrs __setattr__ and don't write our own, + # reset it to object's. + if not self._wrote_own_setattr and getattr( + cls, "__attrs_own_setattr__", False + ): + cls.__attrs_own_setattr__ = False + + if not self._has_custom_setattr: + cls.__setattr__ = _OBJ_SETATTR + + return cls + + def _create_slots_class(self): + """ + Build and return a new class with a `__slots__` attribute. + """ + cd = { + k: v + for k, v in self._cls_dict.items() + if k not in (*tuple(self._attr_names), "__dict__", "__weakref__") + } + + # If our class doesn't have its own implementation of __setattr__ + # (either from the user or by us), check the bases, if one of them has + # an attrs-made __setattr__, that needs to be reset. We don't walk the + # MRO because we only care about our immediate base classes. + # XXX: This can be confused by subclassing a slotted attrs class with + # XXX: a non-attrs class and subclass the resulting class with an attrs + # XXX: class. See `test_slotted_confused` for details. For now that's + # XXX: OK with us. + if not self._wrote_own_setattr: + cd["__attrs_own_setattr__"] = False + + if not self._has_custom_setattr: + for base_cls in self._cls.__bases__: + if base_cls.__dict__.get("__attrs_own_setattr__", False): + cd["__setattr__"] = _OBJ_SETATTR + break + + # Traverse the MRO to collect existing slots + # and check for an existing __weakref__. + existing_slots = {} + weakref_inherited = False + for base_cls in self._cls.__mro__[1:-1]: + if base_cls.__dict__.get("__weakref__", None) is not None: + weakref_inherited = True + existing_slots.update( + { + name: getattr(base_cls, name) + for name in getattr(base_cls, "__slots__", []) + } + ) + + base_names = set(self._base_names) + + names = self._attr_names + if ( + self._weakref_slot + and "__weakref__" not in getattr(self._cls, "__slots__", ()) + and "__weakref__" not in names + and not weakref_inherited + ): + names += ("__weakref__",) + + cached_properties = { + name: cached_prop.func + for name, cached_prop in cd.items() + if isinstance(cached_prop, cached_property) + } + + # Collect methods with a `__class__` reference that are shadowed in the new class. + # To know to update them. + additional_closure_functions_to_update = [] + if cached_properties: + class_annotations = _get_annotations(self._cls) + for name, func in cached_properties.items(): + # Add cached properties to names for slotting. + names += (name,) + # Clear out function from class to avoid clashing. + del cd[name] + additional_closure_functions_to_update.append(func) + annotation = inspect.signature(func).return_annotation + if annotation is not inspect.Parameter.empty: + class_annotations[name] = annotation + + original_getattr = cd.get("__getattr__") + if original_getattr is not None: + additional_closure_functions_to_update.append(original_getattr) + + cd["__getattr__"] = _make_cached_property_getattr( + cached_properties, original_getattr, self._cls + ) + + # We only add the names of attributes that aren't inherited. + # Setting __slots__ to inherited attributes wastes memory. + slot_names = [name for name in names if name not in base_names] + + # There are slots for attributes from current class + # that are defined in parent classes. + # As their descriptors may be overridden by a child class, + # we collect them here and update the class dict + reused_slots = { + slot: slot_descriptor + for slot, slot_descriptor in existing_slots.items() + if slot in slot_names + } + slot_names = [name for name in slot_names if name not in reused_slots] + cd.update(reused_slots) + if self._cache_hash: + slot_names.append(_HASH_CACHE_FIELD) + + cd["__slots__"] = tuple(slot_names) + + cd["__qualname__"] = self._cls.__qualname__ + + # Create new class based on old class and our methods. + cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) + + # The following is a fix for + # . + # If a method mentions `__class__` or uses the no-arg super(), the + # compiler will bake a reference to the class in the method itself + # as `method.__closure__`. Since we replace the class with a + # clone, we rewrite these references so it keeps working. + for item in itertools.chain( + cls.__dict__.values(), additional_closure_functions_to_update + ): + if isinstance(item, (classmethod, staticmethod)): + # Class- and staticmethods hide their functions inside. + # These might need to be rewritten as well. + closure_cells = getattr(item.__func__, "__closure__", None) + elif isinstance(item, property): + # Workaround for property `super()` shortcut (PY3-only). + # There is no universal way for other descriptors. + closure_cells = getattr(item.fget, "__closure__", None) + else: + closure_cells = getattr(item, "__closure__", None) + + if not closure_cells: # Catch None or the empty list. + continue + for cell in closure_cells: + try: + match = cell.cell_contents is self._cls + except ValueError: # noqa: PERF203 + # ValueError: Cell is empty + pass + else: + if match: + cell.cell_contents = cls + return cls + + def add_repr(self, ns): + script, globs = _make_repr_script(self._attrs, ns) + + def _attach_repr(cls_dict, globs): + cls_dict["__repr__"] = self._add_method_dunders(globs["__repr__"]) + + self._script_snippets.append((script, globs, _attach_repr)) + self._repr_added = True + return self + + def add_str(self): + if not self._repr_added: + msg = "__str__ can only be generated if a __repr__ exists." + raise ValueError(msg) + + def __str__(self): + return self.__repr__() + + self._cls_dict["__str__"] = self._add_method_dunders(__str__) + return self + + def _make_getstate_setstate(self): + """ + Create custom __setstate__ and __getstate__ methods. + """ + # __weakref__ is not writable. + state_attr_names = tuple( + an for an in self._attr_names if an != "__weakref__" + ) + + def slots_getstate(self): + """ + Automatically created by attrs. + """ + return {name: getattr(self, name) for name in state_attr_names} + + hash_caching_enabled = self._cache_hash + + def slots_setstate(self, state): + """ + Automatically created by attrs. + """ + __bound_setattr = _OBJ_SETATTR.__get__(self) + if isinstance(state, tuple): + # Backward compatibility with attrs instances pickled with + # attrs versions before v22.2.0 which stored tuples. + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + else: + for name in state_attr_names: + if name in state: + __bound_setattr(name, state[name]) + + # The hash code cache is not included when the object is + # serialized, but it still needs to be initialized to None to + # indicate that the first call to __hash__ should be a cache + # miss. + if hash_caching_enabled: + __bound_setattr(_HASH_CACHE_FIELD, None) + + return slots_getstate, slots_setstate + + def make_unhashable(self): + self._cls_dict["__hash__"] = None + return self + + def add_hash(self): + script, globs = _make_hash_script( + self._cls, + self._attrs, + frozen=self._frozen, + cache_hash=self._cache_hash, + ) + + def attach_hash(cls_dict: dict, locs: dict) -> None: + cls_dict["__hash__"] = self._add_method_dunders(locs["__hash__"]) + + self._script_snippets.append((script, globs, attach_hash)) + + return self + + def add_init(self): + script, globs, annotations = _make_init_script( + self._cls, + self._attrs, + self._has_pre_init, + self._pre_init_has_args, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=False, + ) + + def _attach_init(cls_dict, globs): + init = globs["__init__"] + init.__annotations__ = annotations + cls_dict["__init__"] = self._add_method_dunders(init) + + self._script_snippets.append((script, globs, _attach_init)) + + return self + + def add_replace(self): + self._cls_dict["__replace__"] = self._add_method_dunders( + lambda self, **changes: evolve(self, **changes) + ) + return self + + def add_match_args(self): + self._cls_dict["__match_args__"] = tuple( + field.name + for field in self._attrs + if field.init and not field.kw_only + ) + + def add_attrs_init(self): + script, globs, annotations = _make_init_script( + self._cls, + self._attrs, + self._has_pre_init, + self._pre_init_has_args, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=True, + ) + + def _attach_attrs_init(cls_dict, globs): + init = globs["__attrs_init__"] + init.__annotations__ = annotations + cls_dict["__attrs_init__"] = self._add_method_dunders(init) + + self._script_snippets.append((script, globs, _attach_attrs_init)) + + return self + + def add_eq(self): + cd = self._cls_dict + + script, globs = _make_eq_script(self._attrs) + + def _attach_eq(cls_dict, globs): + cls_dict["__eq__"] = self._add_method_dunders(globs["__eq__"]) + + self._script_snippets.append((script, globs, _attach_eq)) + + cd["__ne__"] = __ne__ + + return self + + def add_order(self): + cd = self._cls_dict + + cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_order(self._cls, self._attrs) + ) + + return self + + def add_setattr(self): + sa_attrs = {} + for a in self._attrs: + on_setattr = a.on_setattr or self._on_setattr + if on_setattr and on_setattr is not setters.NO_OP: + sa_attrs[a.name] = a, on_setattr + + if not sa_attrs: + return self + + if self._has_custom_setattr: + # We need to write a __setattr__ but there already is one! + msg = "Can't combine custom __setattr__ with on_setattr hooks." + raise ValueError(msg) + + # docstring comes from _add_method_dunders + def __setattr__(self, name, val): + try: + a, hook = sa_attrs[name] + except KeyError: + nval = val + else: + nval = hook(self, a, val) + + _OBJ_SETATTR(self, name, nval) + + self._cls_dict["__attrs_own_setattr__"] = True + self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) + self._wrote_own_setattr = True + + return self + + def _add_method_dunders_unsafe(self, method: Callable) -> Callable: + """ + Add __module__ and __qualname__ to a *method*. + """ + method.__module__ = self._cls.__module__ + + method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" + + method.__doc__ = ( + f"Method generated by attrs for class {self._cls.__qualname__}." + ) + + return method + + def _add_method_dunders_safe(self, method: Callable) -> Callable: + """ + Add __module__ and __qualname__ to a *method* if possible. + """ + with contextlib.suppress(AttributeError): + method.__module__ = self._cls.__module__ + + with contextlib.suppress(AttributeError): + method.__qualname__ = f"{self._cls.__qualname__}.{method.__name__}" + + with contextlib.suppress(AttributeError): + method.__doc__ = f"Method generated by attrs for class {self._cls.__qualname__}." + + return method + + +def _determine_attrs_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + msg = "Don't mix `cmp` with `eq' and `order`." + raise ValueError(msg) + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + return cmp, cmp + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq = default_eq + + if order is None: + order = eq + + if eq is False and order is True: + msg = "`order` can only be True if `eq` is True too." + raise ValueError(msg) + + return eq, order + + +def _determine_attrib_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + msg = "Don't mix `cmp` with `eq' and `order`." + raise ValueError(msg) + + def decide_callable_or_boolean(value): + """ + Decide whether a key function is used. + """ + if callable(value): + value, key = True, value + else: + key = None + return value, key + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + cmp, cmp_key = decide_callable_or_boolean(cmp) + return cmp, cmp_key, cmp, cmp_key + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq, eq_key = default_eq, None + else: + eq, eq_key = decide_callable_or_boolean(eq) + + if order is None: + order, order_key = eq, eq_key + else: + order, order_key = decide_callable_or_boolean(order) + + if eq is False and order is True: + msg = "`order` can only be True if `eq` is True too." + raise ValueError(msg) + + return eq, eq_key, order, order_key + + +def _determine_whether_to_implement( + cls, flag, auto_detect, dunders, default=True +): + """ + Check whether we should implement a set of methods for *cls*. + + *flag* is the argument passed into @attr.s like 'init', *auto_detect* the + same as passed into @attr.s and *dunders* is a tuple of attribute names + whose presence signal that the user has implemented it themselves. + + Return *default* if no reason for either for or against is found. + """ + if flag is True or flag is False: + return flag + + if flag is None and auto_detect is False: + return default + + # Logically, flag is None and auto_detect is True here. + for dunder in dunders: + if _has_own_attribute(cls, dunder): + return False + + return default + + +def attrs( + maybe_cls=None, + these=None, + repr_ns=None, + repr=None, + cmp=None, + hash=None, + init=None, + slots=False, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=False, + kw_only=False, + cache_hash=False, + auto_exc=False, + eq=None, + order=None, + auto_detect=False, + collect_by_mro=False, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, + unsafe_hash=None, +): + r""" + A class decorator that adds :term:`dunder methods` according to the + specified attributes using `attr.ib` or the *these* argument. + + Consider using `attrs.define` / `attrs.frozen` in new code (``attr.s`` will + *never* go away, though). + + Args: + repr_ns (str): + When using nested classes, there was no way in Python 2 to + automatically detect that. This argument allows to set a custom + name for a more meaningful ``repr`` output. This argument is + pointless in Python 3 and is therefore deprecated. + + .. caution:: + Refer to `attrs.define` for the rest of the parameters, but note that they + can have different defaults. + + Notably, leaving *on_setattr* as `None` will **not** add any hooks. + + .. versionadded:: 16.0.0 *slots* + .. versionadded:: 16.1.0 *frozen* + .. versionadded:: 16.3.0 *str* + .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. + .. versionchanged:: 17.1.0 + *hash* supports `None` as value which is also the default now. + .. versionadded:: 17.3.0 *auto_attribs* + .. versionchanged:: 18.1.0 + If *these* is passed, no attributes are deleted from the class body. + .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. + .. versionadded:: 18.2.0 *weakref_slot* + .. deprecated:: 18.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a + `DeprecationWarning` if the classes compared are subclasses of + each other. ``__eq`` and ``__ne__`` never tried to compared subclasses + to each other. + .. versionchanged:: 19.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider + subclasses comparable anymore. + .. versionadded:: 18.2.0 *kw_only* + .. versionadded:: 18.2.0 *cache_hash* + .. versionadded:: 19.1.0 *auto_exc* + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *auto_detect* + .. versionadded:: 20.1.0 *collect_by_mro* + .. versionadded:: 20.1.0 *getstate_setstate* + .. versionadded:: 20.1.0 *on_setattr* + .. versionadded:: 20.3.0 *field_transformer* + .. versionchanged:: 21.1.0 + ``init=False`` injects ``__attrs_init__`` + .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 21.3.0 *match_args* + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + .. deprecated:: 24.1.0 *repr_ns* + .. versionchanged:: 24.1.0 + Instances are not compared as tuples of attributes anymore, but using a + big ``and`` condition. This is faster and has more correct behavior for + uncomparable values like `math.nan`. + .. versionadded:: 24.1.0 + If a class has an *inherited* classmethod called + ``__attrs_init_subclass__``, it is executed after the class is created. + .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. + """ + if repr_ns is not None: + import warnings + + warnings.warn( + DeprecationWarning( + "The `repr_ns` argument is deprecated and will be removed in or after August 2025." + ), + stacklevel=2, + ) + + eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) + + # unsafe_hash takes precedence due to PEP 681. + if unsafe_hash is not None: + hash = unsafe_hash + + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + def wrap(cls): + is_frozen = frozen or _has_frozen_base_class(cls) + is_exc = auto_exc is True and issubclass(cls, BaseException) + has_own_setattr = auto_detect and _has_own_attribute( + cls, "__setattr__" + ) + + if has_own_setattr and is_frozen: + msg = "Can't freeze a class with a custom __setattr__." + raise ValueError(msg) + + builder = _ClassBuilder( + cls, + these, + slots, + is_frozen, + weakref_slot, + _determine_whether_to_implement( + cls, + getstate_setstate, + auto_detect, + ("__getstate__", "__setstate__"), + default=slots, + ), + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_own_setattr, + field_transformer, + ) + + if _determine_whether_to_implement( + cls, repr, auto_detect, ("__repr__",) + ): + builder.add_repr(repr_ns) + + if str is True: + builder.add_str() + + eq = _determine_whether_to_implement( + cls, eq_, auto_detect, ("__eq__", "__ne__") + ) + if not is_exc and eq is True: + builder.add_eq() + if not is_exc and _determine_whether_to_implement( + cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") + ): + builder.add_order() + + if not frozen: + builder.add_setattr() + + nonlocal hash + if ( + hash is None + and auto_detect is True + and _has_own_attribute(cls, "__hash__") + ): + hash = False + + if hash is not True and hash is not False and hash is not None: + # Can't use `hash in` because 1 == True for example. + msg = "Invalid value for hash. Must be True, False, or None." + raise TypeError(msg) + + if hash is False or (hash is None and eq is False) or is_exc: + # Don't do anything. Should fall back to __object__'s __hash__ + # which is by id. + if cache_hash: + msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." + raise TypeError(msg) + elif hash is True or ( + hash is None and eq is True and is_frozen is True + ): + # Build a __hash__ if told so, or if it's safe. + builder.add_hash() + else: + # Raise TypeError on attempts to hash. + if cache_hash: + msg = "Invalid value for cache_hash. To use hash caching, hashing must be either explicitly or implicitly enabled." + raise TypeError(msg) + builder.make_unhashable() + + if _determine_whether_to_implement( + cls, init, auto_detect, ("__init__",) + ): + builder.add_init() + else: + builder.add_attrs_init() + if cache_hash: + msg = "Invalid value for cache_hash. To use hash caching, init must be True." + raise TypeError(msg) + + if PY_3_13_PLUS and not _has_own_attribute(cls, "__replace__"): + builder.add_replace() + + if ( + PY_3_10_PLUS + and match_args + and not _has_own_attribute(cls, "__match_args__") + ): + builder.add_match_args() + + return builder.build_class() + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but `None` if used as `@attrs()`. + if maybe_cls is None: + return wrap + + return wrap(maybe_cls) + + +_attrs = attrs +""" +Internal alias so we can use it in functions that take an argument called +*attrs*. +""" + + +def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ is _frozen_setattrs + + +def _generate_unique_filename(cls: type, func_name: str) -> str: + """ + Create a "filename" suitable for a function being generated. + """ + return ( + f"" + ) + + +def _make_hash_script( + cls: type, attrs: list[Attribute], frozen: bool, cache_hash: bool +) -> tuple[str, dict]: + attrs = tuple( + a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) + ) + + tab = " " + + type_hash = hash(_generate_unique_filename(cls, "hash")) + # If eq is custom generated, we need to include the functions in globs + globs = {} + + hash_def = "def __hash__(self" + hash_func = "hash((" + closing_braces = "))" + if not cache_hash: + hash_def += "):" + else: + hash_def += ", *" + + hash_def += ", _cache_wrapper=__import__('attr._make')._make._CacheHashWrapper):" + hash_func = "_cache_wrapper(" + hash_func + closing_braces += ")" + + method_lines = [hash_def] + + def append_hash_computation_lines(prefix, indent): + """ + Generate the code for actually computing the hash code. + Below this will either be returned directly or used to compute + a value which is then cached, depending on the value of cache_hash + """ + + method_lines.extend( + [ + indent + prefix + hash_func, + indent + f" {type_hash},", + ] + ) + + for a in attrs: + if a.eq_key: + cmp_name = f"_{a.name}_key" + globs[cmp_name] = a.eq_key + method_lines.append( + indent + f" {cmp_name}(self.{a.name})," + ) + else: + method_lines.append(indent + f" self.{a.name},") + + method_lines.append(indent + " " + closing_braces) + + if cache_hash: + method_lines.append(tab + f"if self.{_HASH_CACHE_FIELD} is None:") + if frozen: + append_hash_computation_lines( + f"object.__setattr__(self, '{_HASH_CACHE_FIELD}', ", tab * 2 + ) + method_lines.append(tab * 2 + ")") # close __setattr__ + else: + append_hash_computation_lines( + f"self.{_HASH_CACHE_FIELD} = ", tab * 2 + ) + method_lines.append(tab + f"return self.{_HASH_CACHE_FIELD}") + else: + append_hash_computation_lines("return ", tab) + + script = "\n".join(method_lines) + return script, globs + + +def _add_hash(cls: type, attrs: list[Attribute]): + """ + Add a hash method to *cls*. + """ + script, globs = _make_hash_script( + cls, attrs, frozen=False, cache_hash=False + ) + _compile_and_eval( + script, globs, filename=_generate_unique_filename(cls, "__hash__") + ) + cls.__hash__ = globs["__hash__"] + return cls + + +def __ne__(self, other): + """ + Check equality and either forward a NotImplemented or + return the result negated. + """ + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + + return not result + + +def _make_eq_script(attrs: list) -> tuple[str, dict]: + """ + Create __eq__ method for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.eq] + + lines = [ + "def __eq__(self, other):", + " if other.__class__ is not self.__class__:", + " return NotImplemented", + ] + + globs = {} + if attrs: + lines.append(" return (") + for a in attrs: + if a.eq_key: + cmp_name = f"_{a.name}_key" + # Add the key function to the global namespace + # of the evaluated function. + globs[cmp_name] = a.eq_key + lines.append( + f" {cmp_name}(self.{a.name}) == {cmp_name}(other.{a.name})" + ) + else: + lines.append(f" self.{a.name} == other.{a.name}") + if a is not attrs[-1]: + lines[-1] = f"{lines[-1]} and" + lines.append(" )") + else: + lines.append(" return True") + + script = "\n".join(lines) + + return script, globs + + +def _make_order(cls, attrs): + """ + Create ordering methods for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.order] + + def attrs_to_tuple(obj): + """ + Save us some typing. + """ + return tuple( + key(value) if key else value + for value, key in ( + (getattr(obj, a.name), a.order_key) for a in attrs + ) + ) + + def __lt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) < attrs_to_tuple(other) + + return NotImplemented + + def __le__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) <= attrs_to_tuple(other) + + return NotImplemented + + def __gt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) > attrs_to_tuple(other) + + return NotImplemented + + def __ge__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) >= attrs_to_tuple(other) + + return NotImplemented + + return __lt__, __le__, __gt__, __ge__ + + +def _add_eq(cls, attrs=None): + """ + Add equality methods to *cls* with *attrs*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + script, globs = _make_eq_script(attrs) + _compile_and_eval( + script, globs, filename=_generate_unique_filename(cls, "__eq__") + ) + cls.__eq__ = globs["__eq__"] + cls.__ne__ = __ne__ + + return cls + + +def _make_repr_script(attrs, ns) -> tuple[str, dict]: + """ + Create the source and globs for a __repr__ and return it. + """ + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, (repr if a.repr is True else a.repr), a.init) + for a in attrs + if a.repr is not False + ) + globs = { + name + "_repr": r for name, r, _ in attr_names_with_reprs if r != repr + } + globs["_compat"] = _compat + globs["AttributeError"] = AttributeError + globs["NOTHING"] = NOTHING + attribute_fragments = [] + for name, r, i in attr_names_with_reprs: + accessor = ( + "self." + name if i else 'getattr(self, "' + name + '", NOTHING)' + ) + fragment = ( + "%s={%s!r}" % (name, accessor) + if r == repr + else "%s={%s_repr(%s)}" % (name, name, accessor) + ) + attribute_fragments.append(fragment) + repr_fragment = ", ".join(attribute_fragments) + + if ns is None: + cls_name_fragment = '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' + else: + cls_name_fragment = ns + ".{self.__class__.__name__}" + + lines = [ + "def __repr__(self):", + " try:", + " already_repring = _compat.repr_context.already_repring", + " except AttributeError:", + " already_repring = {id(self),}", + " _compat.repr_context.already_repring = already_repring", + " else:", + " if id(self) in already_repring:", + " return '...'", + " else:", + " already_repring.add(id(self))", + " try:", + f" return f'{cls_name_fragment}({repr_fragment})'", + " finally:", + " already_repring.remove(id(self))", + ] + + return "\n".join(lines), globs + + +def _add_repr(cls, ns=None, attrs=None): + """ + Add a repr method to *cls*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + script, globs = _make_repr_script(attrs, ns) + _compile_and_eval( + script, globs, filename=_generate_unique_filename(cls, "__repr__") + ) + cls.__repr__ = globs["__repr__"] + return cls + + +def fields(cls): + """ + Return the tuple of *attrs* attributes for a class. + + The tuple also allows accessing the fields by their names (see below for + examples). + + Args: + cls (type): Class to introspect. + + Raises: + TypeError: If *cls* is not a class. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + Returns: + tuple (with name accessors) of `attrs.Attribute` + + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + .. versionchanged:: 23.1.0 Add support for generic classes. + """ + generic_base = get_generic_base(cls) + + if generic_base is None and not isinstance(cls, type): + msg = "Passed object must be a class." + raise TypeError(msg) + + attrs = getattr(cls, "__attrs_attrs__", None) + + if attrs is None: + if generic_base is not None: + attrs = getattr(generic_base, "__attrs_attrs__", None) + if attrs is not None: + # Even though this is global state, stick it on here to speed + # it up. We rely on `cls` being cached for this to be + # efficient. + cls.__attrs_attrs__ = attrs + return attrs + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) + + return attrs + + +def fields_dict(cls): + """ + Return an ordered dictionary of *attrs* attributes for a class, whose keys + are the attribute names. + + Args: + cls (type): Class to introspect. + + Raises: + TypeError: If *cls* is not a class. + + attrs.exceptions.NotAnAttrsClassError: + If *cls* is not an *attrs* class. + + Returns: + dict[str, attrs.Attribute]: Dict of attribute name to definition + + .. versionadded:: 18.1.0 + """ + if not isinstance(cls, type): + msg = "Passed object must be a class." + raise TypeError(msg) + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + msg = f"{cls!r} is not an attrs-decorated class." + raise NotAnAttrsClassError(msg) + return {a.name: a for a in attrs} + + +def validate(inst): + """ + Validate all attributes on *inst* that have a validator. + + Leaves all exceptions through. + + Args: + inst: Instance of a class with *attrs* attributes. + """ + if _config._run_validators is False: + return + + for a in fields(inst.__class__): + v = a.validator + if v is not None: + v(inst, a, getattr(inst, a.name)) + + +def _is_slot_attr(a_name, base_attr_map): + """ + Check if the attribute name comes from a slot class. + """ + cls = base_attr_map.get(a_name) + return cls and "__slots__" in cls.__dict__ + + +def _make_init_script( + cls, + attrs, + pre_init, + pre_init_has_args, + post_init, + frozen, + slots, + cache_hash, + base_attr_map, + is_exc, + cls_on_setattr, + attrs_init, +) -> tuple[str, dict, dict]: + has_cls_on_setattr = ( + cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP + ) + + if frozen and has_cls_on_setattr: + msg = "Frozen classes can't use on_setattr." + raise ValueError(msg) + + needs_cached_setattr = cache_hash or frozen + filtered_attrs = [] + attr_dict = {} + for a in attrs: + if not a.init and a.default is NOTHING: + continue + + filtered_attrs.append(a) + attr_dict[a.name] = a + + if a.on_setattr is not None: + if frozen is True: + msg = "Frozen classes can't use on_setattr." + raise ValueError(msg) + + needs_cached_setattr = True + elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: + needs_cached_setattr = True + + script, globs, annotations = _attrs_to_init_script( + filtered_attrs, + frozen, + slots, + pre_init, + pre_init_has_args, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_cls_on_setattr, + "__attrs_init__" if attrs_init else "__init__", + ) + if cls.__module__ in sys.modules: + # This makes typing.get_type_hints(CLS.__init__) resolve string types. + globs.update(sys.modules[cls.__module__].__dict__) + + globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) + + if needs_cached_setattr: + # Save the lookup overhead in __init__ if we need to circumvent + # setattr hooks. + globs["_cached_setattr_get"] = _OBJ_SETATTR.__get__ + + return script, globs, annotations + + +def _setattr(attr_name: str, value_var: str, has_on_setattr: bool) -> str: + """ + Use the cached object.setattr to set *attr_name* to *value_var*. + """ + return f"_setattr('{attr_name}', {value_var})" + + +def _setattr_with_converter( + attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter +) -> str: + """ + Use the cached object.setattr to set *attr_name* to *value_var*, but run + its converter first. + """ + return f"_setattr('{attr_name}', {converter._fmt_converter_call(attr_name, value_var)})" + + +def _assign(attr_name: str, value: str, has_on_setattr: bool) -> str: + """ + Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise + relegate to _setattr. + """ + if has_on_setattr: + return _setattr(attr_name, value, True) + + return f"self.{attr_name} = {value}" + + +def _assign_with_converter( + attr_name: str, value_var: str, has_on_setattr: bool, converter: Converter +) -> str: + """ + Unless *attr_name* has an on_setattr hook, use normal assignment after + conversion. Otherwise relegate to _setattr_with_converter. + """ + if has_on_setattr: + return _setattr_with_converter(attr_name, value_var, True, converter) + + return f"self.{attr_name} = {converter._fmt_converter_call(attr_name, value_var)}" + + +def _determine_setters( + frozen: bool, slots: bool, base_attr_map: dict[str, type] +): + """ + Determine the correct setter functions based on whether a class is frozen + and/or slotted. + """ + if frozen is True: + if slots is True: + return (), _setattr, _setattr_with_converter + + # Dict frozen classes assign directly to __dict__. + # But only if the attribute doesn't come from an ancestor slot + # class. + # Note _inst_dict will be used again below if cache_hash is True + + def fmt_setter( + attr_name: str, value_var: str, has_on_setattr: bool + ) -> str: + if _is_slot_attr(attr_name, base_attr_map): + return _setattr(attr_name, value_var, has_on_setattr) + + return f"_inst_dict['{attr_name}'] = {value_var}" + + def fmt_setter_with_converter( + attr_name: str, + value_var: str, + has_on_setattr: bool, + converter: Converter, + ) -> str: + if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): + return _setattr_with_converter( + attr_name, value_var, has_on_setattr, converter + ) + + return f"_inst_dict['{attr_name}'] = {converter._fmt_converter_call(attr_name, value_var)}" + + return ( + ("_inst_dict = self.__dict__",), + fmt_setter, + fmt_setter_with_converter, + ) + + # Not frozen -- we can just assign directly. + return (), _assign, _assign_with_converter + + +def _attrs_to_init_script( + attrs: list[Attribute], + is_frozen: bool, + is_slotted: bool, + call_pre_init: bool, + pre_init_has_args: bool, + call_post_init: bool, + does_cache_hash: bool, + base_attr_map: dict[str, type], + is_exc: bool, + needs_cached_setattr: bool, + has_cls_on_setattr: bool, + method_name: str, +) -> tuple[str, dict, dict]: + """ + Return a script of an initializer for *attrs*, a dict of globals, and + annotations for the initializer. + + The globals are required by the generated script. + """ + lines = ["self.__attrs_pre_init__()"] if call_pre_init else [] + + if needs_cached_setattr: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. Note _setattr will be used again below if + # does_cache_hash is True. + "_setattr = _cached_setattr_get(self)" + ) + + extra_lines, fmt_setter, fmt_setter_with_converter = _determine_setters( + is_frozen, is_slotted, base_attr_map + ) + lines.extend(extra_lines) + + args = [] + kw_only_args = [] + attrs_to_validate = [] + + # This is a dictionary of names to validator and converter callables. + # Injecting this into __init__ globals lets us avoid lookups. + names_for_globals = {} + annotations = {"return": None} + + for a in attrs: + if a.validator: + attrs_to_validate.append(a) + + attr_name = a.name + has_on_setattr = a.on_setattr is not None or ( + a.on_setattr is not setters.NO_OP and has_cls_on_setattr + ) + # a.alias is set to maybe-mangled attr_name in _ClassBuilder if not + # explicitly provided + arg_name = a.alias + + has_factory = isinstance(a.default, Factory) + maybe_self = "self" if has_factory and a.default.takes_self else "" + + if a.converter is not None and not isinstance(a.converter, Converter): + converter = Converter(a.converter) + else: + converter = a.converter + + if a.init is False: + if has_factory: + init_factory_name = _INIT_FACTORY_PAT % (a.name,) + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + init_factory_name + f"({maybe_self})", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + fmt_setter( + attr_name, + init_factory_name + f"({maybe_self})", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + elif converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + fmt_setter( + attr_name, + f"attr_dict['{attr_name}'].default", + has_on_setattr, + ) + ) + elif a.default is not NOTHING and not has_factory: + arg = f"{arg_name}=attr_dict['{attr_name}'].default" + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + elif has_factory: + arg = f"{arg_name}=NOTHING" + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + lines.append(f"if {arg_name} is not NOTHING:") + + init_factory_name = _INIT_FACTORY_PAT % (a.name,) + if converter is not None: + lines.append( + " " + + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter_with_converter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + converter, + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append( + " " + fmt_setter(attr_name, arg_name, has_on_setattr) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.kw_only: + kw_only_args.append(arg_name) + else: + args.append(arg_name) + + if converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr, converter + ) + ) + names_for_globals[converter._get_global_name(a.name)] = ( + converter.converter + ) + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + if a.init is True: + if a.type is not None and converter is None: + annotations[arg_name] = a.type + elif converter is not None and converter._first_param_type: + # Use the type from the converter if present. + annotations[arg_name] = converter._first_param_type + + if attrs_to_validate: # we can skip this if there are no validators. + names_for_globals["_config"] = _config + lines.append("if _config._run_validators is True:") + for a in attrs_to_validate: + val_name = "__attr_validator_" + a.name + attr_name = "__attr_" + a.name + lines.append(f" {val_name}(self, {attr_name}, self.{a.name})") + names_for_globals[val_name] = a.validator + names_for_globals[attr_name] = a + + if call_post_init: + lines.append("self.__attrs_post_init__()") + + # Because this is set only after __attrs_post_init__ is called, a crash + # will result if post-init tries to access the hash code. This seemed + # preferable to setting this beforehand, in which case alteration to field + # values during post-init combined with post-init accessing the hash code + # would result in silent bugs. + if does_cache_hash: + if is_frozen: + if is_slotted: + init_hash_cache = f"_setattr('{_HASH_CACHE_FIELD}', None)" + else: + init_hash_cache = f"_inst_dict['{_HASH_CACHE_FIELD}'] = None" + else: + init_hash_cache = f"self.{_HASH_CACHE_FIELD} = None" + lines.append(init_hash_cache) + + # For exceptions we rely on BaseException.__init__ for proper + # initialization. + if is_exc: + vals = ",".join(f"self.{a.name}" for a in attrs if a.init) + + lines.append(f"BaseException.__init__(self, {vals})") + + args = ", ".join(args) + pre_init_args = args + if kw_only_args: + # leading comma & kw_only args + args += f"{', ' if args else ''}*, {', '.join(kw_only_args)}" + pre_init_kw_only_args = ", ".join( + [ + f"{kw_arg_name}={kw_arg_name}" + # We need to remove the defaults from the kw_only_args. + for kw_arg_name in (kwa.split("=")[0] for kwa in kw_only_args) + ] + ) + pre_init_args += ", " if pre_init_args else "" + pre_init_args += pre_init_kw_only_args + + if call_pre_init and pre_init_has_args: + # If pre init method has arguments, pass same arguments as `__init__`. + lines[0] = f"self.__attrs_pre_init__({pre_init_args})" + + # Python <3.12 doesn't allow backslashes in f-strings. + NL = "\n " + return ( + f"""def {method_name}(self, {args}): + {NL.join(lines) if lines else "pass"} +""", + names_for_globals, + annotations, + ) + + +def _default_init_alias_for(name: str) -> str: + """ + The default __init__ parameter name for a field. + + This performs private-name adjustment via leading-unscore stripping, + and is the default value of Attribute.alias if not provided. + """ + + return name.lstrip("_") + + +class Attribute: + """ + *Read-only* representation of an attribute. + + .. warning:: + + You should never instantiate this class yourself. + + The class has *all* arguments of `attr.ib` (except for ``factory`` which is + only syntactic sugar for ``default=Factory(...)`` plus the following: + + - ``name`` (`str`): The name of the attribute. + - ``alias`` (`str`): The __init__ parameter name of the attribute, after + any explicit overrides and default private-attribute-name handling. + - ``inherited`` (`bool`): Whether or not that attribute has been inherited + from a base class. + - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The + callables that are used for comparing and ordering objects by this + attribute, respectively. These are set by passing a callable to + `attr.ib`'s ``eq``, ``order``, or ``cmp`` arguments. See also + :ref:`comparison customization `. + + Instances of this class are frequently used for introspection purposes + like: + + - `fields` returns a tuple of them. + - Validators get them passed as the first argument. + - The :ref:`field transformer ` hook receives a list of + them. + - The ``alias`` property exposes the __init__ parameter name of the field, + with any overrides and default private-attribute handling applied. + + + .. versionadded:: 20.1.0 *inherited* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.2.0 *inherited* is not taken into account for + equality checks and hashing anymore. + .. versionadded:: 21.1.0 *eq_key* and *order_key* + .. versionadded:: 22.2.0 *alias* + + For the full version history of the fields, see `attr.ib`. + """ + + # These slots must NOT be reordered because we use them later for + # instantiation. + __slots__ = ( # noqa: RUF023 + "name", + "default", + "validator", + "repr", + "eq", + "eq_key", + "order", + "order_key", + "hash", + "init", + "metadata", + "type", + "converter", + "kw_only", + "inherited", + "on_setattr", + "alias", + ) + + def __init__( + self, + name, + default, + validator, + repr, + cmp, # XXX: unused, remove along with other cmp code. + hash, + init, + inherited, + metadata=None, + type=None, + converter=None, + kw_only=False, + eq=None, + eq_key=None, + order=None, + order_key=None, + on_setattr=None, + alias=None, + ): + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq_key or eq, order_key or order, True + ) + + # Cache this descriptor here to speed things up later. + bound_setattr = _OBJ_SETATTR.__get__(self) + + # Despite the big red warning, people *do* instantiate `Attribute` + # themselves. + bound_setattr("name", name) + bound_setattr("default", default) + bound_setattr("validator", validator) + bound_setattr("repr", repr) + bound_setattr("eq", eq) + bound_setattr("eq_key", eq_key) + bound_setattr("order", order) + bound_setattr("order_key", order_key) + bound_setattr("hash", hash) + bound_setattr("init", init) + bound_setattr("converter", converter) + bound_setattr( + "metadata", + ( + types.MappingProxyType(dict(metadata)) # Shallow copy + if metadata + else _EMPTY_METADATA_SINGLETON + ), + ) + bound_setattr("type", type) + bound_setattr("kw_only", kw_only) + bound_setattr("inherited", inherited) + bound_setattr("on_setattr", on_setattr) + bound_setattr("alias", alias) + + def __setattr__(self, name, value): + raise FrozenInstanceError + + @classmethod + def from_counting_attr(cls, name: str, ca: _CountingAttr, type=None): + # type holds the annotated value. deal with conflicts: + if type is None: + type = ca.type + elif ca.type is not None: + msg = f"Type annotation and type argument cannot both be present for '{name}'." + raise ValueError(msg) + return cls( + name, + ca._default, + ca._validator, + ca.repr, + None, + ca.hash, + ca.init, + False, + ca.metadata, + type, + ca.converter, + ca.kw_only, + ca.eq, + ca.eq_key, + ca.order, + ca.order_key, + ca.on_setattr, + ca.alias, + ) + + # Don't use attrs.evolve since fields(Attribute) doesn't work + def evolve(self, **changes): + """ + Copy *self* and apply *changes*. + + This works similarly to `attrs.evolve` but that function does not work + with :class:`attrs.Attribute`. + + It is mainly meant to be used for `transform-fields`. + + .. versionadded:: 20.3.0 + """ + new = copy.copy(self) + + new._setattrs(changes.items()) + + return new + + # Don't use _add_pickle since fields(Attribute) doesn't work + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple( + getattr(self, name) if name != "metadata" else dict(self.metadata) + for name in self.__slots__ + ) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + self._setattrs(zip(self.__slots__, state)) + + def _setattrs(self, name_values_pairs): + bound_setattr = _OBJ_SETATTR.__get__(self) + for name, value in name_values_pairs: + if name != "metadata": + bound_setattr(name, value) + else: + bound_setattr( + name, + ( + types.MappingProxyType(dict(value)) + if value + else _EMPTY_METADATA_SINGLETON + ), + ) + + +_a = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=(name != "metadata"), + init=True, + inherited=False, + alias=_default_init_alias_for(name), + ) + for name in Attribute.__slots__ +] + +Attribute = _add_hash( + _add_eq( + _add_repr(Attribute, attrs=_a), + attrs=[a for a in _a if a.name != "inherited"], + ), + attrs=[a for a in _a if a.hash and a.name != "inherited"], +) + + +class _CountingAttr: + """ + Intermediate representation of attributes that uses a counter to preserve + the order in which the attributes have been defined. + + *Internal* data structure of the attrs library. Running into is most + likely the result of a bug like a forgotten `@attr.s` decorator. + """ + + __slots__ = ( + "_default", + "_validator", + "alias", + "converter", + "counter", + "eq", + "eq_key", + "hash", + "init", + "kw_only", + "metadata", + "on_setattr", + "order", + "order_key", + "repr", + "type", + ) + __attrs_attrs__ = ( + *tuple( + Attribute( + name=name, + alias=_default_init_alias_for(name), + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=True, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "on_setattr", + "alias", + ) + ), + Attribute( + name="metadata", + alias="metadata", + default=None, + validator=None, + repr=True, + cmp=None, + hash=False, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ), + ) + cls_counter = 0 + + def __init__( + self, + default, + validator, + repr, + cmp, + hash, + init, + converter, + metadata, + type, + kw_only, + eq, + eq_key, + order, + order_key, + on_setattr, + alias, + ): + _CountingAttr.cls_counter += 1 + self.counter = _CountingAttr.cls_counter + self._default = default + self._validator = validator + self.converter = converter + self.repr = repr + self.eq = eq + self.eq_key = eq_key + self.order = order + self.order_key = order_key + self.hash = hash + self.init = init + self.metadata = metadata + self.type = type + self.kw_only = kw_only + self.on_setattr = on_setattr + self.alias = alias + + def validator(self, meth): + """ + Decorator that adds *meth* to the list of validators. + + Returns *meth* unchanged. + + .. versionadded:: 17.1.0 + """ + if self._validator is None: + self._validator = meth + else: + self._validator = and_(self._validator, meth) + return meth + + def default(self, meth): + """ + Decorator that allows to set the default for an attribute. + + Returns *meth* unchanged. + + Raises: + DefaultAlreadySetError: If default has been set before. + + .. versionadded:: 17.1.0 + """ + if self._default is not NOTHING: + raise DefaultAlreadySetError + + self._default = Factory(meth, takes_self=True) + + return meth + + +_CountingAttr = _add_eq(_add_repr(_CountingAttr)) + + +class Factory: + """ + Stores a factory callable. + + If passed as the default value to `attrs.field`, the factory is used to + generate a new value. + + Args: + factory (typing.Callable): + A callable that takes either none or exactly one mandatory + positional argument depending on *takes_self*. + + takes_self (bool): + Pass the partially initialized instance that is being initialized + as a positional argument. + + .. versionadded:: 17.1.0 *takes_self* + """ + + __slots__ = ("factory", "takes_self") + + def __init__(self, factory, takes_self=False): + self.factory = factory + self.takes_self = takes_self + + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + for name, value in zip(self.__slots__, state): + setattr(self, name, value) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in Factory.__slots__ +] + +Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) + + +class Converter: + """ + Stores a converter callable. + + Allows for the wrapped converter to take additional arguments. The + arguments are passed in the order they are documented. + + Args: + converter (Callable): A callable that converts the passed value. + + takes_self (bool): + Pass the partially initialized instance that is being initialized + as a positional argument. (default: `False`) + + takes_field (bool): + Pass the field definition (an :class:`Attribute`) into the + converter as a positional argument. (default: `False`) + + .. versionadded:: 24.1.0 + """ + + __slots__ = ( + "__call__", + "_first_param_type", + "_global_name", + "converter", + "takes_field", + "takes_self", + ) + + def __init__(self, converter, *, takes_self=False, takes_field=False): + self.converter = converter + self.takes_self = takes_self + self.takes_field = takes_field + + ex = _AnnotationExtractor(converter) + self._first_param_type = ex.get_first_param_type() + + if not (self.takes_self or self.takes_field): + self.__call__ = lambda value, _, __: self.converter(value) + elif self.takes_self and not self.takes_field: + self.__call__ = lambda value, instance, __: self.converter( + value, instance + ) + elif not self.takes_self and self.takes_field: + self.__call__ = lambda value, __, field: self.converter( + value, field + ) + else: + self.__call__ = lambda value, instance, field: self.converter( + value, instance, field + ) + + rt = ex.get_return_type() + if rt is not None: + self.__call__.__annotations__["return"] = rt + + @staticmethod + def _get_global_name(attr_name: str) -> str: + """ + Return the name that a converter for an attribute name *attr_name* + would have. + """ + return f"__attr_converter_{attr_name}" + + def _fmt_converter_call(self, attr_name: str, value_var: str) -> str: + """ + Return a string that calls the converter for an attribute name + *attr_name* and the value in variable named *value_var* according to + `self.takes_self` and `self.takes_field`. + """ + if not (self.takes_self or self.takes_field): + return f"{self._get_global_name(attr_name)}({value_var})" + + if self.takes_self and self.takes_field: + return f"{self._get_global_name(attr_name)}({value_var}, self, attr_dict['{attr_name}'])" + + if self.takes_self: + return f"{self._get_global_name(attr_name)}({value_var}, self)" + + return f"{self._get_global_name(attr_name)}({value_var}, attr_dict['{attr_name}'])" + + def __getstate__(self): + """ + Return a dict containing only converter and takes_self -- the rest gets + computed when loading. + """ + return { + "converter": self.converter, + "takes_self": self.takes_self, + "takes_field": self.takes_field, + } + + def __setstate__(self, state): + """ + Load instance from state. + """ + self.__init__(**state) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in ("converter", "takes_self", "takes_field") +] + +Converter = _add_hash( + _add_eq(_add_repr(Converter, attrs=_f), attrs=_f), attrs=_f +) + + +def make_class( + name, attrs, bases=(object,), class_body=None, **attributes_arguments +): + r""" + A quick way to create a new class called *name* with *attrs*. + + .. note:: + + ``make_class()`` is a thin wrapper around `attr.s`, not `attrs.define` + which means that it doesn't come with some of the improved defaults. + + For example, if you want the same ``on_setattr`` behavior as in + `attrs.define`, you have to pass the hooks yourself: ``make_class(..., + on_setattr=setters.pipe(setters.convert, setters.validate)`` + + .. warning:: + + It is *your* duty to ensure that the class name and the attribute names + are valid identifiers. ``make_class()`` will *not* validate them for + you. + + Args: + name (str): The name for the new class. + + attrs (list | dict): + A list of names or a dictionary of mappings of names to `attr.ib`\ + s / `attrs.field`\ s. + + The order is deduced from the order of the names or attributes + inside *attrs*. Otherwise the order of the definition of the + attributes is used. + + bases (tuple[type, ...]): Classes that the new class will subclass. + + class_body (dict): + An optional dictionary of class attributes for the new class. + + attributes_arguments: Passed unmodified to `attr.s`. + + Returns: + type: A new class with *attrs*. + + .. versionadded:: 17.1.0 *bases* + .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + .. versionchanged:: 23.2.0 *class_body* + .. versionchanged:: 25.2.0 Class names can now be unicode. + """ + # Class identifiers are converted into the normal form NFKC while parsing + name = unicodedata.normalize("NFKC", name) + + if isinstance(attrs, dict): + cls_dict = attrs + elif isinstance(attrs, (list, tuple)): + cls_dict = {a: attrib() for a in attrs} + else: + msg = "attrs argument must be a dict or a list." + raise TypeError(msg) + + pre_init = cls_dict.pop("__attrs_pre_init__", None) + post_init = cls_dict.pop("__attrs_post_init__", None) + user_init = cls_dict.pop("__init__", None) + + body = {} + if class_body is not None: + body.update(class_body) + if pre_init is not None: + body["__attrs_pre_init__"] = pre_init + if post_init is not None: + body["__attrs_post_init__"] = post_init + if user_init is not None: + body["__init__"] = user_init + + type_ = types.new_class(name, bases, {}, lambda ns: ns.update(body)) + + # For pickling to work, the __module__ variable needs to be set to the + # frame where the class is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython). + with contextlib.suppress(AttributeError, ValueError): + type_.__module__ = sys._getframe(1).f_globals.get( + "__name__", "__main__" + ) + + # We do it here for proper warnings with meaningful stacklevel. + cmp = attributes_arguments.pop("cmp", None) + ( + attributes_arguments["eq"], + attributes_arguments["order"], + ) = _determine_attrs_eq_order( + cmp, + attributes_arguments.get("eq"), + attributes_arguments.get("order"), + True, + ) + + cls = _attrs(these=cls_dict, **attributes_arguments)(type_) + # Only add type annotations now or "_attrs()" will complain: + cls.__annotations__ = { + k: v.type for k, v in cls_dict.items() if v.type is not None + } + return cls + + +# These are required by within this module so we define them here and merely +# import into .validators / .converters. + + +@attrs(slots=True, unsafe_hash=True) +class _AndValidator: + """ + Compose many validators to a single one. + """ + + _validators = attrib() + + def __call__(self, inst, attr, value): + for v in self._validators: + v(inst, attr, value) + + +def and_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators. + + Args: + validators (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of validators. + + .. versionadded:: 17.1.0 + """ + vals = [] + for validator in validators: + vals.extend( + validator._validators + if isinstance(validator, _AndValidator) + else [validator] + ) + + return _AndValidator(tuple(vals)) + + +def pipe(*converters): + """ + A converter that composes multiple converters into one. + + When called on a value, it runs all wrapped converters, returning the + *last* value. + + Type annotations will be inferred from the wrapped converters', if they + have any. + + converters (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of converters. + + .. versionadded:: 20.1.0 + """ + + return_instance = any(isinstance(c, Converter) for c in converters) + + if return_instance: + + def pipe_converter(val, inst, field): + for c in converters: + val = ( + c(val, inst, field) if isinstance(c, Converter) else c(val) + ) + + return val + + else: + + def pipe_converter(val): + for c in converters: + val = c(val) + + return val + + if not converters: + # If the converter list is empty, pipe_converter is the identity. + A = TypeVar("A") + pipe_converter.__annotations__.update({"val": A, "return": A}) + else: + # Get parameter type from first converter. + t = _AnnotationExtractor(converters[0]).get_first_param_type() + if t: + pipe_converter.__annotations__["val"] = t + + last = converters[-1] + if not PY_3_11_PLUS and isinstance(last, Converter): + last = last.__call__ + + # Get return type from last converter. + rt = _AnnotationExtractor(last).get_return_type() + if rt: + pipe_converter.__annotations__["return"] = rt + + if return_instance: + return Converter(pipe_converter, takes_self=True, takes_field=True) + return pipe_converter diff --git a/server/libs/attr/_next_gen.py b/server/libs/attr/_next_gen.py new file mode 100644 index 0000000..9290664 --- /dev/null +++ b/server/libs/attr/_next_gen.py @@ -0,0 +1,623 @@ +# SPDX-License-Identifier: MIT + +""" +These are keyword-only APIs that call `attr.s` and `attr.ib` with different +default values. +""" + +from functools import partial + +from . import setters +from ._funcs import asdict as _asdict +from ._funcs import astuple as _astuple +from ._make import ( + _DEFAULT_ON_SETATTR, + NOTHING, + _frozen_setattrs, + attrib, + attrs, +) +from .exceptions import UnannotatedAttributeError + + +def define( + maybe_cls=None, + *, + these=None, + repr=None, + unsafe_hash=None, + hash=None, + init=None, + slots=True, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=None, + kw_only=False, + cache_hash=False, + auto_exc=True, + eq=None, + order=False, + auto_detect=True, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, +): + r""" + A class decorator that adds :term:`dunder methods` according to + :term:`fields ` specified using :doc:`type annotations `, + `field()` calls, or the *these* argument. + + Since *attrs* patches or replaces an existing class, you cannot use + `object.__init_subclass__` with *attrs* classes, because it runs too early. + As a replacement, you can define ``__attrs_init_subclass__`` on your class. + It will be called by *attrs* classes that subclass it after they're + created. See also :ref:`init-subclass`. + + Args: + slots (bool): + Create a :term:`slotted class ` that's more + memory-efficient. Slotted classes are generally superior to the + default dict classes, but have some gotchas you should know about, + so we encourage you to read the :term:`glossary entry `. + + auto_detect (bool): + Instead of setting the *init*, *repr*, *eq*, and *hash* arguments + explicitly, assume they are set to True **unless any** of the + involved methods for one of the arguments is implemented in the + *current* class (meaning, it is *not* inherited from some base + class). + + So, for example by implementing ``__eq__`` on a class yourself, + *attrs* will deduce ``eq=False`` and will create *neither* + ``__eq__`` *nor* ``__ne__`` (but Python classes come with a + sensible ``__ne__`` by default, so it *should* be enough to only + implement ``__eq__`` in most cases). + + Passing True or False` to *init*, *repr*, *eq*, or *hash* + overrides whatever *auto_detect* would determine. + + auto_exc (bool): + If the class subclasses `BaseException` (which implicitly includes + any subclass of any exception), the following happens to behave + like a well-behaved Python exception class: + + - the values for *eq*, *order*, and *hash* are ignored and the + instances compare and hash by the instance's ids [#]_ , + - all attributes that are either passed into ``__init__`` or have a + default value are additionally available as a tuple in the + ``args`` attribute, + - the value of *str* is ignored leaving ``__str__`` to base + classes. + + .. [#] + Note that *attrs* will *not* remove existing implementations of + ``__hash__`` or the equality methods. It just won't add own + ones. + + on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): + A callable that is run whenever the user attempts to set an + attribute (either by assignment like ``i.x = 42`` or by using + `setattr` like ``setattr(i, "x", 42)``). It receives the same + arguments as validators: the instance, the attribute that is being + modified, and the new value. + + If no exception is raised, the attribute is set to the return value + of the callable. + + If a list of callables is passed, they're automatically wrapped in + an `attrs.setters.pipe`. + + If left None, the default behavior is to run converters and + validators whenever an attribute is set. + + init (bool): + Create a ``__init__`` method that initializes the *attrs* + attributes. Leading underscores are stripped for the argument name, + unless an alias is set on the attribute. + + .. seealso:: + `init` shows advanced ways to customize the generated + ``__init__`` method, including executing code before and after. + + repr(bool): + Create a ``__repr__`` method with a human readable representation + of *attrs* attributes. + + str (bool): + Create a ``__str__`` method that is identical to ``__repr__``. This + is usually not necessary except for `Exception`\ s. + + eq (bool | None): + If True or None (default), add ``__eq__`` and ``__ne__`` methods + that check two instances for equality. + + .. seealso:: + `comparison` describes how to customize the comparison behavior + going as far comparing NumPy arrays. + + order (bool | None): + If True, add ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` + methods that behave like *eq* above and allow instances to be + ordered. + + They compare the instances as if they were tuples of their *attrs* + attributes if and only if the types of both classes are + *identical*. + + If `None` mirror value of *eq*. + + .. seealso:: `comparison` + + unsafe_hash (bool | None): + If None (default), the ``__hash__`` method is generated according + how *eq* and *frozen* are set. + + 1. If *both* are True, *attrs* will generate a ``__hash__`` for + you. + 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set + to None, marking it unhashable (which it is). + 3. If *eq* is False, ``__hash__`` will be left untouched meaning + the ``__hash__`` method of the base class will be used. If the + base class is `object`, this means it will fall back to id-based + hashing. + + Although not recommended, you can decide for yourself and force + *attrs* to create one (for example, if the class is immutable even + though you didn't freeze it programmatically) by passing True or + not. Both of these cases are rather special and should be used + carefully. + + .. seealso:: + + - Our documentation on `hashing`, + - Python's documentation on `object.__hash__`, + - and the `GitHub issue that led to the default \ behavior + `_ for more + details. + + hash (bool | None): + Deprecated alias for *unsafe_hash*. *unsafe_hash* takes precedence. + + cache_hash (bool): + Ensure that the object's hash code is computed only once and stored + on the object. If this is set to True, hashing must be either + explicitly or implicitly enabled for this class. If the hash code + is cached, avoid any reassignments of fields involved in hash code + computation or mutations of the objects those fields point to after + object creation. If such changes occur, the behavior of the + object's hash code is undefined. + + frozen (bool): + Make instances immutable after initialization. If someone attempts + to modify a frozen instance, `attrs.exceptions.FrozenInstanceError` + is raised. + + .. note:: + + 1. This is achieved by installing a custom ``__setattr__`` + method on your class, so you can't implement your own. + + 2. True immutability is impossible in Python. + + 3. This *does* have a minor a runtime performance `impact + ` when initializing new instances. In other + words: ``__init__`` is slightly slower with ``frozen=True``. + + 4. If a class is frozen, you cannot modify ``self`` in + ``__attrs_post_init__`` or a self-written ``__init__``. You + can circumvent that limitation by using + ``object.__setattr__(self, "attribute_name", value)``. + + 5. Subclasses of a frozen class are frozen too. + + kw_only (bool): + Make all attributes keyword-only in the generated ``__init__`` (if + *init* is False, this parameter is ignored). + + weakref_slot (bool): + Make instances weak-referenceable. This has no effect unless + *slots* is True. + + field_transformer (~typing.Callable | None): + A function that is called with the original class object and all + fields right before *attrs* finalizes the class. You can use this, + for example, to automatically add converters or validators to + fields based on their types. + + .. seealso:: `transform-fields` + + match_args (bool): + If True (default), set ``__match_args__`` on the class to support + :pep:`634` (*Structural Pattern Matching*). It is a tuple of all + non-keyword-only ``__init__`` parameter names on Python 3.10 and + later. Ignored on older Python versions. + + collect_by_mro (bool): + If True, *attrs* collects attributes from base classes correctly + according to the `method resolution order + `_. If False, *attrs* + will mimic the (wrong) behavior of `dataclasses` and :pep:`681`. + + See also `issue #428 + `_. + + getstate_setstate (bool | None): + .. note:: + + This is usually only interesting for slotted classes and you + should probably just set *auto_detect* to True. + + If True, ``__getstate__`` and ``__setstate__`` are generated and + attached to the class. This is necessary for slotted classes to be + pickleable. If left None, it's True by default for slotted classes + and False for dict classes. + + If *auto_detect* is True, and *getstate_setstate* is left None, and + **either** ``__getstate__`` or ``__setstate__`` is detected + directly on the class (meaning: not inherited), it is set to False + (this is usually what you want). + + auto_attribs (bool | None): + If True, look at type annotations to determine which attributes to + use, like `dataclasses`. If False, it will only look for explicit + :func:`field` class attributes, like classic *attrs*. + + If left None, it will guess: + + 1. If any attributes are annotated and no unannotated + `attrs.field`\ s are found, it assumes *auto_attribs=True*. + 2. Otherwise it assumes *auto_attribs=False* and tries to collect + `attrs.field`\ s. + + If *attrs* decides to look at type annotations, **all** fields + **must** be annotated. If *attrs* encounters a field that is set to + a :func:`field` / `attr.ib` but lacks a type annotation, an + `attrs.exceptions.UnannotatedAttributeError` is raised. Use + ``field_name: typing.Any = field(...)`` if you don't want to set a + type. + + .. warning:: + + For features that use the attribute name to create decorators + (for example, :ref:`validators `), you still *must* + assign :func:`field` / `attr.ib` to them. Otherwise Python will + either not find the name or try to use the default value to + call, for example, ``validator`` on it. + + Attributes annotated as `typing.ClassVar`, and attributes that are + neither annotated nor set to an `field()` are **ignored**. + + these (dict[str, object]): + A dictionary of name to the (private) return value of `field()` + mappings. This is useful to avoid the definition of your attributes + within the class body because you can't (for example, if you want + to add ``__repr__`` methods to Django models) or don't want to. + + If *these* is not `None`, *attrs* will *not* search the class body + for attributes and will *not* remove any attributes from it. + + The order is deduced from the order of the attributes inside + *these*. + + Arguably, this is a rather obscure feature. + + .. versionadded:: 20.1.0 + .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. + .. versionadded:: 22.2.0 + *unsafe_hash* as an alias for *hash* (for :pep:`681` compliance). + .. versionchanged:: 24.1.0 + Instances are not compared as tuples of attributes anymore, but using a + big ``and`` condition. This is faster and has more correct behavior for + uncomparable values like `math.nan`. + .. versionadded:: 24.1.0 + If a class has an *inherited* classmethod called + ``__attrs_init_subclass__``, it is executed after the class is created. + .. deprecated:: 24.1.0 *hash* is deprecated in favor of *unsafe_hash*. + .. versionadded:: 24.3.0 + Unless already present, a ``__replace__`` method is automatically + created for `copy.replace` (Python 3.13+ only). + + .. note:: + + The main differences to the classic `attr.s` are: + + - Automatically detect whether or not *auto_attribs* should be `True` + (c.f. *auto_attribs* parameter). + - Converters and validators run when attributes are set by default -- + if *frozen* is `False`. + - *slots=True* + + Usually, this has only upsides and few visible effects in everyday + programming. But it *can* lead to some surprising behaviors, so + please make sure to read :term:`slotted classes`. + + - *auto_exc=True* + - *auto_detect=True* + - *order=False* + - Some options that were only relevant on Python 2 or were kept around + for backwards-compatibility have been removed. + + """ + + def do_it(cls, auto_attribs): + return attrs( + maybe_cls=cls, + these=these, + repr=repr, + hash=hash, + unsafe_hash=unsafe_hash, + init=init, + slots=slots, + frozen=frozen, + weakref_slot=weakref_slot, + str=str, + auto_attribs=auto_attribs, + kw_only=kw_only, + cache_hash=cache_hash, + auto_exc=auto_exc, + eq=eq, + order=order, + auto_detect=auto_detect, + collect_by_mro=True, + getstate_setstate=getstate_setstate, + on_setattr=on_setattr, + field_transformer=field_transformer, + match_args=match_args, + ) + + def wrap(cls): + """ + Making this a wrapper ensures this code runs during class creation. + + We also ensure that frozen-ness of classes is inherited. + """ + nonlocal frozen, on_setattr + + had_on_setattr = on_setattr not in (None, setters.NO_OP) + + # By default, mutable classes convert & validate on setattr. + if frozen is False and on_setattr is None: + on_setattr = _DEFAULT_ON_SETATTR + + # However, if we subclass a frozen class, we inherit the immutability + # and disable on_setattr. + for base_cls in cls.__bases__: + if base_cls.__setattr__ is _frozen_setattrs: + if had_on_setattr: + msg = "Frozen classes can't use on_setattr (frozen-ness was inherited)." + raise ValueError(msg) + + on_setattr = setters.NO_OP + break + + if auto_attribs is not None: + return do_it(cls, auto_attribs) + + try: + return do_it(cls, True) + except UnannotatedAttributeError: + return do_it(cls, False) + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but `None` if used as `@attrs()`. + if maybe_cls is None: + return wrap + + return wrap(maybe_cls) + + +mutable = define +frozen = partial(define, frozen=True, on_setattr=None) + + +def field( + *, + default=NOTHING, + validator=None, + repr=True, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, + alias=None, +): + """ + Create a new :term:`field` / :term:`attribute` on a class. + + .. warning:: + + Does **nothing** unless the class is also decorated with + `attrs.define` (or similar)! + + Args: + default: + A value that is used if an *attrs*-generated ``__init__`` is used + and no value is passed while instantiating or the attribute is + excluded using ``init=False``. + + If the value is an instance of `attrs.Factory`, its callable will + be used to construct a new value (useful for mutable data types + like lists or dicts). + + If a default is not set (or set manually to `attrs.NOTHING`), a + value *must* be supplied when instantiating; otherwise a + `TypeError` will be raised. + + .. seealso:: `defaults` + + factory (~typing.Callable): + Syntactic sugar for ``default=attr.Factory(factory)``. + + validator (~typing.Callable | list[~typing.Callable]): + Callable that is called by *attrs*-generated ``__init__`` methods + after the instance has been initialized. They receive the + initialized instance, the :func:`~attrs.Attribute`, and the passed + value. + + The return value is *not* inspected so the validator has to throw + an exception itself. + + If a `list` is passed, its items are treated as validators and must + all pass. + + Validators can be globally disabled and re-enabled using + `attrs.validators.get_disabled` / `attrs.validators.set_disabled`. + + The validator can also be set using decorator notation as shown + below. + + .. seealso:: :ref:`validators` + + repr (bool | ~typing.Callable): + Include this attribute in the generated ``__repr__`` method. If + True, include the attribute; if False, omit it. By default, the + built-in ``repr()`` function is used. To override how the attribute + value is formatted, pass a ``callable`` that takes a single value + and returns a string. Note that the resulting string is used as-is, + which means it will be used directly *instead* of calling + ``repr()`` (the default). + + eq (bool | ~typing.Callable): + If True (default), include this attribute in the generated + ``__eq__`` and ``__ne__`` methods that check two instances for + equality. To override how the attribute value is compared, pass a + callable that takes a single value and returns the value to be + compared. + + .. seealso:: `comparison` + + order (bool | ~typing.Callable): + If True (default), include this attributes in the generated + ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. To + override how the attribute value is ordered, pass a callable that + takes a single value and returns the value to be ordered. + + .. seealso:: `comparison` + + hash (bool | None): + Include this attribute in the generated ``__hash__`` method. If + None (default), mirror *eq*'s value. This is the correct behavior + according the Python spec. Setting this value to anything else + than None is *discouraged*. + + .. seealso:: `hashing` + + init (bool): + Include this attribute in the generated ``__init__`` method. + + It is possible to set this to False and set a default value. In + that case this attributed is unconditionally initialized with the + specified default value or factory. + + .. seealso:: `init` + + converter (typing.Callable | Converter): + A callable that is called by *attrs*-generated ``__init__`` methods + to convert attribute's value to the desired format. + + If a vanilla callable is passed, it is given the passed-in value as + the only positional argument. It is possible to receive additional + arguments by wrapping the callable in a `Converter`. + + Either way, the returned value will be used as the new value of the + attribute. The value is converted before being passed to the + validator, if any. + + .. seealso:: :ref:`converters` + + metadata (dict | None): + An arbitrary mapping, to be used by third-party code. + + .. seealso:: `extending-metadata`. + + type (type): + The type of the attribute. Nowadays, the preferred method to + specify the type is using a variable annotation (see :pep:`526`). + This argument is provided for backwards-compatibility and for usage + with `make_class`. Regardless of the approach used, the type will + be stored on ``Attribute.type``. + + Please note that *attrs* doesn't do anything with this metadata by + itself. You can use it as part of your own code or for `static type + checking `. + + kw_only (bool): + Make this attribute keyword-only in the generated ``__init__`` (if + ``init`` is False, this parameter is ignored). + + on_setattr (~typing.Callable | list[~typing.Callable] | None | ~typing.Literal[attrs.setters.NO_OP]): + Allows to overwrite the *on_setattr* setting from `attr.s`. If left + None, the *on_setattr* value from `attr.s` is used. Set to + `attrs.setters.NO_OP` to run **no** `setattr` hooks for this + attribute -- regardless of the setting in `define()`. + + alias (str | None): + Override this attribute's parameter name in the generated + ``__init__`` method. If left None, default to ``name`` stripped + of leading underscores. See `private-attributes`. + + .. versionadded:: 20.1.0 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionadded:: 22.2.0 *alias* + .. versionadded:: 23.1.0 + The *type* parameter has been re-added; mostly for `attrs.make_class`. + Please note that type checkers ignore this metadata. + + .. seealso:: + + `attr.ib` + """ + return attrib( + default=default, + validator=validator, + repr=repr, + hash=hash, + init=init, + metadata=metadata, + type=type, + converter=converter, + factory=factory, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + alias=alias, + ) + + +def asdict(inst, *, recurse=True, filter=None, value_serializer=None): + """ + Same as `attr.asdict`, except that collections types are always retained + and dict is always used as *dict_factory*. + + .. versionadded:: 21.3.0 + """ + return _asdict( + inst=inst, + recurse=recurse, + filter=filter, + value_serializer=value_serializer, + retain_collection_types=True, + ) + + +def astuple(inst, *, recurse=True, filter=None): + """ + Same as `attr.astuple`, except that collections types are always retained + and `tuple` is always used as the *tuple_factory*. + + .. versionadded:: 21.3.0 + """ + return _astuple( + inst=inst, recurse=recurse, filter=filter, retain_collection_types=True + ) diff --git a/server/libs/attr/_typing_compat.pyi b/server/libs/attr/_typing_compat.pyi new file mode 100644 index 0000000..ca7b71e --- /dev/null +++ b/server/libs/attr/_typing_compat.pyi @@ -0,0 +1,15 @@ +from typing import Any, ClassVar, Protocol + +# MYPY is a special constant in mypy which works the same way as `TYPE_CHECKING`. +MYPY = False + +if MYPY: + # A protocol to be able to statically accept an attrs class. + class AttrsInstance_(Protocol): + __attrs_attrs__: ClassVar[Any] + +else: + # For type checkers without plug-in support use an empty protocol that + # will (hopefully) be combined into a union. + class AttrsInstance_(Protocol): + pass diff --git a/server/libs/attr/_version_info.py b/server/libs/attr/_version_info.py new file mode 100644 index 0000000..51a1312 --- /dev/null +++ b/server/libs/attr/_version_info.py @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: MIT + + +from functools import total_ordering + +from ._funcs import astuple +from ._make import attrib, attrs + + +@total_ordering +@attrs(eq=False, order=False, slots=True, frozen=True) +class VersionInfo: + """ + A version object that can be compared to tuple of length 1--4: + + >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) + True + >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) + True + >>> vi = attr.VersionInfo(19, 2, 0, "final") + >>> vi < (19, 1, 1) + False + >>> vi < (19,) + False + >>> vi == (19, 2,) + True + >>> vi == (19, 2, 1) + False + + .. versionadded:: 19.2 + """ + + year = attrib(type=int) + minor = attrib(type=int) + micro = attrib(type=int) + releaselevel = attrib(type=str) + + @classmethod + def _from_version_string(cls, s): + """ + Parse *s* and return a _VersionInfo. + """ + v = s.split(".") + if len(v) == 3: + v.append("final") + + return cls( + year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] + ) + + def _ensure_tuple(self, other): + """ + Ensure *other* is a tuple of a valid length. + + Returns a possibly transformed *other* and ourselves as a tuple of + the same length as *other*. + """ + + if self.__class__ is other.__class__: + other = astuple(other) + + if not isinstance(other, tuple): + raise NotImplementedError + + if not (1 <= len(other) <= 4): + raise NotImplementedError + + return astuple(self)[: len(other)], other + + def __eq__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + return us == them + + def __lt__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't + # have to do anything special with releaselevel for now. + return us < them diff --git a/server/libs/attr/_version_info.pyi b/server/libs/attr/_version_info.pyi new file mode 100644 index 0000000..45ced08 --- /dev/null +++ b/server/libs/attr/_version_info.pyi @@ -0,0 +1,9 @@ +class VersionInfo: + @property + def year(self) -> int: ... + @property + def minor(self) -> int: ... + @property + def micro(self) -> int: ... + @property + def releaselevel(self) -> str: ... diff --git a/server/libs/attr/converters.py b/server/libs/attr/converters.py new file mode 100644 index 0000000..0a79dee --- /dev/null +++ b/server/libs/attr/converters.py @@ -0,0 +1,162 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful converters. +""" + +import typing + +from ._compat import _AnnotationExtractor +from ._make import NOTHING, Converter, Factory, pipe + + +__all__ = [ + "default_if_none", + "optional", + "pipe", + "to_bool", +] + + +def optional(converter): + """ + A converter that allows an attribute to be optional. An optional attribute + is one which can be set to `None`. + + Type annotations will be inferred from the wrapped converter's, if it has + any. + + Args: + converter (typing.Callable): + the converter that is used for non-`None` values. + + .. versionadded:: 17.1.0 + """ + + if isinstance(converter, Converter): + + def optional_converter(val, inst, field): + if val is None: + return None + return converter(val, inst, field) + + else: + + def optional_converter(val): + if val is None: + return None + return converter(val) + + xtr = _AnnotationExtractor(converter) + + t = xtr.get_first_param_type() + if t: + optional_converter.__annotations__["val"] = typing.Optional[t] + + rt = xtr.get_return_type() + if rt: + optional_converter.__annotations__["return"] = typing.Optional[rt] + + if isinstance(converter, Converter): + return Converter(optional_converter, takes_self=True, takes_field=True) + + return optional_converter + + +def default_if_none(default=NOTHING, factory=None): + """ + A converter that allows to replace `None` values by *default* or the result + of *factory*. + + Args: + default: + Value to be used if `None` is passed. Passing an instance of + `attrs.Factory` is supported, however the ``takes_self`` option is + *not*. + + factory (typing.Callable): + A callable that takes no parameters whose result is used if `None` + is passed. + + Raises: + TypeError: If **neither** *default* or *factory* is passed. + + TypeError: If **both** *default* and *factory* are passed. + + ValueError: + If an instance of `attrs.Factory` is passed with + ``takes_self=True``. + + .. versionadded:: 18.2.0 + """ + if default is NOTHING and factory is None: + msg = "Must pass either `default` or `factory`." + raise TypeError(msg) + + if default is not NOTHING and factory is not None: + msg = "Must pass either `default` or `factory` but not both." + raise TypeError(msg) + + if factory is not None: + default = Factory(factory) + + if isinstance(default, Factory): + if default.takes_self: + msg = "`takes_self` is not supported by default_if_none." + raise ValueError(msg) + + def default_if_none_converter(val): + if val is not None: + return val + + return default.factory() + + else: + + def default_if_none_converter(val): + if val is not None: + return val + + return default + + return default_if_none_converter + + +def to_bool(val): + """ + Convert "boolean" strings (for example, from environment variables) to real + booleans. + + Values mapping to `True`: + + - ``True`` + - ``"true"`` / ``"t"`` + - ``"yes"`` / ``"y"`` + - ``"on"`` + - ``"1"`` + - ``1`` + + Values mapping to `False`: + + - ``False`` + - ``"false"`` / ``"f"`` + - ``"no"`` / ``"n"`` + - ``"off"`` + - ``"0"`` + - ``0`` + + Raises: + ValueError: For any other value. + + .. versionadded:: 21.3.0 + """ + if isinstance(val, str): + val = val.lower() + + if val in (True, "true", "t", "yes", "y", "on", "1", 1): + return True + if val in (False, "false", "f", "no", "n", "off", "0", 0): + return False + + msg = f"Cannot convert value to bool: {val!r}" + raise ValueError(msg) diff --git a/server/libs/attr/converters.pyi b/server/libs/attr/converters.pyi new file mode 100644 index 0000000..12bd0c4 --- /dev/null +++ b/server/libs/attr/converters.pyi @@ -0,0 +1,19 @@ +from typing import Callable, Any, overload + +from attrs import _ConverterType, _CallableConverterType + +@overload +def pipe(*validators: _CallableConverterType) -> _CallableConverterType: ... +@overload +def pipe(*validators: _ConverterType) -> _ConverterType: ... +@overload +def optional(converter: _CallableConverterType) -> _CallableConverterType: ... +@overload +def optional(converter: _ConverterType) -> _ConverterType: ... +@overload +def default_if_none(default: Any) -> _CallableConverterType: ... +@overload +def default_if_none( + *, factory: Callable[[], Any] +) -> _CallableConverterType: ... +def to_bool(val: str | int | bool) -> bool: ... diff --git a/server/libs/attr/exceptions.py b/server/libs/attr/exceptions.py new file mode 100644 index 0000000..3b7abb8 --- /dev/null +++ b/server/libs/attr/exceptions.py @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: MIT + +from __future__ import annotations + +from typing import ClassVar + + +class FrozenError(AttributeError): + """ + A frozen/immutable instance or attribute have been attempted to be + modified. + + It mirrors the behavior of ``namedtuples`` by using the same error message + and subclassing `AttributeError`. + + .. versionadded:: 20.1.0 + """ + + msg = "can't set attribute" + args: ClassVar[tuple[str]] = [msg] + + +class FrozenInstanceError(FrozenError): + """ + A frozen instance has been attempted to be modified. + + .. versionadded:: 16.1.0 + """ + + +class FrozenAttributeError(FrozenError): + """ + A frozen attribute has been attempted to be modified. + + .. versionadded:: 20.1.0 + """ + + +class AttrsAttributeNotFoundError(ValueError): + """ + An *attrs* function couldn't find an attribute that the user asked for. + + .. versionadded:: 16.2.0 + """ + + +class NotAnAttrsClassError(ValueError): + """ + A non-*attrs* class has been passed into an *attrs* function. + + .. versionadded:: 16.2.0 + """ + + +class DefaultAlreadySetError(RuntimeError): + """ + A default has been set when defining the field and is attempted to be reset + using the decorator. + + .. versionadded:: 17.1.0 + """ + + +class UnannotatedAttributeError(RuntimeError): + """ + A class with ``auto_attribs=True`` has a field without a type annotation. + + .. versionadded:: 17.3.0 + """ + + +class PythonTooOldError(RuntimeError): + """ + It was attempted to use an *attrs* feature that requires a newer Python + version. + + .. versionadded:: 18.2.0 + """ + + +class NotCallableError(TypeError): + """ + A field requiring a callable has been set with a value that is not + callable. + + .. versionadded:: 19.2.0 + """ + + def __init__(self, msg, value): + super(TypeError, self).__init__(msg, value) + self.msg = msg + self.value = value + + def __str__(self): + return str(self.msg) diff --git a/server/libs/attr/exceptions.pyi b/server/libs/attr/exceptions.pyi new file mode 100644 index 0000000..f268011 --- /dev/null +++ b/server/libs/attr/exceptions.pyi @@ -0,0 +1,17 @@ +from typing import Any + +class FrozenError(AttributeError): + msg: str = ... + +class FrozenInstanceError(FrozenError): ... +class FrozenAttributeError(FrozenError): ... +class AttrsAttributeNotFoundError(ValueError): ... +class NotAnAttrsClassError(ValueError): ... +class DefaultAlreadySetError(RuntimeError): ... +class UnannotatedAttributeError(RuntimeError): ... +class PythonTooOldError(RuntimeError): ... + +class NotCallableError(TypeError): + msg: str = ... + value: Any = ... + def __init__(self, msg: str, value: Any) -> None: ... diff --git a/server/libs/attr/filters.py b/server/libs/attr/filters.py new file mode 100644 index 0000000..689b170 --- /dev/null +++ b/server/libs/attr/filters.py @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful filters for `attrs.asdict` and `attrs.astuple`. +""" + +from ._make import Attribute + + +def _split_what(what): + """ + Returns a tuple of `frozenset`s of classes and attributes. + """ + return ( + frozenset(cls for cls in what if isinstance(cls, type)), + frozenset(cls for cls in what if isinstance(cls, str)), + frozenset(cls for cls in what if isinstance(cls, Attribute)), + ) + + +def include(*what): + """ + Create a filter that only allows *what*. + + Args: + what (list[type, str, attrs.Attribute]): + What to include. Can be a type, a name, or an attribute. + + Returns: + Callable: + A callable that can be passed to `attrs.asdict`'s and + `attrs.astuple`'s *filter* argument. + + .. versionchanged:: 23.1.0 Accept strings with field names. + """ + cls, names, attrs = _split_what(what) + + def include_(attribute, value): + return ( + value.__class__ in cls + or attribute.name in names + or attribute in attrs + ) + + return include_ + + +def exclude(*what): + """ + Create a filter that does **not** allow *what*. + + Args: + what (list[type, str, attrs.Attribute]): + What to exclude. Can be a type, a name, or an attribute. + + Returns: + Callable: + A callable that can be passed to `attrs.asdict`'s and + `attrs.astuple`'s *filter* argument. + + .. versionchanged:: 23.3.0 Accept field name string as input argument + """ + cls, names, attrs = _split_what(what) + + def exclude_(attribute, value): + return not ( + value.__class__ in cls + or attribute.name in names + or attribute in attrs + ) + + return exclude_ diff --git a/server/libs/attr/filters.pyi b/server/libs/attr/filters.pyi new file mode 100644 index 0000000..974abdc --- /dev/null +++ b/server/libs/attr/filters.pyi @@ -0,0 +1,6 @@ +from typing import Any + +from . import Attribute, _FilterType + +def include(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... +def exclude(*what: type | str | Attribute[Any]) -> _FilterType[Any]: ... diff --git a/server/README.md b/server/libs/attr/py.typed similarity index 100% rename from server/README.md rename to server/libs/attr/py.typed diff --git a/server/libs/attr/setters.py b/server/libs/attr/setters.py new file mode 100644 index 0000000..78b0839 --- /dev/null +++ b/server/libs/attr/setters.py @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly used hooks for on_setattr. +""" + +from . import _config +from .exceptions import FrozenAttributeError + + +def pipe(*setters): + """ + Run all *setters* and return the return value of the last one. + + .. versionadded:: 20.1.0 + """ + + def wrapped_pipe(instance, attrib, new_value): + rv = new_value + + for setter in setters: + rv = setter(instance, attrib, rv) + + return rv + + return wrapped_pipe + + +def frozen(_, __, ___): + """ + Prevent an attribute to be modified. + + .. versionadded:: 20.1.0 + """ + raise FrozenAttributeError + + +def validate(instance, attrib, new_value): + """ + Run *attrib*'s validator on *new_value* if it has one. + + .. versionadded:: 20.1.0 + """ + if _config._run_validators is False: + return new_value + + v = attrib.validator + if not v: + return new_value + + v(instance, attrib, new_value) + + return new_value + + +def convert(instance, attrib, new_value): + """ + Run *attrib*'s converter -- if it has one -- on *new_value* and return the + result. + + .. versionadded:: 20.1.0 + """ + c = attrib.converter + if c: + # This can be removed once we drop 3.8 and use attrs.Converter instead. + from ._make import Converter + + if not isinstance(c, Converter): + return c(new_value) + + return c(new_value, instance, attrib) + + return new_value + + +# Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. +# Sphinx's autodata stopped working, so the docstring is inlined in the API +# docs. +NO_OP = object() diff --git a/server/libs/attr/setters.pyi b/server/libs/attr/setters.pyi new file mode 100644 index 0000000..73abf36 --- /dev/null +++ b/server/libs/attr/setters.pyi @@ -0,0 +1,20 @@ +from typing import Any, NewType, NoReturn, TypeVar + +from . import Attribute +from attrs import _OnSetAttrType + +_T = TypeVar("_T") + +def frozen( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> NoReturn: ... +def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... +def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... + +# convert is allowed to return Any, because they can be chained using pipe. +def convert( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> Any: ... + +_NoOpType = NewType("_NoOpType", object) +NO_OP: _NoOpType diff --git a/server/libs/attr/validators.py b/server/libs/attr/validators.py new file mode 100644 index 0000000..e7b7552 --- /dev/null +++ b/server/libs/attr/validators.py @@ -0,0 +1,710 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful validators. +""" + +import operator +import re + +from contextlib import contextmanager +from re import Pattern + +from ._config import get_run_validators, set_run_validators +from ._make import _AndValidator, and_, attrib, attrs +from .converters import default_if_none +from .exceptions import NotCallableError + + +__all__ = [ + "and_", + "deep_iterable", + "deep_mapping", + "disabled", + "ge", + "get_disabled", + "gt", + "in_", + "instance_of", + "is_callable", + "le", + "lt", + "matches_re", + "max_len", + "min_len", + "not_", + "optional", + "or_", + "set_disabled", +] + + +def set_disabled(disabled): + """ + Globally disable or enable running validators. + + By default, they are run. + + Args: + disabled (bool): If `True`, disable running all validators. + + .. warning:: + + This function is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(not disabled) + + +def get_disabled(): + """ + Return a bool indicating whether validators are currently disabled or not. + + Returns: + bool:`True` if validators are currently disabled. + + .. versionadded:: 21.3.0 + """ + return not get_run_validators() + + +@contextmanager +def disabled(): + """ + Context manager that disables running validators within its context. + + .. warning:: + + This context manager is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(False) + try: + yield + finally: + set_run_validators(True) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _InstanceOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not isinstance(value, self.type): + msg = f"'{attr.name}' must be {self.type!r} (got {value!r} that is a {value.__class__!r})." + raise TypeError( + msg, + attr, + self.type, + value, + ) + + def __repr__(self): + return f"" + + +def instance_of(type): + """ + A validator that raises a `TypeError` if the initializer is called with a + wrong type for this particular attribute (checks are performed using + `isinstance` therefore it's also valid to pass a tuple of types). + + Args: + type (type | tuple[type]): The type to check for. + + Raises: + TypeError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected type, and the value it got. + """ + return _InstanceOfValidator(type) + + +@attrs(repr=False, frozen=True, slots=True) +class _MatchesReValidator: + pattern = attrib() + match_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.match_func(value): + msg = f"'{attr.name}' must match regex {self.pattern.pattern!r} ({value!r} doesn't)" + raise ValueError( + msg, + attr, + self.pattern, + value, + ) + + def __repr__(self): + return f"" + + +def matches_re(regex, flags=0, func=None): + r""" + A validator that raises `ValueError` if the initializer is called with a + string that doesn't match *regex*. + + Args: + regex (str, re.Pattern): + A regex string or precompiled pattern to match against + + flags (int): + Flags that will be passed to the underlying re function (default 0) + + func (typing.Callable): + Which underlying `re` function to call. Valid options are + `re.fullmatch`, `re.search`, and `re.match`; the default `None` + means `re.fullmatch`. For performance reasons, the pattern is + always precompiled using `re.compile`. + + .. versionadded:: 19.2.0 + .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. + """ + valid_funcs = (re.fullmatch, None, re.search, re.match) + if func not in valid_funcs: + msg = "'func' must be one of {}.".format( + ", ".join( + sorted((e and e.__name__) or "None" for e in set(valid_funcs)) + ) + ) + raise ValueError(msg) + + if isinstance(regex, Pattern): + if flags: + msg = "'flags' can only be used with a string pattern; pass flags to re.compile() instead" + raise TypeError(msg) + pattern = regex + else: + pattern = re.compile(regex, flags) + + if func is re.match: + match_func = pattern.match + elif func is re.search: + match_func = pattern.search + else: + match_func = pattern.fullmatch + + return _MatchesReValidator(pattern, match_func) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _OptionalValidator: + validator = attrib() + + def __call__(self, inst, attr, value): + if value is None: + return + + self.validator(inst, attr, value) + + def __repr__(self): + return f"" + + +def optional(validator): + """ + A validator that makes an attribute optional. An optional attribute is one + which can be set to `None` in addition to satisfying the requirements of + the sub-validator. + + Args: + validator + (typing.Callable | tuple[typing.Callable] | list[typing.Callable]): + A validator (or validators) that is used for non-`None` values. + + .. versionadded:: 15.1.0 + .. versionchanged:: 17.1.0 *validator* can be a list of validators. + .. versionchanged:: 23.1.0 *validator* can also be a tuple of validators. + """ + if isinstance(validator, (list, tuple)): + return _OptionalValidator(_AndValidator(validator)) + + return _OptionalValidator(validator) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _InValidator: + options = attrib() + _original_options = attrib(hash=False) + + def __call__(self, inst, attr, value): + try: + in_options = value in self.options + except TypeError: # e.g. `1 in "abc"` + in_options = False + + if not in_options: + msg = f"'{attr.name}' must be in {self._original_options!r} (got {value!r})" + raise ValueError( + msg, + attr, + self._original_options, + value, + ) + + def __repr__(self): + return f"" + + +def in_(options): + """ + A validator that raises a `ValueError` if the initializer is called with a + value that does not belong in the *options* provided. + + The check is performed using ``value in options``, so *options* has to + support that operation. + + To keep the validator hashable, dicts, lists, and sets are transparently + transformed into a `tuple`. + + Args: + options: Allowed options. + + Raises: + ValueError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected options, and the value it got. + + .. versionadded:: 17.1.0 + .. versionchanged:: 22.1.0 + The ValueError was incomplete until now and only contained the human + readable error message. Now it contains all the information that has + been promised since 17.1.0. + .. versionchanged:: 24.1.0 + *options* that are a list, dict, or a set are now transformed into a + tuple to keep the validator hashable. + """ + repr_options = options + if isinstance(options, (list, dict, set)): + options = tuple(options) + + return _InValidator(options, repr_options) + + +@attrs(repr=False, slots=False, unsafe_hash=True) +class _IsCallableValidator: + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not callable(value): + message = ( + "'{name}' must be callable " + "(got {value!r} that is a {actual!r})." + ) + raise NotCallableError( + msg=message.format( + name=attr.name, value=value, actual=value.__class__ + ), + value=value, + ) + + def __repr__(self): + return "" + + +def is_callable(): + """ + A validator that raises a `attrs.exceptions.NotCallableError` if the + initializer is called with a value for this particular attribute that is + not callable. + + .. versionadded:: 19.1.0 + + Raises: + attrs.exceptions.NotCallableError: + With a human readable error message containing the attribute + (`attrs.Attribute`) name, and the value it got. + """ + return _IsCallableValidator() + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _DeepIterable: + member_validator = attrib(validator=is_callable()) + iterable_validator = attrib( + default=None, validator=optional(is_callable()) + ) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.iterable_validator is not None: + self.iterable_validator(inst, attr, value) + + for member in value: + self.member_validator(inst, attr, member) + + def __repr__(self): + iterable_identifier = ( + "" + if self.iterable_validator is None + else f" {self.iterable_validator!r}" + ) + return ( + f"" + ) + + +def deep_iterable(member_validator, iterable_validator=None): + """ + A validator that performs deep validation of an iterable. + + Args: + member_validator: Validator to apply to iterable members. + + iterable_validator: + Validator to apply to iterable itself (optional). + + Raises + TypeError: if any sub-validators fail + + .. versionadded:: 19.1.0 + """ + if isinstance(member_validator, (list, tuple)): + member_validator = and_(*member_validator) + return _DeepIterable(member_validator, iterable_validator) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _DeepMapping: + key_validator = attrib(validator=is_callable()) + value_validator = attrib(validator=is_callable()) + mapping_validator = attrib(default=None, validator=optional(is_callable())) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.mapping_validator is not None: + self.mapping_validator(inst, attr, value) + + for key in value: + self.key_validator(inst, attr, key) + self.value_validator(inst, attr, value[key]) + + def __repr__(self): + return f"" + + +def deep_mapping(key_validator, value_validator, mapping_validator=None): + """ + A validator that performs deep validation of a dictionary. + + Args: + key_validator: Validator to apply to dictionary keys. + + value_validator: Validator to apply to dictionary values. + + mapping_validator: + Validator to apply to top-level mapping attribute (optional). + + .. versionadded:: 19.1.0 + + Raises: + TypeError: if any sub-validators fail + """ + return _DeepMapping(key_validator, value_validator, mapping_validator) + + +@attrs(repr=False, frozen=True, slots=True) +class _NumberValidator: + bound = attrib() + compare_op = attrib() + compare_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.compare_func(value, self.bound): + msg = f"'{attr.name}' must be {self.compare_op} {self.bound}: {value}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def lt(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number larger or equal to *val*. + + The validator uses `operator.lt` to compare the values. + + Args: + val: Exclusive upper bound for values. + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<", operator.lt) + + +def le(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number greater than *val*. + + The validator uses `operator.le` to compare the values. + + Args: + val: Inclusive upper bound for values. + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<=", operator.le) + + +def ge(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number smaller than *val*. + + The validator uses `operator.ge` to compare the values. + + Args: + val: Inclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">=", operator.ge) + + +def gt(val): + """ + A validator that raises `ValueError` if the initializer is called with a + number smaller or equal to *val*. + + The validator uses `operator.ge` to compare the values. + + Args: + val: Exclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">", operator.gt) + + +@attrs(repr=False, frozen=True, slots=True) +class _MaxLengthValidator: + max_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) > self.max_length: + msg = f"Length of '{attr.name}' must be <= {self.max_length}: {len(value)}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def max_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is longer than *length*. + + Args: + length (int): Maximum length of the string or iterable + + .. versionadded:: 21.3.0 + """ + return _MaxLengthValidator(length) + + +@attrs(repr=False, frozen=True, slots=True) +class _MinLengthValidator: + min_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) < self.min_length: + msg = f"Length of '{attr.name}' must be >= {self.min_length}: {len(value)}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def min_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is shorter than *length*. + + Args: + length (int): Minimum length of the string or iterable + + .. versionadded:: 22.1.0 + """ + return _MinLengthValidator(length) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _SubclassOfValidator: + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not issubclass(value, self.type): + msg = f"'{attr.name}' must be a subclass of {self.type!r} (got {value!r})." + raise TypeError( + msg, + attr, + self.type, + value, + ) + + def __repr__(self): + return f"" + + +def _subclass_of(type): + """ + A validator that raises a `TypeError` if the initializer is called with a + wrong type for this particular attribute (checks are performed using + `issubclass` therefore it's also valid to pass a tuple of types). + + Args: + type (type | tuple[type, ...]): The type(s) to check for. + + Raises: + TypeError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the expected type, and the value it got. + """ + return _SubclassOfValidator(type) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _NotValidator: + validator = attrib() + msg = attrib( + converter=default_if_none( + "not_ validator child '{validator!r}' " + "did not raise a captured error" + ) + ) + exc_types = attrib( + validator=deep_iterable( + member_validator=_subclass_of(Exception), + iterable_validator=instance_of(tuple), + ), + ) + + def __call__(self, inst, attr, value): + try: + self.validator(inst, attr, value) + except self.exc_types: + pass # suppress error to invert validity + else: + raise ValueError( + self.msg.format( + validator=self.validator, + exc_types=self.exc_types, + ), + attr, + self.validator, + value, + self.exc_types, + ) + + def __repr__(self): + return f"" + + +def not_(validator, *, msg=None, exc_types=(ValueError, TypeError)): + """ + A validator that wraps and logically 'inverts' the validator passed to it. + It will raise a `ValueError` if the provided validator *doesn't* raise a + `ValueError` or `TypeError` (by default), and will suppress the exception + if the provided validator *does*. + + Intended to be used with existing validators to compose logic without + needing to create inverted variants, for example, ``not_(in_(...))``. + + Args: + validator: A validator to be logically inverted. + + msg (str): + Message to raise if validator fails. Formatted with keys + ``exc_types`` and ``validator``. + + exc_types (tuple[type, ...]): + Exception type(s) to capture. Other types raised by child + validators will not be intercepted and pass through. + + Raises: + ValueError: + With a human readable error message, the attribute (of type + `attrs.Attribute`), the validator that failed to raise an + exception, the value it got, and the expected exception types. + + .. versionadded:: 22.2.0 + """ + try: + exc_types = tuple(exc_types) + except TypeError: + exc_types = (exc_types,) + return _NotValidator(validator, msg, exc_types) + + +@attrs(repr=False, slots=True, unsafe_hash=True) +class _OrValidator: + validators = attrib() + + def __call__(self, inst, attr, value): + for v in self.validators: + try: + v(inst, attr, value) + except Exception: # noqa: BLE001, PERF203, S112 + continue + else: + return + + msg = f"None of {self.validators!r} satisfied for value {value!r}" + raise ValueError(msg) + + def __repr__(self): + return f"" + + +def or_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators until one of them is + satisfied. + + Args: + validators (~collections.abc.Iterable[typing.Callable]): + Arbitrary number of validators. + + Raises: + ValueError: + If no validator is satisfied. Raised with a human-readable error + message listing all the wrapped validators and the value that + failed all of them. + + .. versionadded:: 24.1.0 + """ + vals = [] + for v in validators: + vals.extend(v.validators if isinstance(v, _OrValidator) else [v]) + + return _OrValidator(tuple(vals)) diff --git a/server/libs/attr/validators.pyi b/server/libs/attr/validators.pyi new file mode 100644 index 0000000..a0fdda7 --- /dev/null +++ b/server/libs/attr/validators.pyi @@ -0,0 +1,86 @@ +from types import UnionType +from typing import ( + Any, + AnyStr, + Callable, + Container, + ContextManager, + Iterable, + Mapping, + Match, + Pattern, + TypeVar, + overload, +) + +from attrs import _ValidatorType +from attrs import _ValidatorArgType + +_T = TypeVar("_T") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_I = TypeVar("_I", bound=Iterable) +_K = TypeVar("_K") +_V = TypeVar("_V") +_M = TypeVar("_M", bound=Mapping) + +def set_disabled(run: bool) -> None: ... +def get_disabled() -> bool: ... +def disabled() -> ContextManager[None]: ... + +# To be more precise on instance_of use some overloads. +# If there are more than 3 items in the tuple then we fall back to Any +@overload +def instance_of(type: type[_T]) -> _ValidatorType[_T]: ... +@overload +def instance_of(type: tuple[type[_T]]) -> _ValidatorType[_T]: ... +@overload +def instance_of( + type: tuple[type[_T1], type[_T2]], +) -> _ValidatorType[_T1 | _T2]: ... +@overload +def instance_of( + type: tuple[type[_T1], type[_T2], type[_T3]], +) -> _ValidatorType[_T1 | _T2 | _T3]: ... +@overload +def instance_of(type: tuple[type, ...]) -> _ValidatorType[Any]: ... +@overload +def instance_of(type: UnionType) -> _ValidatorType[Any]: ... +def optional( + validator: ( + _ValidatorType[_T] + | list[_ValidatorType[_T]] + | tuple[_ValidatorType[_T]] + ), +) -> _ValidatorType[_T | None]: ... +def in_(options: Container[_T]) -> _ValidatorType[_T]: ... +def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... +def matches_re( + regex: Pattern[AnyStr] | AnyStr, + flags: int = ..., + func: Callable[[AnyStr, AnyStr, int], Match[AnyStr] | None] | None = ..., +) -> _ValidatorType[AnyStr]: ... +def deep_iterable( + member_validator: _ValidatorArgType[_T], + iterable_validator: _ValidatorType[_I] | None = ..., +) -> _ValidatorType[_I]: ... +def deep_mapping( + key_validator: _ValidatorType[_K], + value_validator: _ValidatorType[_V], + mapping_validator: _ValidatorType[_M] | None = ..., +) -> _ValidatorType[_M]: ... +def is_callable() -> _ValidatorType[_T]: ... +def lt(val: _T) -> _ValidatorType[_T]: ... +def le(val: _T) -> _ValidatorType[_T]: ... +def ge(val: _T) -> _ValidatorType[_T]: ... +def gt(val: _T) -> _ValidatorType[_T]: ... +def max_len(length: int) -> _ValidatorType[_T]: ... +def min_len(length: int) -> _ValidatorType[_T]: ... +def not_( + validator: _ValidatorType[_T], + *, + msg: str | None = None, + exc_types: type[Exception] | Iterable[type[Exception]] = ..., +) -> _ValidatorType[_T]: ... +def or_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... diff --git a/server/libs/attrs-25.3.0.dist-info/INSTALLER b/server/libs/attrs-25.3.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/server/libs/attrs-25.3.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/server/libs/attrs-25.3.0.dist-info/METADATA b/server/libs/attrs-25.3.0.dist-info/METADATA new file mode 100644 index 0000000..029afee --- /dev/null +++ b/server/libs/attrs-25.3.0.dist-info/METADATA @@ -0,0 +1,232 @@ +Metadata-Version: 2.4 +Name: attrs +Version: 25.3.0 +Summary: Classes Without Boilerplate +Project-URL: Documentation, https://www.attrs.org/ +Project-URL: Changelog, https://www.attrs.org/en/stable/changelog.html +Project-URL: GitHub, https://github.com/python-attrs/attrs +Project-URL: Funding, https://github.com/sponsors/hynek +Project-URL: Tidelift, https://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypi +Author-email: Hynek Schlawack +License-Expression: MIT +License-File: LICENSE +Keywords: attribute,boilerplate,class +Classifier: Development Status :: 5 - Production/Stable +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Typing :: Typed +Requires-Python: >=3.8 +Provides-Extra: benchmark +Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'benchmark' +Requires-Dist: hypothesis; extra == 'benchmark' +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'benchmark' +Requires-Dist: pympler; extra == 'benchmark' +Requires-Dist: pytest-codspeed; extra == 'benchmark' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'benchmark' +Requires-Dist: pytest-xdist[psutil]; extra == 'benchmark' +Requires-Dist: pytest>=4.3.0; extra == 'benchmark' +Provides-Extra: cov +Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'cov' +Requires-Dist: coverage[toml]>=5.3; extra == 'cov' +Requires-Dist: hypothesis; extra == 'cov' +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'cov' +Requires-Dist: pympler; extra == 'cov' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'cov' +Requires-Dist: pytest-xdist[psutil]; extra == 'cov' +Requires-Dist: pytest>=4.3.0; extra == 'cov' +Provides-Extra: dev +Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'dev' +Requires-Dist: hypothesis; extra == 'dev' +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'dev' +Requires-Dist: pre-commit-uv; extra == 'dev' +Requires-Dist: pympler; extra == 'dev' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'dev' +Requires-Dist: pytest-xdist[psutil]; extra == 'dev' +Requires-Dist: pytest>=4.3.0; extra == 'dev' +Provides-Extra: docs +Requires-Dist: cogapp; extra == 'docs' +Requires-Dist: furo; extra == 'docs' +Requires-Dist: myst-parser; extra == 'docs' +Requires-Dist: sphinx; extra == 'docs' +Requires-Dist: sphinx-notfound-page; extra == 'docs' +Requires-Dist: sphinxcontrib-towncrier; extra == 'docs' +Requires-Dist: towncrier; extra == 'docs' +Provides-Extra: tests +Requires-Dist: cloudpickle; (platform_python_implementation == 'CPython') and extra == 'tests' +Requires-Dist: hypothesis; extra == 'tests' +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests' +Requires-Dist: pympler; extra == 'tests' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests' +Requires-Dist: pytest-xdist[psutil]; extra == 'tests' +Requires-Dist: pytest>=4.3.0; extra == 'tests' +Provides-Extra: tests-mypy +Requires-Dist: mypy>=1.11.1; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests-mypy' +Requires-Dist: pytest-mypy-plugins; (platform_python_implementation == 'CPython' and python_version >= '3.10') and extra == 'tests-mypy' +Description-Content-Type: text/markdown + +

+ + attrs + +

+ + +*attrs* is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka [dunder methods](https://www.attrs.org/en/latest/glossary.html#term-dunder-methods)). +[Trusted by NASA](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/personalizing-your-profile#list-of-qualifying-repositories-for-mars-2020-helicopter-contributor-achievement) for Mars missions since 2020! + +Its main goal is to help you to write **concise** and **correct** software without slowing down your code. + + +## Sponsors + +*attrs* would not be possible without our [amazing sponsors](https://github.com/sponsors/hynek). +Especially those generously supporting us at the *The Organization* tier and higher: + + + +

+ + + + + + + + + + + +

+ + + +

+ Please consider joining them to help make attrs’s maintenance more sustainable! +

+ + + +## Example + +*attrs* gives you a class decorator and a way to declaratively define the attributes on that class: + + + +```pycon +>>> from attrs import asdict, define, make_class, Factory + +>>> @define +... class SomeClass: +... a_number: int = 42 +... list_of_numbers: list[int] = Factory(list) +... +... def hard_math(self, another_number): +... return self.a_number + sum(self.list_of_numbers) * another_number + + +>>> sc = SomeClass(1, [1, 2, 3]) +>>> sc +SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) + +>>> sc.hard_math(3) +19 +>>> sc == SomeClass(1, [1, 2, 3]) +True +>>> sc != SomeClass(2, [3, 2, 1]) +True + +>>> asdict(sc) +{'a_number': 1, 'list_of_numbers': [1, 2, 3]} + +>>> SomeClass() +SomeClass(a_number=42, list_of_numbers=[]) + +>>> C = make_class("C", ["a", "b"]) +>>> C("foo", "bar") +C(a='foo', b='bar') +``` + +After *declaring* your attributes, *attrs* gives you: + +- a concise and explicit overview of the class's attributes, +- a nice human-readable `__repr__`, +- equality-checking methods, +- an initializer, +- and much more, + +*without* writing dull boilerplate code again and again and *without* runtime performance penalties. + +--- + +This example uses *attrs*'s modern APIs that have been introduced in version 20.1.0, and the *attrs* package import name that has been added in version 21.3.0. +The classic APIs (`@attr.s`, `attr.ib`, plus their serious-business aliases) and the `attr` package import name will remain **indefinitely**. + +Check out [*On The Core API Names*](https://www.attrs.org/en/latest/names.html) for an in-depth explanation! + + +### Hate Type Annotations!? + +No problem! +Types are entirely **optional** with *attrs*. +Simply assign `attrs.field()` to the attributes instead of annotating them with types: + +```python +from attrs import define, field + +@define +class SomeClass: + a_number = field(default=42) + list_of_numbers = field(factory=list) +``` + + +## Data Classes + +On the tin, *attrs* might remind you of `dataclasses` (and indeed, `dataclasses` [are a descendant](https://hynek.me/articles/import-attrs/) of *attrs*). +In practice it does a lot more and is more flexible. +For instance, it allows you to define [special handling of NumPy arrays for equality checks](https://www.attrs.org/en/stable/comparison.html#customization), allows more ways to [plug into the initialization process](https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization), has a replacement for `__init_subclass__`, and allows for stepping through the generated methods using a debugger. + +For more details, please refer to our [comparison page](https://www.attrs.org/en/stable/why.html#data-classes), but generally speaking, we are more likely to commit crimes against nature to make things work that one would expect to work, but that are quite complicated in practice. + + +## Project Information + +- [**Changelog**](https://www.attrs.org/en/stable/changelog.html) +- [**Documentation**](https://www.attrs.org/) +- [**PyPI**](https://pypi.org/project/attrs/) +- [**Source Code**](https://github.com/python-attrs/attrs) +- [**Contributing**](https://github.com/python-attrs/attrs/blob/main/.github/CONTRIBUTING.md) +- [**Third-party Extensions**](https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs) +- **Get Help**: use the `python-attrs` tag on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs) + + +### *attrs* for Enterprise + +Available as part of the [Tidelift Subscription](https://tidelift.com/?utm_source=lifter&utm_medium=referral&utm_campaign=hynek). + +The maintainers of *attrs* and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. + +## Release Information + +### Changes + +- Restore support for generator-based `field_transformer`s. + [#1417](https://github.com/python-attrs/attrs/issues/1417) + + + +--- + +[Full changelog →](https://www.attrs.org/en/stable/changelog.html) diff --git a/server/libs/attrs-25.3.0.dist-info/RECORD b/server/libs/attrs-25.3.0.dist-info/RECORD new file mode 100644 index 0000000..93d392c --- /dev/null +++ b/server/libs/attrs-25.3.0.dist-info/RECORD @@ -0,0 +1,56 @@ +attr/__init__.py,sha256=fOYIvt1eGSqQre4uCS3sJWKZ0mwAuC8UD6qba5OS9_U,2057 +attr/__init__.pyi,sha256=QIXnnHPoucmDWkbpNsWTP-cgJ1bn8le7DjyRa_wYdew,11281 +attr/__pycache__/__init__.cpython-311.pyc,, +attr/__pycache__/_cmp.cpython-311.pyc,, +attr/__pycache__/_compat.cpython-311.pyc,, +attr/__pycache__/_config.cpython-311.pyc,, +attr/__pycache__/_funcs.cpython-311.pyc,, +attr/__pycache__/_make.cpython-311.pyc,, +attr/__pycache__/_next_gen.cpython-311.pyc,, +attr/__pycache__/_version_info.cpython-311.pyc,, +attr/__pycache__/converters.cpython-311.pyc,, +attr/__pycache__/exceptions.cpython-311.pyc,, +attr/__pycache__/filters.cpython-311.pyc,, +attr/__pycache__/setters.cpython-311.pyc,, +attr/__pycache__/validators.cpython-311.pyc,, +attr/_cmp.py,sha256=3Nn1TjxllUYiX_nJoVnEkXoDk0hM1DYKj5DE7GZe4i0,4117 +attr/_cmp.pyi,sha256=U-_RU_UZOyPUEQzXE6RMYQQcjkZRY25wTH99sN0s7MM,368 +attr/_compat.py,sha256=4hlXbWhdDjQCDK6FKF1EgnZ3POiHgtpp54qE0nxaGHg,2704 +attr/_config.py,sha256=dGq3xR6fgZEF6UBt_L0T-eUHIB4i43kRmH0P28sJVw8,843 +attr/_funcs.py,sha256=5-tUKJtp3h5El55EcDl6GWXFp68fT8D8U7uCRN6497I,15854 +attr/_make.py,sha256=lBUPPmxiA1BeHzB6OlHoCEh--tVvM1ozXO8eXOa6g4c,96664 +attr/_next_gen.py,sha256=7FRkbtl_N017SuBhf_Vw3mw2c2pGZhtCGOzadgz7tp4,24395 +attr/_typing_compat.pyi,sha256=XDP54TUn-ZKhD62TOQebmzrwFyomhUCoGRpclb6alRA,469 +attr/_version_info.py,sha256=exSqb3b5E-fMSsgZAlEw9XcLpEgobPORCZpcaEglAM4,2121 +attr/_version_info.pyi,sha256=x_M3L3WuB7r_ULXAWjx959udKQ4HLB8l-hsc1FDGNvk,209 +attr/converters.py,sha256=GlDeOzPeTFgeBBLbj9G57Ez5lAk68uhSALRYJ_exe84,3861 +attr/converters.pyi,sha256=orU2bff-VjQa2kMDyvnMQV73oJT2WRyQuw4ZR1ym1bE,643 +attr/exceptions.py,sha256=HRFq4iybmv7-DcZwyjl6M1euM2YeJVK_hFxuaBGAngI,1977 +attr/exceptions.pyi,sha256=zZq8bCUnKAy9mDtBEw42ZhPhAUIHoTKedDQInJD883M,539 +attr/filters.py,sha256=ZBiKWLp3R0LfCZsq7X11pn9WX8NslS2wXM4jsnLOGc8,1795 +attr/filters.pyi,sha256=3J5BG-dTxltBk1_-RuNRUHrv2qu1v8v4aDNAQ7_mifA,208 +attr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attr/setters.py,sha256=5-dcT63GQK35ONEzSgfXCkbB7pPkaR-qv15mm4PVSzQ,1617 +attr/setters.pyi,sha256=NnVkaFU1BB4JB8E4JuXyrzTUgvtMpj8p3wBdJY7uix4,584 +attr/validators.py,sha256=WaB1HLAHHqRHWsrv_K9H-sJ7ESil3H3Cmv2d8TtVZx4,20046 +attr/validators.pyi,sha256=s2WhKPqskxbsckJfKk8zOuuB088GfgpyxcCYSNFLqNU,2603 +attrs-25.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +attrs-25.3.0.dist-info/METADATA,sha256=W38cREj7s1wqNf1fg4hVwZmL1xh0AdSp4IhtTMROinw,10993 +attrs-25.3.0.dist-info/RECORD,, +attrs-25.3.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attrs-25.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +attrs-25.3.0.dist-info/licenses/LICENSE,sha256=iCEVyV38KvHutnFPjsbVy8q_Znyv-HKfQkINpj9xTp8,1109 +attrs/__init__.py,sha256=qeQJZ4O08yczSn840v9bYOaZyRE81WsVi-QCrY3krCU,1107 +attrs/__init__.pyi,sha256=nZmInocjM7tHV4AQw0vxO_fo6oJjL_PonlV9zKKW8DY,7931 +attrs/__pycache__/__init__.cpython-311.pyc,, +attrs/__pycache__/converters.cpython-311.pyc,, +attrs/__pycache__/exceptions.cpython-311.pyc,, +attrs/__pycache__/filters.cpython-311.pyc,, +attrs/__pycache__/setters.cpython-311.pyc,, +attrs/__pycache__/validators.cpython-311.pyc,, +attrs/converters.py,sha256=8kQljrVwfSTRu8INwEk8SI0eGrzmWftsT7rM0EqyohM,76 +attrs/exceptions.py,sha256=ACCCmg19-vDFaDPY9vFl199SPXCQMN_bENs4DALjzms,76 +attrs/filters.py,sha256=VOUMZug9uEU6dUuA0dF1jInUK0PL3fLgP0VBS5d-CDE,73 +attrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +attrs/setters.py,sha256=eL1YidYQV3T2h9_SYIZSZR1FAcHGb1TuCTy0E0Lv2SU,73 +attrs/validators.py,sha256=xcy6wD5TtTkdCG1f4XWbocPSO0faBjk5IfVJfP6SUj0,76 diff --git a/server/libs/attrs-25.3.0.dist-info/REQUESTED b/server/libs/attrs-25.3.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/attrs-25.3.0.dist-info/WHEEL b/server/libs/attrs-25.3.0.dist-info/WHEEL new file mode 100644 index 0000000..12228d4 --- /dev/null +++ b/server/libs/attrs-25.3.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/server/libs/attrs-25.3.0.dist-info/licenses/LICENSE b/server/libs/attrs-25.3.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..2bd6453 --- /dev/null +++ b/server/libs/attrs-25.3.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack and the attrs contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/server/libs/attrs/__init__.py b/server/libs/attrs/__init__.py new file mode 100644 index 0000000..e8023ff --- /dev/null +++ b/server/libs/attrs/__init__.py @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: MIT + +from attr import ( + NOTHING, + Attribute, + AttrsInstance, + Converter, + Factory, + NothingType, + _make_getattr, + assoc, + cmp_using, + define, + evolve, + field, + fields, + fields_dict, + frozen, + has, + make_class, + mutable, + resolve_types, + validate, +) +from attr._next_gen import asdict, astuple + +from . import converters, exceptions, filters, setters, validators + + +__all__ = [ + "NOTHING", + "Attribute", + "AttrsInstance", + "Converter", + "Factory", + "NothingType", + "__author__", + "__copyright__", + "__description__", + "__doc__", + "__email__", + "__license__", + "__title__", + "__url__", + "__version__", + "__version_info__", + "asdict", + "assoc", + "astuple", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "field", + "fields", + "fields_dict", + "filters", + "frozen", + "has", + "make_class", + "mutable", + "resolve_types", + "setters", + "validate", + "validators", +] + +__getattr__ = _make_getattr(__name__) diff --git a/server/libs/attrs/__init__.pyi b/server/libs/attrs/__init__.pyi new file mode 100644 index 0000000..648fa7a --- /dev/null +++ b/server/libs/attrs/__init__.pyi @@ -0,0 +1,263 @@ +import sys + +from typing import ( + Any, + Callable, + Mapping, + Sequence, + overload, + TypeVar, +) + +# Because we need to type our own stuff, we have to make everything from +# attr explicitly public too. +from attr import __author__ as __author__ +from attr import __copyright__ as __copyright__ +from attr import __description__ as __description__ +from attr import __email__ as __email__ +from attr import __license__ as __license__ +from attr import __title__ as __title__ +from attr import __url__ as __url__ +from attr import __version__ as __version__ +from attr import __version_info__ as __version_info__ +from attr import assoc as assoc +from attr import Attribute as Attribute +from attr import AttrsInstance as AttrsInstance +from attr import cmp_using as cmp_using +from attr import converters as converters +from attr import Converter as Converter +from attr import evolve as evolve +from attr import exceptions as exceptions +from attr import Factory as Factory +from attr import fields as fields +from attr import fields_dict as fields_dict +from attr import filters as filters +from attr import has as has +from attr import make_class as make_class +from attr import NOTHING as NOTHING +from attr import resolve_types as resolve_types +from attr import setters as setters +from attr import validate as validate +from attr import validators as validators +from attr import attrib, asdict as asdict, astuple as astuple +from attr import NothingType as NothingType + +if sys.version_info >= (3, 11): + from typing import dataclass_transform +else: + from typing_extensions import dataclass_transform + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_EqOrderType = bool | Callable[[Any], Any] +_ValidatorType = Callable[[Any, "Attribute[_T]", _T], Any] +_CallableConverterType = Callable[[Any], Any] +_ConverterType = _CallableConverterType | Converter[Any, Any] +_ReprType = Callable[[Any], str] +_ReprArgType = bool | _ReprType +_OnSetAttrType = Callable[[Any, "Attribute[Any]", Any], Any] +_OnSetAttrArgType = _OnSetAttrType | list[_OnSetAttrType] | setters._NoOpType +_FieldTransformer = Callable[ + [type, list["Attribute[Any]"]], list["Attribute[Any]"] +] +# FIXME: in reality, if multiple validators are passed they must be in a list +# or tuple, but those are invariant and so would prevent subtypes of +# _ValidatorType from working when passed in a list or tuple. +_ValidatorArgType = _ValidatorType[_T] | Sequence[_ValidatorType[_T]] + +@overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: _T | None = ..., + validator: _ValidatorArgType[_T] | None = ..., + repr: _ReprArgType = ..., + hash: bool | None = ..., + init: bool = ..., + metadata: Mapping[Any, Any] | None = ..., + converter: _ConverterType + | list[_ConverterType] + | tuple[_ConverterType] + | None = ..., + factory: Callable[[], _T] | None = ..., + kw_only: bool = ..., + eq: _EqOrderType | None = ..., + order: _EqOrderType | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + alias: str | None = ..., + type: type | None = ..., +) -> Any: ... +@overload +@dataclass_transform(field_specifiers=(attrib, field)) +def define( + maybe_cls: _C, + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@dataclass_transform(field_specifiers=(attrib, field)) +def define( + maybe_cls: None = ..., + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... + +mutable = define + +@overload +@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) +def frozen( + maybe_cls: _C, + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@dataclass_transform(frozen_default=True, field_specifiers=(attrib, field)) +def frozen( + maybe_cls: None = ..., + *, + these: dict[str, Any] | None = ..., + repr: bool = ..., + unsafe_hash: bool | None = ..., + hash: bool | None = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: bool | None = ..., + order: bool | None = ..., + auto_detect: bool = ..., + getstate_setstate: bool | None = ..., + on_setattr: _OnSetAttrArgType | None = ..., + field_transformer: _FieldTransformer | None = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... diff --git a/server/libs/attrs/converters.py b/server/libs/attrs/converters.py new file mode 100644 index 0000000..7821f6c --- /dev/null +++ b/server/libs/attrs/converters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.converters import * # noqa: F403 diff --git a/server/libs/attrs/exceptions.py b/server/libs/attrs/exceptions.py new file mode 100644 index 0000000..3323f9d --- /dev/null +++ b/server/libs/attrs/exceptions.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.exceptions import * # noqa: F403 diff --git a/server/libs/attrs/filters.py b/server/libs/attrs/filters.py new file mode 100644 index 0000000..3080f48 --- /dev/null +++ b/server/libs/attrs/filters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.filters import * # noqa: F403 diff --git a/server/libs/attrs/py.typed b/server/libs/attrs/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/attrs/setters.py b/server/libs/attrs/setters.py new file mode 100644 index 0000000..f3d73bb --- /dev/null +++ b/server/libs/attrs/setters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.setters import * # noqa: F403 diff --git a/server/libs/attrs/validators.py b/server/libs/attrs/validators.py new file mode 100644 index 0000000..037e124 --- /dev/null +++ b/server/libs/attrs/validators.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.validators import * # noqa: F403 diff --git a/server/libs/cattr/__init__.py b/server/libs/cattr/__init__.py new file mode 100644 index 0000000..50f2a06 --- /dev/null +++ b/server/libs/cattr/__init__.py @@ -0,0 +1,25 @@ +from .converters import BaseConverter, Converter, GenConverter, UnstructureStrategy +from .gen import override + +__all__ = ( + "BaseConverter", + "Converter", + "GenConverter", + "UnstructureStrategy", + "global_converter", + "override", + "structure", + "structure_attrs_fromdict", + "structure_attrs_fromtuple", + "unstructure", +) +from cattrs import global_converter + +unstructure = global_converter.unstructure +structure = global_converter.structure +structure_attrs_fromtuple = global_converter.structure_attrs_fromtuple +structure_attrs_fromdict = global_converter.structure_attrs_fromdict +register_structure_hook = global_converter.register_structure_hook +register_structure_hook_func = global_converter.register_structure_hook_func +register_unstructure_hook = global_converter.register_unstructure_hook +register_unstructure_hook_func = global_converter.register_unstructure_hook_func diff --git a/server/libs/cattr/converters.py b/server/libs/cattr/converters.py new file mode 100644 index 0000000..4434fe5 --- /dev/null +++ b/server/libs/cattr/converters.py @@ -0,0 +1,8 @@ +from cattrs.converters import ( + BaseConverter, + Converter, + GenConverter, + UnstructureStrategy, +) + +__all__ = ["BaseConverter", "Converter", "GenConverter", "UnstructureStrategy"] diff --git a/server/libs/cattr/disambiguators.py b/server/libs/cattr/disambiguators.py new file mode 100644 index 0000000..f10797a --- /dev/null +++ b/server/libs/cattr/disambiguators.py @@ -0,0 +1,3 @@ +from cattrs.disambiguators import create_uniq_field_dis_func + +__all__ = ["create_uniq_field_dis_func"] diff --git a/server/libs/cattr/dispatch.py b/server/libs/cattr/dispatch.py new file mode 100644 index 0000000..2474247 --- /dev/null +++ b/server/libs/cattr/dispatch.py @@ -0,0 +1,3 @@ +from cattrs.dispatch import FunctionDispatch, MultiStrategyDispatch + +__all__ = ["FunctionDispatch", "MultiStrategyDispatch"] diff --git a/server/libs/cattr/errors.py b/server/libs/cattr/errors.py new file mode 100644 index 0000000..af092e9 --- /dev/null +++ b/server/libs/cattr/errors.py @@ -0,0 +1,15 @@ +from cattrs.errors import ( + BaseValidationError, + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, + StructureHandlerNotFoundError, +) + +__all__ = [ + "BaseValidationError", + "ClassValidationError", + "ForbiddenExtraKeysError", + "IterableValidationError", + "StructureHandlerNotFoundError", +] diff --git a/server/libs/cattr/gen.py b/server/libs/cattr/gen.py new file mode 100644 index 0000000..b1f63b5 --- /dev/null +++ b/server/libs/cattr/gen.py @@ -0,0 +1,21 @@ +from cattrs.cols import iterable_unstructure_factory as make_iterable_unstructure_fn +from cattrs.gen import ( + make_dict_structure_fn, + make_dict_unstructure_fn, + make_hetero_tuple_unstructure_fn, + make_mapping_structure_fn, + make_mapping_unstructure_fn, + override, +) +from cattrs.gen._consts import AttributeOverride + +__all__ = [ + "AttributeOverride", + "make_dict_structure_fn", + "make_dict_unstructure_fn", + "make_hetero_tuple_unstructure_fn", + "make_iterable_unstructure_fn", + "make_mapping_structure_fn", + "make_mapping_unstructure_fn", + "override", +] diff --git a/server/libs/cattr/preconf/__init__.py b/server/libs/cattr/preconf/__init__.py new file mode 100644 index 0000000..fa6ad35 --- /dev/null +++ b/server/libs/cattr/preconf/__init__.py @@ -0,0 +1,3 @@ +from cattrs.preconf import validate_datetime + +__all__ = ["validate_datetime"] diff --git a/server/libs/cattr/preconf/bson.py b/server/libs/cattr/preconf/bson.py new file mode 100644 index 0000000..4ac9743 --- /dev/null +++ b/server/libs/cattr/preconf/bson.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for bson.""" + +from cattrs.preconf.bson import BsonConverter, configure_converter, make_converter + +__all__ = ["BsonConverter", "configure_converter", "make_converter"] diff --git a/server/libs/cattr/preconf/json.py b/server/libs/cattr/preconf/json.py new file mode 100644 index 0000000..ac77398 --- /dev/null +++ b/server/libs/cattr/preconf/json.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for the stdlib json.""" + +from cattrs.preconf.json import JsonConverter, configure_converter, make_converter + +__all__ = ["JsonConverter", "configure_converter", "make_converter"] diff --git a/server/libs/cattr/preconf/msgpack.py b/server/libs/cattr/preconf/msgpack.py new file mode 100644 index 0000000..bb90250 --- /dev/null +++ b/server/libs/cattr/preconf/msgpack.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for msgpack.""" + +from cattrs.preconf.msgpack import MsgpackConverter, configure_converter, make_converter + +__all__ = ["MsgpackConverter", "configure_converter", "make_converter"] diff --git a/server/libs/cattr/preconf/orjson.py b/server/libs/cattr/preconf/orjson.py new file mode 100644 index 0000000..569ec18 --- /dev/null +++ b/server/libs/cattr/preconf/orjson.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for orjson.""" + +from cattrs.preconf.orjson import OrjsonConverter, configure_converter, make_converter + +__all__ = ["OrjsonConverter", "configure_converter", "make_converter"] diff --git a/server/libs/cattr/preconf/pyyaml.py b/server/libs/cattr/preconf/pyyaml.py new file mode 100644 index 0000000..6bf8b36 --- /dev/null +++ b/server/libs/cattr/preconf/pyyaml.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for pyyaml.""" + +from cattrs.preconf.pyyaml import PyyamlConverter, configure_converter, make_converter + +__all__ = ["PyyamlConverter", "configure_converter", "make_converter"] diff --git a/server/libs/cattr/preconf/tomlkit.py b/server/libs/cattr/preconf/tomlkit.py new file mode 100644 index 0000000..7c0e703 --- /dev/null +++ b/server/libs/cattr/preconf/tomlkit.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for tomlkit.""" + +from cattrs.preconf.tomlkit import TomlkitConverter, configure_converter, make_converter + +__all__ = ["TomlkitConverter", "configure_converter", "make_converter"] diff --git a/server/libs/cattr/preconf/ujson.py b/server/libs/cattr/preconf/ujson.py new file mode 100644 index 0000000..2efbbee --- /dev/null +++ b/server/libs/cattr/preconf/ujson.py @@ -0,0 +1,5 @@ +"""Preconfigured converters for ujson.""" + +from cattrs.preconf.ujson import UjsonConverter, configure_converter, make_converter + +__all__ = ["UjsonConverter", "configure_converter", "make_converter"] diff --git a/server/libs/cattr/py.typed b/server/libs/cattr/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/cattrs-25.1.1.dist-info/INSTALLER b/server/libs/cattrs-25.1.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/server/libs/cattrs-25.1.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/server/libs/cattrs-25.1.1.dist-info/METADATA b/server/libs/cattrs-25.1.1.dist-info/METADATA new file mode 100644 index 0000000..302a49f --- /dev/null +++ b/server/libs/cattrs-25.1.1.dist-info/METADATA @@ -0,0 +1,161 @@ +Metadata-Version: 2.4 +Name: cattrs +Version: 25.1.1 +Summary: Composable complex class support for attrs and dataclasses. +Project-URL: Homepage, https://catt.rs +Project-URL: Changelog, https://catt.rs/en/latest/history.html +Project-URL: Bug Tracker, https://github.com/python-attrs/cattrs/issues +Project-URL: Repository, https://github.com/python-attrs/cattrs +Project-URL: Documentation, https://catt.rs/en/stable/ +Author-email: Tin Tvrtkovic +License: MIT +License-File: LICENSE +Keywords: attrs,dataclasses,serialization +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Typing :: Typed +Requires-Python: >=3.9 +Requires-Dist: attrs>=24.3.0 +Requires-Dist: exceptiongroup>=1.1.1; python_version < '3.11' +Requires-Dist: typing-extensions>=4.12.2 +Provides-Extra: bson +Requires-Dist: pymongo>=4.4.0; extra == 'bson' +Provides-Extra: cbor2 +Requires-Dist: cbor2>=5.4.6; extra == 'cbor2' +Provides-Extra: msgpack +Requires-Dist: msgpack>=1.0.5; extra == 'msgpack' +Provides-Extra: msgspec +Requires-Dist: msgspec>=0.19.0; (implementation_name == 'cpython') and extra == 'msgspec' +Provides-Extra: orjson +Requires-Dist: orjson>=3.10.7; (implementation_name == 'cpython') and extra == 'orjson' +Provides-Extra: pyyaml +Requires-Dist: pyyaml>=6.0; extra == 'pyyaml' +Provides-Extra: tomlkit +Requires-Dist: tomlkit>=0.11.8; extra == 'tomlkit' +Provides-Extra: ujson +Requires-Dist: ujson>=5.10.0; extra == 'ujson' +Description-Content-Type: text/markdown + +# *cattrs*: Flexible Object Serialization and Validation + +*Because validation belongs to the edges.* + +[![Documentation](https://img.shields.io/badge/Docs-Read%20The%20Docs-black)](https://catt.rs/) +[![License: MIT](https://img.shields.io/badge/license-MIT-C06524)](https://github.com/hynek/stamina/blob/main/LICENSE) +[![PyPI](https://img.shields.io/pypi/v/cattrs.svg)](https://pypi.python.org/pypi/cattrs) +[![Supported Python Versions](https://img.shields.io/pypi/pyversions/cattrs.svg)](https://github.com/python-attrs/cattrs) +[![Downloads](https://static.pepy.tech/badge/cattrs/month)](https://pepy.tech/project/cattrs) +[![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Tinche/22405310d6a663164d894a2beab4d44d/raw/covbadge.json)](https://github.com/python-attrs/cattrs/actions/workflows/main.yml) + +--- + + + +**cattrs** is a Swiss Army knife for (un)structuring and validating data in Python. +In practice, that means it converts **unstructured dictionaries** into **proper classes** and back, while **validating** their contents. + + + + +## Example + + + +_cattrs_ works best with [_attrs_](https://www.attrs.org/) classes, and [dataclasses](https://docs.python.org/3/library/dataclasses.html) where simple (un-)structuring works out of the box, even for nested data, without polluting your data model with serialization details: + +```python +>>> from attrs import define +>>> from cattrs import structure, unstructure +>>> @define +... class C: +... a: int +... b: list[str] +>>> instance = structure({'a': 1, 'b': ['x', 'y']}, C) +>>> instance +C(a=1, b=['x', 'y']) +>>> unstructure(instance) +{'a': 1, 'b': ['x', 'y']} +``` + + + + +Have a look at [*Why *cattrs*?*](https://catt.rs/en/latest/why.html) for more examples! + + + +## Features + +### Recursive Unstructuring + +- _attrs_ classes and dataclasses are converted into dictionaries in a way similar to `attrs.asdict()`, or into tuples in a way similar to `attrs.astuple()`. +- Enumeration instances are converted to their values. +- Other types are let through without conversion. This includes types such as integers, dictionaries, lists and instances of non-_attrs_ classes. +- Custom converters for any type can be registered using `register_unstructure_hook`. + + +### Recursive Structuring + +Converts unstructured data into structured data, recursively, according to your specification given as a type. +The following types are supported: + +- `typing.Optional[T]` and its 3.10+ form, `T | None`. +- `list[T]`, `typing.List[T]`, `typing.MutableSequence[T]`, `typing.Sequence[T]` convert to lists. +- `tuple` and `typing.Tuple` (both variants, `tuple[T, ...]` and `tuple[X, Y, Z]`). +- `set[T]`, `typing.MutableSet[T]`, and `typing.Set[T]` convert to sets. +- `frozenset[T]`, and `typing.FrozenSet[T]` convert to frozensets. +- `dict[K, V]`, `typing.Dict[K, V]`, `typing.MutableMapping[K, V]`, and `typing.Mapping[K, V]` convert to dictionaries. +- [`typing.TypedDict`](https://docs.python.org/3/library/typing.html#typing.TypedDict), both ordinary and generic. +- [`typing.NewType`](https://docs.python.org/3/library/typing.html#newtype) +- [PEP 695 type aliases](https://docs.python.org/3/library/typing.html#type-aliases) on 3.12+ +- _attrs_ classes with simple attributes and the usual `__init__`[^simple]. +- All _attrs_ classes and dataclasses with the usual `__init__`, if their complex attributes have type metadata. +- Unions of supported _attrs_ classes, given that all of the classes have a unique field. +- Unions of anything, if you provide a disambiguation function for it. +- Custom converters for any type can be registered using `register_structure_hook`. + +[^simple]: Simple attributes are attributes that can be assigned unstructured data, like numbers, strings, and collections of unstructured data. + + +### Batteries Included + +_cattrs_ comes with pre-configured converters for a number of serialization libraries, including JSON (standard library, [_orjson_](https://pypi.org/project/orjson/), [UltraJSON](https://pypi.org/project/ujson/)), [_msgpack_](https://pypi.org/project/msgpack/), [_cbor2_](https://pypi.org/project/cbor2/), [_bson_](https://pypi.org/project/bson/), [PyYAML](https://pypi.org/project/PyYAML/), [_tomlkit_](https://pypi.org/project/tomlkit/) and [_msgspec_](https://pypi.org/project/msgspec/) (supports only JSON at this time). + +For details, see the [cattrs.preconf package](https://catt.rs/en/stable/preconf.html). + + +## Design Decisions + +_cattrs_ is based on a few fundamental design decisions: + +- Un/structuring rules are separate from the models. + This allows models to have a one-to-many relationship with un/structuring rules, and to create un/structuring rules for models which you do not own and you cannot change. + (_cattrs_ can be configured to use un/structuring rules from models using the [`use_class_methods` strategy](https://catt.rs/en/latest/strategies.html#using-class-specific-structure-and-unstructure-methods).) +- Invent as little as possible; reuse existing ordinary Python instead. + For example, _cattrs_ did not have a custom exception type to group exceptions until the sanctioned Python [`exceptiongroups`](https://docs.python.org/3/library/exceptions.html#ExceptionGroup). + A side-effect of this design decision is that, in a lot of cases, when you're solving _cattrs_ problems you're actually learning Python instead of learning _cattrs_. +- Resist the temptation to guess. + If there are two ways of solving a problem, _cattrs_ should refuse to guess and let the user configure it themselves. + +A foolish consistency is the hobgoblin of little minds, so these decisions can and are sometimes broken, but they have proven to be a good foundation. + + + + +## Credits + +Major credits to Hynek Schlawack for creating [attrs](https://attrs.org) and its predecessor, [characteristic](https://github.com/hynek/characteristic). + +_cattrs_ is tested with [Hypothesis](http://hypothesis.readthedocs.io/en/latest/), by David R. MacIver. + +_cattrs_ is benchmarked using [perf](https://github.com/haypo/perf) and [pytest-benchmark](https://pytest-benchmark.readthedocs.io/en/latest/index.html). + +This package was created with [Cookiecutter](https://github.com/audreyr/cookiecutter) and the [`audreyr/cookiecutter-pypackage`](https://github.com/audreyr/cookiecutter-pypackage) project template. diff --git a/server/libs/cattrs-25.1.1.dist-info/RECORD b/server/libs/cattrs-25.1.1.dist-info/RECORD new file mode 100644 index 0000000..3b261d8 --- /dev/null +++ b/server/libs/cattrs-25.1.1.dist-info/RECORD @@ -0,0 +1,102 @@ +cattr/__init__.py,sha256=bYrmwTYSdYC_ut1xW31V7mxhXBlJQKs8EECgtUBgAuc,906 +cattr/__pycache__/__init__.cpython-311.pyc,, +cattr/__pycache__/converters.cpython-311.pyc,, +cattr/__pycache__/disambiguators.cpython-311.pyc,, +cattr/__pycache__/dispatch.cpython-311.pyc,, +cattr/__pycache__/errors.cpython-311.pyc,, +cattr/__pycache__/gen.cpython-311.pyc,, +cattr/converters.py,sha256=rQhY4J8r7QTZh5WICuFe4GWO1v0DS3DgQ9r569zd6jg,192 +cattr/disambiguators.py,sha256=ugD1fq1Z5x1pGu5P1lMzcT-IEi1q7IfQJIHEdmg62vM,103 +cattr/dispatch.py,sha256=uVEOgHWR9Hn5tm-wIw-bDccqrxJByVi8yRKaYyvL67k,125 +cattr/errors.py,sha256=V4RhoCObwGrlaM3oyn1H_FYxGR8iAB9dG5NxFDYM548,343 +cattr/gen.py,sha256=hWyKoZ_d2D36Jz_npspyGw8s9pWtUA69sXf0R3uOvgM,597 +cattr/preconf/__init__.py,sha256=NqPE7uhVfcP-PggkUpsbfAutMo8oHjcoB1cvjgLft-s,78 +cattr/preconf/__pycache__/__init__.cpython-311.pyc,, +cattr/preconf/__pycache__/bson.cpython-311.pyc,, +cattr/preconf/__pycache__/json.cpython-311.pyc,, +cattr/preconf/__pycache__/msgpack.cpython-311.pyc,, +cattr/preconf/__pycache__/orjson.cpython-311.pyc,, +cattr/preconf/__pycache__/pyyaml.cpython-311.pyc,, +cattr/preconf/__pycache__/tomlkit.cpython-311.pyc,, +cattr/preconf/__pycache__/ujson.cpython-311.pyc,, +cattr/preconf/bson.py,sha256=Bn4hJxac7OthGg_CR4LCPeBp_fz4kx3QniBVOZhguGs,195 +cattr/preconf/json.py,sha256=LpqYuO3oePDxbQtKFKB0SaoeAi3Z_agIgyNn1VQSIVo,206 +cattr/preconf/msgpack.py,sha256=pyJ9L9ekNlZ0IQHbJ9Ay_fi_NOqY5_rE_q-UnD94-RM,207 +cattr/preconf/orjson.py,sha256=Adh-7csx4eqCjx22zipMFgSlDXbR554wvgNHEb8Q5JM,203 +cattr/preconf/pyyaml.py,sha256=Fy40bejjp7uqgoLhTA_p4wZYF0uFaguHbUK9zs9LoC0,203 +cattr/preconf/tomlkit.py,sha256=_gADJ_UYpj3EiNXGYjAfSOkcoFIkLpYVOFfLEqBfIJQ,207 +cattr/preconf/ujson.py,sha256=IzEa7QUcYOaSUMiLQsFEWJnBihmmOLhehsM-5cPY9NI,199 +cattr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cattrs-25.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +cattrs-25.1.1.dist-info/METADATA,sha256=ODqSak3dhIZZjmFa-SZT8Si32_3ey_oo2tUefYx0QtU,8388 +cattrs-25.1.1.dist-info/RECORD,, +cattrs-25.1.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cattrs-25.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87 +cattrs-25.1.1.dist-info/licenses/LICENSE,sha256=9fudHt43qIykf0IMSZ3KD0oFvJk-Esd9I1IKrSkcAb8,1074 +cattrs/__init__.py,sha256=UhiFdxf81gCuBBA6FutoE1oOzthzF_PkAdoE2AVslIo,1901 +cattrs/__pycache__/__init__.cpython-311.pyc,, +cattrs/__pycache__/_compat.cpython-311.pyc,, +cattrs/__pycache__/_generics.cpython-311.pyc,, +cattrs/__pycache__/cols.cpython-311.pyc,, +cattrs/__pycache__/converters.cpython-311.pyc,, +cattrs/__pycache__/disambiguators.cpython-311.pyc,, +cattrs/__pycache__/dispatch.cpython-311.pyc,, +cattrs/__pycache__/errors.cpython-311.pyc,, +cattrs/__pycache__/fns.cpython-311.pyc,, +cattrs/__pycache__/literals.cpython-311.pyc,, +cattrs/__pycache__/typealiases.cpython-311.pyc,, +cattrs/__pycache__/types.cpython-311.pyc,, +cattrs/__pycache__/v.cpython-311.pyc,, +cattrs/_compat.py,sha256=dMRB8a8RkxFdnQDKRpamresVF_SBkksM2_ZMAuL0s2w,11987 +cattrs/_generics.py,sha256=keExDE2CGIer8ci12SoJ_rXYTLva9P29uLlvyb_fxtM,966 +cattrs/cols.py,sha256=mWDchfvjMQ6uACKSfdZs05YiXrWdph2HOJfaqY3D_EI,8848 +cattrs/converters.py,sha256=ui4BSAxnV1J6Oh0pYN2oOHgp-mIybsXW-M8SXPDNzlo,54262 +cattrs/disambiguators.py,sha256=eUyWMtW6bQJcXGOWiraq1pFcMlUbo9BJHxFY--KF6Lk,6867 +cattrs/dispatch.py,sha256=9qA-pmsPvgrM6MGP8Ev2gVP6YL2rXvoBla_C0VgHxQ0,6780 +cattrs/errors.py,sha256=6IGfE-wVQbOaDNN4xAQJf7Hk_t2QNV0Zem64D6yMZrU,4168 +cattrs/fns.py,sha256=z5z1VZOZv8t5LwG8cBM_tIXg-_PlQUOyZb9wIrXNqlw,626 +cattrs/gen/__init__.py,sha256=bpRGHd3G0UTpWcHRcJTNAUcWmX-evoPHGvpH6u29FDU,38842 +cattrs/gen/__pycache__/__init__.cpython-311.pyc,, +cattrs/gen/__pycache__/_consts.cpython-311.pyc,, +cattrs/gen/__pycache__/_generics.cpython-311.pyc,, +cattrs/gen/__pycache__/_lc.cpython-311.pyc,, +cattrs/gen/__pycache__/_shared.cpython-311.pyc,, +cattrs/gen/__pycache__/typeddicts.cpython-311.pyc,, +cattrs/gen/_consts.py,sha256=ZwT_m2J3S7p-UjltpbA1WtfQZLNj9KhmFYCAv6Zl-g0,511 +cattrs/gen/_generics.py,sha256=_DyXCGql2QIxGhAv3_B1hsi80uPK8PhK2hhZa95YOlo,3011 +cattrs/gen/_lc.py,sha256=4fjeUsmgQcCAIjnNndBic0gf5qKmxVS3CZHqUQ9Rw5g,882 +cattrs/gen/_shared.py,sha256=xKsfcVtpyYIir9AW8VuOVoiSbaEI7tsSL0JpUCIUX-g,2296 +cattrs/gen/typeddicts.py,sha256=Ck3QMr_B1T7vwxyRjfZPHafKphN2hndL181dpQNxzPs,21254 +cattrs/literals.py,sha256=0kzAewmWk9ikJGoKq4ysnAR22DMawG3iNqLl8NLgpk0,331 +cattrs/preconf/__init__.py,sha256=P7czFRcjeN6zBcdwUyeBloniltlJptCa8Yd2uFGlz9w,1527 +cattrs/preconf/__pycache__/__init__.cpython-311.pyc,, +cattrs/preconf/__pycache__/bson.cpython-311.pyc,, +cattrs/preconf/__pycache__/cbor2.cpython-311.pyc,, +cattrs/preconf/__pycache__/json.cpython-311.pyc,, +cattrs/preconf/__pycache__/msgpack.cpython-311.pyc,, +cattrs/preconf/__pycache__/msgspec.cpython-311.pyc,, +cattrs/preconf/__pycache__/orjson.cpython-311.pyc,, +cattrs/preconf/__pycache__/pyyaml.cpython-311.pyc,, +cattrs/preconf/__pycache__/tomlkit.cpython-311.pyc,, +cattrs/preconf/__pycache__/ujson.cpython-311.pyc,, +cattrs/preconf/bson.py,sha256=6p1kmOFMjswSXFCb1hKJeNvr3kNsAm1gfX_DA6igq8E,4201 +cattrs/preconf/cbor2.py,sha256=LnREcjpOp_402poUGRVIhDWI4f_R1wvJkdKvs5MrTGU,2022 +cattrs/preconf/json.py,sha256=zTrkfjOxXZFwwabNESeafy-C7MEpn7Cw5AdhkkeOjU4,2631 +cattrs/preconf/msgpack.py,sha256=dZE9tsAA5qX3pSc3MZmlGuvJ5q_wI6mANDyugKXKj-E,2325 +cattrs/preconf/msgspec.py,sha256=Ds0rPW4900zsBLqfupF-smkg9_Kwyx7D_Vh9a0yJB8M,7250 +cattrs/preconf/orjson.py,sha256=5MBcUsyp3eGsHgLfLtt8-q90L2mxjD0ttnrWBUIwouo,3870 +cattrs/preconf/pyyaml.py,sha256=w0aM_gJ6VhZf-Zpu_UlJki7rdgv4mfaSXElPofB3nlE,2378 +cattrs/preconf/tomlkit.py,sha256=gJWGJjMONCViTMZuphOg2xXzjQt3SCEVFVdoKgDjqc8,3148 +cattrs/preconf/ujson.py,sha256=wRLidBM8aWucFkCQ9haiktY8xYoCdanDhQuKJLQJgGM,2425 +cattrs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +cattrs/strategies/__init__.py,sha256=nkZWCzSRYcS-75FMfk52mioZSuWykaN8hB39Vig5Xkg,339 +cattrs/strategies/__pycache__/__init__.cpython-311.pyc,, +cattrs/strategies/__pycache__/_class_methods.cpython-311.pyc,, +cattrs/strategies/__pycache__/_subclasses.cpython-311.pyc,, +cattrs/strategies/__pycache__/_unions.cpython-311.pyc,, +cattrs/strategies/_class_methods.py,sha256=O5xhQCzNpuFiDNDMlbcyeOVqyrV65NhMZNRsG3jnoBU,2591 +cattrs/strategies/_subclasses.py,sha256=aCE2UQjevZQHMnPOPyl2qR_hgRpgRUt1j9lE4qZ3hNc,9365 +cattrs/strategies/_unions.py,sha256=YBBklVSWJ-7DSkLDLpumwAJJ39ALuSGyB6W0Ptz5Rz4,9355 +cattrs/typealiases.py,sha256=toHavC2kJsIcxThwvATPO5JShzKeC8kIl9KqteFohbw,1619 +cattrs/types.py,sha256=cqvfmzliYfrvPswxlW_tN4DmhQ2xpAKQvVbNBJaxiWs,278 +cattrs/v.py,sha256=IqUajgJFCKJYf-4S9TCKRtJcmmK4c3En69TGuf2FKOs,4126 diff --git a/server/libs/cattrs-25.1.1.dist-info/REQUESTED b/server/libs/cattrs-25.1.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/cattrs-25.1.1.dist-info/WHEEL b/server/libs/cattrs-25.1.1.dist-info/WHEEL new file mode 100644 index 0000000..12228d4 --- /dev/null +++ b/server/libs/cattrs-25.1.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: hatchling 1.27.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/server/libs/cattrs-25.1.1.dist-info/licenses/LICENSE b/server/libs/cattrs-25.1.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000..340022c --- /dev/null +++ b/server/libs/cattrs-25.1.1.dist-info/licenses/LICENSE @@ -0,0 +1,11 @@ + +MIT License + +Copyright (c) 2016, Tin Tvrtković + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/server/libs/cattrs/__init__.py b/server/libs/cattrs/__init__.py new file mode 100644 index 0000000..2252272 --- /dev/null +++ b/server/libs/cattrs/__init__.py @@ -0,0 +1,57 @@ +from typing import Final + +from .converters import BaseConverter, Converter, GenConverter, UnstructureStrategy +from .errors import ( + AttributeValidationNote, + BaseValidationError, + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) +from .gen import override +from .types import SimpleStructureHook +from .v import transform_error + +__all__ = [ + "AttributeValidationNote", + "BaseConverter", + "BaseValidationError", + "ClassValidationError", + "Converter", + "ForbiddenExtraKeysError", + "GenConverter", + "IterableValidationError", + "IterableValidationNote", + "SimpleStructureHook", + "StructureHandlerNotFoundError", + "UnstructureStrategy", + "get_structure_hook", + "get_unstructure_hook", + "global_converter", + "override", + "register_structure_hook", + "register_structure_hook_func", + "register_unstructure_hook", + "register_unstructure_hook_func", + "structure", + "structure_attrs_fromdict", + "structure_attrs_fromtuple", + "transform_error", + "unstructure", +] + +#: The global converter. Prefer creating your own if customizations are required. +global_converter: Final = Converter() + +unstructure = global_converter.unstructure +structure = global_converter.structure +structure_attrs_fromtuple = global_converter.structure_attrs_fromtuple +structure_attrs_fromdict = global_converter.structure_attrs_fromdict +register_structure_hook = global_converter.register_structure_hook +register_structure_hook_func = global_converter.register_structure_hook_func +register_unstructure_hook = global_converter.register_unstructure_hook +register_unstructure_hook_func = global_converter.register_unstructure_hook_func +get_structure_hook: Final = global_converter.get_structure_hook +get_unstructure_hook: Final = global_converter.get_unstructure_hook diff --git a/server/libs/cattrs/_compat.py b/server/libs/cattrs/_compat.py new file mode 100644 index 0000000..da50c22 --- /dev/null +++ b/server/libs/cattrs/_compat.py @@ -0,0 +1,428 @@ +import sys +from collections import Counter, deque +from collections.abc import Mapping as AbcMapping +from collections.abc import MutableMapping as AbcMutableMapping +from collections.abc import MutableSequence as AbcMutableSequence +from collections.abc import MutableSet as AbcMutableSet +from collections.abc import Sequence as AbcSequence +from collections.abc import Set as AbcSet +from dataclasses import MISSING, Field, is_dataclass +from dataclasses import fields as dataclass_fields +from functools import partial +from inspect import signature as _signature +from types import GenericAlias +from typing import ( + Annotated, + Any, + Deque, + Dict, + Final, + FrozenSet, + Generic, + List, + Literal, + NewType, + Optional, + Protocol, + Tuple, + Union, + _AnnotatedAlias, + _GenericAlias, + _SpecialGenericAlias, + _UnionGenericAlias, + get_args, + get_origin, + get_type_hints, +) +from typing import Counter as TypingCounter +from typing import Mapping as TypingMapping +from typing import MutableMapping as TypingMutableMapping +from typing import MutableSequence as TypingMutableSequence +from typing import MutableSet as TypingMutableSet +from typing import Sequence as TypingSequence +from typing import Set as TypingSet + +from attrs import NOTHING, Attribute, Factory, NothingType, resolve_types +from attrs import fields as attrs_fields +from attrs import fields_dict as attrs_fields_dict + +__all__ = [ + "ANIES", + "ExceptionGroup", + "ExtensionsTypedDict", + "TypeAlias", + "adapted_fields", + "fields_dict", + "has", + "is_typeddict", +] + +try: + from typing_extensions import TypedDict as ExtensionsTypedDict +except ImportError: # pragma: no cover + ExtensionsTypedDict = None + +if sys.version_info >= (3, 11): + from builtins import ExceptionGroup +else: + from exceptiongroup import ExceptionGroup + +try: + from typing_extensions import is_typeddict as _is_typeddict +except ImportError: # pragma: no cover + assert sys.version_info >= (3, 10) + from typing import is_typeddict as _is_typeddict + +try: + from typing_extensions import TypeAlias +except ImportError: # pragma: no cover + assert sys.version_info >= (3, 11) + from typing import TypeAlias + +LITERALS = {Literal} +try: + from typing_extensions import Literal as teLiteral + + LITERALS.add(teLiteral) +except ImportError: # pragma: no cover + pass + +# On some Python versions, `typing_extensions.Any` is different than +# `typing.Any`. +try: + from typing_extensions import Any as teAny + + ANIES = frozenset([Any, teAny]) +except ImportError: # pragma: no cover + ANIES = frozenset([Any]) + +NoneType = type(None) + + +def is_optional(typ: Any) -> bool: + return is_union_type(typ) and NoneType in typ.__args__ and len(typ.__args__) == 2 + + +def is_typeddict(cls: Any): + """Thin wrapper around typing(_extensions).is_typeddict""" + return _is_typeddict(getattr(cls, "__origin__", cls)) + + +def has(cls): + return hasattr(cls, "__attrs_attrs__") or hasattr(cls, "__dataclass_fields__") + + +def has_with_generic(cls): + """Test whether the class if a normal or generic attrs or dataclass.""" + return has(cls) or has(get_origin(cls)) + + +def fields(type): + try: + return type.__attrs_attrs__ + except AttributeError: + return dataclass_fields(type) + + +def fields_dict(type) -> dict[str, Union[Attribute, Field]]: + """Return the fields_dict for attrs and dataclasses.""" + if is_dataclass(type): + return {f.name: f for f in dataclass_fields(type)} + return attrs_fields_dict(type) + + +def adapted_fields(cl: type) -> list[Attribute]: + """Return the attrs format of `fields()` for attrs and dataclasses. + + Resolves `attrs` stringified annotations, if present. + """ + if is_dataclass(cl): + attrs = dataclass_fields(cl) + if any(isinstance(a.type, str) for a in attrs): + # Do this conditionally in case `get_type_hints` fails, so + # users can resolve on their own first. + type_hints = get_type_hints(cl) + else: + type_hints = {} + return [ + Attribute( + attr.name, + ( + attr.default + if attr.default is not MISSING + else ( + Factory(attr.default_factory) + if attr.default_factory is not MISSING + else NOTHING + ) + ), + None, + True, + None, + True, + attr.init, + True, + type=type_hints.get(attr.name, attr.type), + alias=attr.name, + kw_only=getattr(attr, "kw_only", False), + ) + for attr in attrs + ] + attribs = attrs_fields(cl) + if any(isinstance(a.type, str) for a in attribs): + # PEP 563 annotations - need to be resolved. + resolve_types(cl) + attribs = attrs_fields(cl) + return attribs + + +def is_subclass(obj: type, bases) -> bool: + """A safe version of issubclass (won't raise).""" + try: + return issubclass(obj, bases) + except TypeError: + return False + + +def is_hetero_tuple(type: Any) -> bool: + origin = getattr(type, "__origin__", None) + return origin is tuple and ... not in type.__args__ + + +def is_protocol(type: Any) -> bool: + return is_subclass(type, Protocol) and getattr(type, "_is_protocol", False) + + +def is_bare_final(type) -> bool: + return type is Final + + +def get_final_base(type) -> Optional[type]: + """Return the base of the Final annotation, if it is Final.""" + if type is Final: + return Any + if type.__class__ is _GenericAlias and type.__origin__ is Final: + return type.__args__[0] + return None + + +OriginAbstractSet = AbcSet +OriginMutableSet = AbcMutableSet + +signature = _signature + +if sys.version_info >= (3, 10): + signature = partial(_signature, eval_str=True) + + +try: + # Not present on 3.9.0, so we try carefully. + from typing import _LiteralGenericAlias + + def is_literal(type: Any) -> bool: + """Is this a literal?""" + return type in LITERALS or ( + isinstance( + type, (_GenericAlias, _LiteralGenericAlias, _SpecialGenericAlias) + ) + and type.__origin__ in LITERALS + ) + +except ImportError: # pragma: no cover + + def is_literal(_) -> bool: + return False + + +Set = AbcSet +MutableSet = AbcMutableSet +Sequence = AbcSequence +MutableSequence = AbcMutableSequence +MutableMapping = AbcMutableMapping +Mapping = AbcMapping +FrozenSetSubscriptable = frozenset +TupleSubscriptable = tuple + + +def is_annotated(type) -> bool: + return getattr(type, "__class__", None) is _AnnotatedAlias + + +def is_tuple(type): + return ( + type in (Tuple, tuple) + or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, Tuple)) + or (getattr(type, "__origin__", None) is tuple) + ) + + +if sys.version_info >= (3, 10): + + def is_union_type(obj): + from types import UnionType + + return ( + obj is Union + or (isinstance(obj, _UnionGenericAlias) and obj.__origin__ is Union) + or isinstance(obj, UnionType) + ) + + def get_newtype_base(typ: Any) -> Optional[type]: + if typ is NewType or isinstance(typ, NewType): + return typ.__supertype__ + return None + + if sys.version_info >= (3, 11): + from typing import NotRequired, Required + else: + from typing_extensions import NotRequired, Required + +else: + # 3.9 + from typing_extensions import NotRequired, Required + + def is_union_type(obj): + return obj is Union or ( + isinstance(obj, _UnionGenericAlias) and obj.__origin__ is Union + ) + + def get_newtype_base(typ: Any) -> Optional[type]: + supertype = getattr(typ, "__supertype__", None) + if ( + supertype is not None + and getattr(typ, "__qualname__", "") == "NewType..new_type" + and typ.__module__ in ("typing", "typing_extensions") + ): + return supertype + return None + + +def get_notrequired_base(type) -> Union[Any, NothingType]: + if is_annotated(type): + # Handle `Annotated[NotRequired[int]]` + type = get_args(type)[0] + if get_origin(type) in (NotRequired, Required): + return get_args(type)[0] + return NOTHING + + +def is_sequence(type: Any) -> bool: + """A predicate function for sequences. + + Matches lists, sequences, mutable sequences, deques and homogenous + tuples. + """ + origin = getattr(type, "__origin__", None) + return ( + type + in ( + List, + list, + TypingSequence, + TypingMutableSequence, + AbcMutableSequence, + tuple, + Tuple, + deque, + Deque, + ) + or ( + type.__class__ is _GenericAlias + and ( + ((origin is not tuple) and is_subclass(origin, TypingSequence)) + or (origin is tuple and type.__args__[1] is ...) + ) + ) + or (origin in (list, deque, AbcMutableSequence, AbcSequence)) + or (origin is tuple and type.__args__[1] is ...) + ) + + +def is_deque(type): + return ( + type in (deque, Deque) + or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, deque)) + or (getattr(type, "__origin__", None) is deque) + ) + + +def is_mutable_set(type: Any) -> bool: + """A predicate function for (mutable) sets. + + Matches built-in sets and sets from the typing module. + """ + return ( + type in (TypingSet, TypingMutableSet, set) + or ( + type.__class__ is _GenericAlias + and is_subclass(type.__origin__, TypingMutableSet) + ) + or (getattr(type, "__origin__", None) in (set, AbcMutableSet, AbcSet)) + ) + + +def is_frozenset(type: Any) -> bool: + """A predicate function for frozensets. + + Matches built-in frozensets and frozensets from the typing module. + """ + return ( + type in (FrozenSet, frozenset) + or (type.__class__ is _GenericAlias and is_subclass(type.__origin__, FrozenSet)) + or (getattr(type, "__origin__", None) is frozenset) + ) + + +def is_bare(type): + return isinstance(type, _SpecialGenericAlias) or ( + not hasattr(type, "__origin__") and not hasattr(type, "__args__") + ) + + +def is_mapping(type: Any) -> bool: + """A predicate function for mappings.""" + return ( + type in (dict, Dict, TypingMapping, TypingMutableMapping, AbcMutableMapping) + or ( + type.__class__ is _GenericAlias + and is_subclass(type.__origin__, TypingMapping) + ) + or is_subclass( + getattr(type, "__origin__", type), (dict, AbcMutableMapping, AbcMapping) + ) + ) + + +def is_counter(type): + return ( + type in (Counter, TypingCounter) or getattr(type, "__origin__", None) is Counter + ) + + +def is_generic(type) -> bool: + """Whether `type` is a generic type.""" + # Inheriting from protocol will inject `Generic` into the MRO + # without `__orig_bases__`. + return isinstance(type, (_GenericAlias, GenericAlias)) or ( + is_subclass(type, Generic) and hasattr(type, "__orig_bases__") + ) + + +def copy_with(type, args): + """Replace a generic type's arguments.""" + if is_annotated(type): + # typing.Annotated requires a special case. + return Annotated[args] + if isinstance(args, tuple) and len(args) == 1: + # Some annotations can't handle 1-tuples. + args = args[0] + return type.__origin__[args] + + +def get_full_type_hints(obj, globalns=None, localns=None): + return get_type_hints(obj, globalns, localns, include_extras=True) + + +def is_generic_attrs(type) -> bool: + """Return True for both specialized (A[int]) and unspecialized (A) generics.""" + return is_generic(type) and has(type.__origin__) diff --git a/server/libs/cattrs/_generics.py b/server/libs/cattrs/_generics.py new file mode 100644 index 0000000..6f36e94 --- /dev/null +++ b/server/libs/cattrs/_generics.py @@ -0,0 +1,32 @@ +from collections.abc import Mapping +from typing import Any + +from attrs import NOTHING +from typing_extensions import Self + +from ._compat import copy_with, get_args, is_annotated, is_generic + + +def deep_copy_with(t, mapping: Mapping[str, Any], self_is=NOTHING): + args = get_args(t) + rest = () + if is_annotated(t) and args: + # If we're dealing with `Annotated`, we only map the first type parameter + rest = tuple(args[1:]) + args = (args[0],) + new_args = ( + tuple( + ( + self_is + if a is Self and self_is is not NOTHING + else ( + mapping[a.__name__] + if hasattr(a, "__name__") and a.__name__ in mapping + else (deep_copy_with(a, mapping, self_is) if is_generic(a) else a) + ) + ) + for a in args + ) + + rest + ) + return copy_with(t, new_args) if new_args != args else t diff --git a/server/libs/cattrs/cols.py b/server/libs/cattrs/cols.py new file mode 100644 index 0000000..0b578eb --- /dev/null +++ b/server/libs/cattrs/cols.py @@ -0,0 +1,309 @@ +"""Utility functions for collections.""" + +from __future__ import annotations + +from collections import defaultdict +from collections.abc import Callable, Iterable +from functools import partial +from typing import ( + TYPE_CHECKING, + Any, + DefaultDict, + Literal, + NamedTuple, + TypeVar, + get_type_hints, +) + +from attrs import NOTHING, Attribute, NothingType + +from ._compat import ( + ANIES, + get_args, + get_origin, + is_bare, + is_frozenset, + is_mapping, + is_sequence, + is_subclass, +) +from ._compat import is_mutable_set as is_set +from .dispatch import StructureHook, UnstructureHook +from .errors import IterableValidationError, IterableValidationNote +from .fns import identity +from .gen import ( + AttributeOverride, + already_generating, + make_dict_structure_fn_from_attrs, + make_dict_unstructure_fn_from_attrs, + make_hetero_tuple_unstructure_fn, + mapping_structure_factory, + mapping_unstructure_factory, +) +from .gen import make_iterable_unstructure_fn as iterable_unstructure_factory + +if TYPE_CHECKING: + from .converters import BaseConverter + +__all__ = [ + "defaultdict_structure_factory", + "is_any_set", + "is_defaultdict", + "is_frozenset", + "is_mapping", + "is_namedtuple", + "is_sequence", + "is_set", + "iterable_unstructure_factory", + "list_structure_factory", + "mapping_structure_factory", + "mapping_unstructure_factory", + "namedtuple_dict_structure_factory", + "namedtuple_dict_unstructure_factory", + "namedtuple_structure_factory", + "namedtuple_unstructure_factory", +] + + +def is_any_set(type) -> bool: + """A predicate function for both mutable and frozensets.""" + return is_set(type) or is_frozenset(type) + + +def is_namedtuple(type: Any) -> bool: + """A predicate function for named tuples.""" + + if is_subclass(type, tuple): + for cl in type.mro(): + orig_bases = cl.__dict__.get("__orig_bases__", ()) + if NamedTuple in orig_bases: + return True + return False + + +def _is_passthrough(type: type[tuple], converter: BaseConverter) -> bool: + """If all fields would be passed through, this class should not be processed + either. + """ + return all( + converter.get_unstructure_hook(t) == identity + for t in type.__annotations__.values() + ) + + +T = TypeVar("T") + + +def list_structure_factory(type: type, converter: BaseConverter) -> StructureHook: + """A hook factory for structuring lists. + + Converts any given iterable into a list. + """ + + if is_bare(type) or type.__args__[0] in ANIES: + + def structure_list(obj: Iterable[T], _: type = type) -> list[T]: + return list(obj) + + return structure_list + + elem_type = type.__args__[0] + + try: + handler = converter.get_structure_hook(elem_type) + except RecursionError: + # Break the cycle by using late binding. + handler = converter.structure + + if converter.detailed_validation: + + def structure_list( + obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type + ) -> list[T]: + errors = [] + res = [] + ix = 0 # Avoid `enumerate` for performance. + for e in obj: + try: + res.append(handler(e, _elem_type)) + except Exception as e: + msg = IterableValidationNote( + f"Structuring {type} @ index {ix}", ix, elem_type + ) + e.__notes__ = [*getattr(e, "__notes__", []), msg] + errors.append(e) + finally: + ix += 1 + if errors: + raise IterableValidationError( + f"While structuring {type!r}", errors, type + ) + + return res + + else: + + def structure_list( + obj: Iterable[T], _: type = type, _handler=handler, _elem_type=elem_type + ) -> list[T]: + return [_handler(e, _elem_type) for e in obj] + + return structure_list + + +def namedtuple_unstructure_factory( + cl: type[tuple], converter: BaseConverter, unstructure_to: Any = None +) -> UnstructureHook: + """A hook factory for unstructuring namedtuples. + + :param unstructure_to: Force unstructuring to this type, if provided. + """ + + if unstructure_to is None and _is_passthrough(cl, converter): + return identity + + return make_hetero_tuple_unstructure_fn( + cl, + converter, + unstructure_to=tuple if unstructure_to is None else unstructure_to, + type_args=tuple(cl.__annotations__.values()), + ) + + +def namedtuple_structure_factory( + cl: type[tuple], converter: BaseConverter +) -> StructureHook: + """A hook factory for structuring namedtuples from iterables.""" + # We delegate to the existing infrastructure for heterogenous tuples. + hetero_tuple_type = tuple[tuple(cl.__annotations__.values())] + base_hook = converter.get_structure_hook(hetero_tuple_type) + return lambda v, _: cl(*base_hook(v, hetero_tuple_type)) + + +def _namedtuple_to_attrs(cl: type[tuple]) -> list[Attribute]: + """Generate pseudo attributes for a namedtuple.""" + return [ + Attribute( + name, + cl._field_defaults.get(name, NOTHING), + None, + False, + False, + False, + True, + False, + type=a, + alias=name, + ) + for name, a in get_type_hints(cl).items() + ] + + +def namedtuple_dict_structure_factory( + cl: type[tuple], + converter: BaseConverter, + detailed_validation: bool | Literal["from_converter"] = "from_converter", + forbid_extra_keys: bool = False, + use_linecache: bool = True, + /, + **kwargs: AttributeOverride, +) -> StructureHook: + """A hook factory for hooks structuring namedtuples from dictionaries. + + :param forbid_extra_keys: Whether the hook should raise a `ForbiddenExtraKeysError` + if unknown keys are encountered. + :param use_linecache: Whether to store the source code in the Python linecache. + + .. versionadded:: 24.1.0 + """ + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + else: + if cl in working_set: + raise RecursionError() + + working_set.add(cl) + + try: + return make_dict_structure_fn_from_attrs( + _namedtuple_to_attrs(cl), + cl, + converter, + _cattrs_forbid_extra_keys=forbid_extra_keys, + _cattrs_use_detailed_validation=detailed_validation, + _cattrs_use_linecache=use_linecache, + **kwargs, + ) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + +def namedtuple_dict_unstructure_factory( + cl: type[tuple], + converter: BaseConverter, + omit_if_default: bool = False, + use_linecache: bool = True, + /, + **kwargs: AttributeOverride, +) -> UnstructureHook: + """A hook factory for hooks unstructuring namedtuples to dictionaries. + + :param omit_if_default: When true, attributes equal to their default values + will be omitted in the result dictionary. + :param use_linecache: Whether to store the source code in the Python linecache. + + .. versionadded:: 24.1.0 + """ + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + if cl in working_set: + raise RecursionError() + + working_set.add(cl) + + try: + return make_dict_unstructure_fn_from_attrs( + _namedtuple_to_attrs(cl), + cl, + converter, + _cattrs_omit_if_default=omit_if_default, + _cattrs_use_linecache=use_linecache, + **kwargs, + ) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + +def is_defaultdict(type: Any) -> bool: + """Is this type a defaultdict? + + Bare defaultdicts (defaultdicts with no type arguments) are not supported + since there's no way to discover their _default_factory_. + """ + return is_subclass(get_origin(type), (defaultdict, DefaultDict)) + + +def defaultdict_structure_factory( + type: type[defaultdict], + converter: BaseConverter, + default_factory: Callable[[], Any] | NothingType = NOTHING, +) -> StructureHook: + """A structure hook factory for defaultdicts. + + The value type parameter will be used as the _default factory_. + """ + if default_factory is NOTHING: + default_factory = get_args(type)[1] + return mapping_structure_factory( + type, converter, partial(defaultdict, default_factory) + ) diff --git a/server/libs/cattrs/converters.py b/server/libs/cattrs/converters.py new file mode 100644 index 0000000..9a54476 --- /dev/null +++ b/server/libs/cattrs/converters.py @@ -0,0 +1,1429 @@ +from __future__ import annotations + +from collections import Counter, deque +from collections.abc import Callable, Iterable +from collections.abc import Mapping as AbcMapping +from collections.abc import MutableMapping as AbcMutableMapping +from dataclasses import Field +from enum import Enum +from inspect import Signature +from inspect import signature as inspect_signature +from pathlib import Path +from typing import Any, Optional, Tuple, TypeVar, overload + +from attrs import Attribute, resolve_types +from attrs import has as attrs_has +from typing_extensions import Self + +from ._compat import ( + ANIES, + FrozenSetSubscriptable, + Mapping, + MutableMapping, + MutableSequence, + NoneType, + OriginAbstractSet, + OriginMutableSet, + Sequence, + Set, + TypeAlias, + fields, + get_final_base, + get_newtype_base, + get_origin, + has, + has_with_generic, + is_annotated, + is_bare, + is_counter, + is_deque, + is_frozenset, + is_generic, + is_generic_attrs, + is_hetero_tuple, + is_literal, + is_mapping, + is_mutable_set, + is_optional, + is_protocol, + is_sequence, + is_tuple, + is_typeddict, + is_union_type, + signature, +) +from .cols import ( + defaultdict_structure_factory, + is_defaultdict, + is_namedtuple, + iterable_unstructure_factory, + list_structure_factory, + mapping_structure_factory, + mapping_unstructure_factory, + namedtuple_structure_factory, + namedtuple_unstructure_factory, +) +from .disambiguators import create_default_dis_func, is_supported_union +from .dispatch import ( + HookFactory, + MultiStrategyDispatch, + StructuredValue, + StructureHook, + TargetType, + UnstructuredValue, + UnstructureHook, +) +from .errors import ( + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) +from .fns import Predicate, identity, raise_error +from .gen import ( + AttributeOverride, + HeteroTupleUnstructureFn, + IterableUnstructureFn, + MappingUnstructureFn, + make_dict_structure_fn, + make_dict_unstructure_fn, + make_hetero_tuple_unstructure_fn, +) +from .gen.typeddicts import make_dict_structure_fn as make_typeddict_dict_struct_fn +from .gen.typeddicts import make_dict_unstructure_fn as make_typeddict_dict_unstruct_fn +from .literals import is_literal_containing_enums +from .typealiases import ( + get_type_alias_base, + is_type_alias, + type_alias_structure_factory, +) +from .types import SimpleStructureHook + +__all__ = ["BaseConverter", "Converter", "GenConverter", "UnstructureStrategy"] + +T = TypeVar("T") +V = TypeVar("V") + +UnstructureHookFactory = TypeVar( + "UnstructureHookFactory", bound=HookFactory[UnstructureHook] +) + +# The Extended factory also takes a converter. +ExtendedUnstructureHookFactory: TypeAlias = Callable[[TargetType, T], UnstructureHook] + +# This typevar for the BaseConverter. +AnyUnstructureHookFactoryBase = TypeVar( + "AnyUnstructureHookFactoryBase", + bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[BaseConverter]", +) + +# This typevar for the Converter. +AnyUnstructureHookFactory = TypeVar( + "AnyUnstructureHookFactory", + bound="HookFactory[UnstructureHook] | ExtendedUnstructureHookFactory[Converter]", +) + +StructureHookFactory = TypeVar("StructureHookFactory", bound=HookFactory[StructureHook]) + +# The Extended factory also takes a converter. +ExtendedStructureHookFactory: TypeAlias = Callable[[TargetType, T], StructureHook] + +# This typevar for the BaseConverter. +AnyStructureHookFactoryBase = TypeVar( + "AnyStructureHookFactoryBase", + bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[BaseConverter]", +) + +# This typevar for the Converter. +AnyStructureHookFactory = TypeVar( + "AnyStructureHookFactory", + bound="HookFactory[StructureHook] | ExtendedStructureHookFactory[Converter]", +) + +UnstructureHookT = TypeVar("UnstructureHookT", bound=UnstructureHook) +StructureHookT = TypeVar("StructureHookT", bound=StructureHook) +CounterT = TypeVar("CounterT", bound=Counter) + + +class UnstructureStrategy(Enum): + """`attrs` classes unstructuring strategies.""" + + AS_DICT = "asdict" + AS_TUPLE = "astuple" + + +def _is_extended_factory(factory: Callable) -> bool: + """Does this factory also accept a converter arg?""" + # We use the original `inspect.signature` to not evaluate string + # annotations. + sig = inspect_signature(factory) + return ( + len(sig.parameters) >= 2 + and (list(sig.parameters.values())[1]).default is Signature.empty + ) + + +class BaseConverter: + """Converts between structured and unstructured data.""" + + __slots__ = ( + "_dict_factory", + "_prefer_attrib_converters", + "_struct_copy_skip", + "_structure_attrs", + "_structure_func", + "_union_struct_registry", + "_unstruct_copy_skip", + "_unstructure_attrs", + "_unstructure_func", + "detailed_validation", + ) + + def __init__( + self, + dict_factory: Callable[[], Any] = dict, + unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT, + prefer_attrib_converters: bool = False, + detailed_validation: bool = True, + unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity, + structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error( + None, t + ), + ) -> None: + """ + :param detailed_validation: Whether to use a slightly slower mode for detailed + validation errors. + :param unstructure_fallback_factory: A hook factory to be called when no + registered unstructuring hooks match. + :param structure_fallback_factory: A hook factory to be called when no + registered structuring hooks match. + + .. versionadded:: 23.2.0 *unstructure_fallback_factory* + .. versionadded:: 23.2.0 *structure_fallback_factory* + .. versionchanged:: 24.2.0 + The default `structure_fallback_factory` now raises errors for missing handlers + more eagerly, surfacing problems earlier. + """ + unstruct_strat = UnstructureStrategy(unstruct_strat) + self._prefer_attrib_converters = prefer_attrib_converters + + self.detailed_validation = detailed_validation + self._union_struct_registry: dict[Any, Callable[[Any, type[T]], T]] = {} + + # Create a per-instance cache. + if unstruct_strat is UnstructureStrategy.AS_DICT: + self._unstructure_attrs = self.unstructure_attrs_asdict + self._structure_attrs = self.structure_attrs_fromdict + else: + self._unstructure_attrs = self.unstructure_attrs_astuple + self._structure_attrs = self.structure_attrs_fromtuple + + self._unstructure_func = MultiStrategyDispatch( + unstructure_fallback_factory, self + ) + self._unstructure_func.register_cls_list( + [(bytes, identity), (str, identity), (Path, str)] + ) + self._unstructure_func.register_func_list( + [ + ( + is_protocol, + lambda o: self.unstructure(o, unstructure_as=o.__class__), + ), + ( + lambda t: get_final_base(t) is not None, + lambda t: self.get_unstructure_hook(get_final_base(t)), + True, + ), + ( + is_type_alias, + lambda t: self.get_unstructure_hook(get_type_alias_base(t)), + True, + ), + (is_literal_containing_enums, self.unstructure), + (is_mapping, self._unstructure_mapping), + (is_sequence, self._unstructure_seq), + (is_mutable_set, self._unstructure_seq), + (is_frozenset, self._unstructure_seq), + (lambda t: issubclass(t, Enum), self._unstructure_enum), + (has, self._unstructure_attrs), + (is_union_type, self._unstructure_union), + (lambda t: t in ANIES, self.unstructure), + ] + ) + + # Per-instance register of to-attrs converters. + # Singledispatch dispatches based on the first argument, so we + # store the function and switch the arguments in self.loads. + self._structure_func = MultiStrategyDispatch(structure_fallback_factory, self) + self._structure_func.register_func_list( + [ + ( + lambda cl: cl in ANIES or cl is Optional or cl is None, + lambda v, _: v, + ), + (is_generic_attrs, self._gen_structure_generic, True), + (lambda t: get_newtype_base(t) is not None, self._structure_newtype), + (is_type_alias, type_alias_structure_factory, "extended"), + ( + lambda t: get_final_base(t) is not None, + self._structure_final_factory, + True, + ), + (is_literal, self._structure_simple_literal), + (is_literal_containing_enums, self._structure_enum_literal), + (is_sequence, list_structure_factory, "extended"), + (is_deque, self._structure_deque), + (is_mutable_set, self._structure_set), + (is_frozenset, self._structure_frozenset), + (is_tuple, self._structure_tuple), + (is_namedtuple, namedtuple_structure_factory, "extended"), + (is_mapping, self._structure_dict), + (is_supported_union, self._gen_attrs_union_structure, True), + (is_optional, self._structure_optional), + ( + lambda t: is_union_type(t) and t in self._union_struct_registry, + self._union_struct_registry.__getitem__, + True, + ), + (has, self._structure_attrs), + ] + ) + # Strings are sequences. + self._structure_func.register_cls_list( + [ + (str, self._structure_call), + (bytes, self._structure_call), + (int, self._structure_call), + (float, self._structure_call), + (Enum, self._structure_call), + (Path, self._structure_call), + ] + ) + + self._dict_factory = dict_factory + + self._unstruct_copy_skip = self._unstructure_func.get_num_fns() + self._struct_copy_skip = self._structure_func.get_num_fns() + + def unstructure(self, obj: Any, unstructure_as: Any = None) -> Any: + return self._unstructure_func.dispatch( + obj.__class__ if unstructure_as is None else unstructure_as + )(obj) + + @property + def unstruct_strat(self) -> UnstructureStrategy: + """The default way of unstructuring ``attrs`` classes.""" + return ( + UnstructureStrategy.AS_DICT + if self._unstructure_attrs == self.unstructure_attrs_asdict + else UnstructureStrategy.AS_TUPLE + ) + + @overload + def register_unstructure_hook(self, cls: UnstructureHookT) -> UnstructureHookT: ... + + @overload + def register_unstructure_hook(self, cls: Any, func: UnstructureHook) -> None: ... + + def register_unstructure_hook( + self, cls: Any = None, func: UnstructureHook | None = None + ) -> Callable[[UnstructureHook]] | None: + """Register a class-to-primitive converter function for a class. + + The converter function should take an instance of the class and return + its Python equivalent. + + May also be used as a decorator. When used as a decorator, the first + argument annotation from the decorated function will be used as the + type to register the hook for. + + .. versionchanged:: 24.1.0 + This method may now be used as a decorator. + .. versionchanged:: 25.1.0 + Modern type aliases are now supported. + """ + if func is None: + # Autodetecting decorator. + func = cls + sig = signature(func) + cls = next(iter(sig.parameters.values())).annotation + self.register_unstructure_hook(cls, func) + + return func + + if attrs_has(cls): + resolve_types(cls) + if is_union_type(cls): + self._unstructure_func.register_func_list([(lambda t: t == cls, func)]) + elif is_type_alias(cls): + self._unstructure_func.register_func_list([(lambda t: t is cls, func)]) + elif get_newtype_base(cls) is not None: + # This is a newtype, so we handle it specially. + self._unstructure_func.register_func_list([(lambda t: t is cls, func)]) + else: + self._unstructure_func.register_cls_list([(cls, func)]) + return None + + def register_unstructure_hook_func( + self, check_func: Predicate, func: UnstructureHook + ) -> None: + """Register a class-to-primitive converter function for a class, using + a function to check if it's a match. + """ + self._unstructure_func.register_func_list([(check_func, func)]) + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate + ) -> Callable[[AnyUnstructureHookFactoryBase], AnyUnstructureHookFactoryBase]: ... + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate, factory: UnstructureHookFactory + ) -> UnstructureHookFactory: ... + + @overload + def register_unstructure_hook_factory( + self, + predicate: Predicate, + factory: ExtendedUnstructureHookFactory[BaseConverter], + ) -> ExtendedUnstructureHookFactory[BaseConverter]: ... + + def register_unstructure_hook_factory(self, predicate, factory=None): + """ + Register a hook factory for a given predicate. + + The hook factory may expose an additional required parameter. In this case, + the current converter will be provided to the hook factory as that + parameter. + + May also be used as a decorator. + + :param predicate: A function that, given a type, returns whether the factory + can produce a hook for that type. + :param factory: A callable that, given a type, produces an unstructuring + hook for that type. This unstructuring hook will be cached. + + .. versionchanged:: 24.1.0 + This method may now be used as a decorator. + The factory may also receive the converter as a second, required argument. + """ + if factory is None: + + def decorator(factory): + # Is this an extended factory (takes a converter too)? + if _is_extended_factory(factory): + self._unstructure_func.register_func_list( + [(predicate, factory, "extended")] + ) + else: + self._unstructure_func.register_func_list( + [(predicate, factory, True)] + ) + + return decorator + + self._unstructure_func.register_func_list( + [ + ( + predicate, + factory, + "extended" if _is_extended_factory(factory) else True, + ) + ] + ) + return factory + + def get_unstructure_hook( + self, type: Any, cache_result: bool = True + ) -> UnstructureHook: + """Get the unstructure hook for the given type. + + This hook can be manually called, or composed with other functions + and re-registered. + + If no hook is registered, the converter unstructure fallback factory + will be used to produce one. + + :param cache: Whether to cache the returned hook. + + .. versionadded:: 24.1.0 + """ + return ( + self._unstructure_func.dispatch(type) + if cache_result + else self._unstructure_func.dispatch_without_caching(type) + ) + + @overload + def register_structure_hook(self, cl: StructureHookT) -> StructureHookT: ... + + @overload + def register_structure_hook(self, cl: Any, func: StructureHook) -> None: ... + + def register_structure_hook( + self, cl: Any, func: StructureHook | None = None + ) -> None: + """Register a primitive-to-class converter function for a type. + + The converter function should take two arguments: + * a Python object to be converted, + * the type to convert to + + and return the instance of the class. The type may seem redundant, but + is sometimes needed (for example, when dealing with generic classes). + + This method may be used as a decorator. In this case, the decorated + hook must have a return type annotation, and this annotation will be used + as the type for the hook. + + .. versionchanged:: 24.1.0 + This method may now be used as a decorator. + .. versionchanged:: 25.1.0 + Modern type aliases are now supported. + """ + if func is None: + # The autodetecting decorator. + func = cl + sig = signature(func) + self.register_structure_hook(sig.return_annotation, func) + return func + + if attrs_has(cl): + resolve_types(cl) + if is_union_type(cl): + self._union_struct_registry[cl] = func + self._structure_func.clear_cache() + elif is_type_alias(cl): + # Type aliases are special-cased. + self._structure_func.register_func_list([(lambda t: t is cl, func)]) + elif get_newtype_base(cl) is not None: + # This is a newtype, so we handle it specially. + self._structure_func.register_func_list([(lambda t: t is cl, func)]) + else: + self._structure_func.register_cls_list([(cl, func)]) + return None + + def register_structure_hook_func( + self, check_func: Predicate, func: StructureHook + ) -> None: + """Register a class-to-primitive converter function for a class, using + a function to check if it's a match. + """ + self._structure_func.register_func_list([(check_func, func)]) + + @overload + def register_structure_hook_factory( + self, predicate: Predicate + ) -> Callable[[AnyStructureHookFactoryBase], AnyStructureHookFactoryBase]: ... + + @overload + def register_structure_hook_factory( + self, predicate: Predicate, factory: StructureHookFactory + ) -> StructureHookFactory: ... + + @overload + def register_structure_hook_factory( + self, predicate: Predicate, factory: ExtendedStructureHookFactory[BaseConverter] + ) -> ExtendedStructureHookFactory[BaseConverter]: ... + + def register_structure_hook_factory(self, predicate, factory=None): + """ + Register a hook factory for a given predicate. + + The hook factory may expose an additional required parameter. In this case, + the current converter will be provided to the hook factory as that + parameter. + + May also be used as a decorator. + + :param predicate: A function that, given a type, returns whether the factory + can produce a hook for that type. + :param factory: A callable that, given a type, produces a structuring + hook for that type. This structuring hook will be cached. + + .. versionchanged:: 24.1.0 + This method may now be used as a decorator. + The factory may also receive the converter as a second, required argument. + """ + if factory is None: + # Decorator use. + def decorator(factory): + # Is this an extended factory (takes a converter too)? + if _is_extended_factory(factory): + self._structure_func.register_func_list( + [(predicate, factory, "extended")] + ) + else: + self._structure_func.register_func_list( + [(predicate, factory, True)] + ) + + return decorator + self._structure_func.register_func_list( + [ + ( + predicate, + factory, + "extended" if _is_extended_factory(factory) else True, + ) + ] + ) + return factory + + def structure(self, obj: UnstructuredValue, cl: type[T]) -> T: + """Convert unstructured Python data structures to structured data.""" + return self._structure_func.dispatch(cl)(obj, cl) + + def get_structure_hook(self, type: Any, cache_result: bool = True) -> StructureHook: + """Get the structure hook for the given type. + + This hook can be manually called, or composed with other functions + and re-registered. + + If no hook is registered, the converter structure fallback factory + will be used to produce one. + + :param cache: Whether to cache the returned hook. + + .. versionadded:: 24.1.0 + """ + return ( + self._structure_func.dispatch(type) + if cache_result + else self._structure_func.dispatch_without_caching(type) + ) + + # Classes to Python primitives. + def unstructure_attrs_asdict(self, obj: Any) -> dict[str, Any]: + """Our version of `attrs.asdict`, so we can call back to us.""" + attrs = fields(obj.__class__) + dispatch = self._unstructure_func.dispatch + rv = self._dict_factory() + for a in attrs: + name = a.name + v = getattr(obj, name) + rv[name] = dispatch(a.type or v.__class__)(v) + return rv + + def unstructure_attrs_astuple(self, obj: Any) -> tuple[Any, ...]: + """Our version of `attrs.astuple`, so we can call back to us.""" + attrs = fields(obj.__class__) + dispatch = self._unstructure_func.dispatch + res = [] + for a in attrs: + name = a.name + v = getattr(obj, name) + res.append(dispatch(a.type or v.__class__)(v)) + return tuple(res) + + def _unstructure_enum(self, obj: Enum) -> Any: + """Convert an enum to its value.""" + return obj.value + + def _unstructure_seq(self, seq: Sequence[T]) -> Sequence[T]: + """Convert a sequence to primitive equivalents.""" + # We can reuse the sequence class, so tuples stay tuples. + dispatch = self._unstructure_func.dispatch + return seq.__class__(dispatch(e.__class__)(e) for e in seq) + + def _unstructure_mapping(self, mapping: Mapping[T, V]) -> Mapping[T, V]: + """Convert a mapping of attr classes to primitive equivalents.""" + + # We can reuse the mapping class, so dicts stay dicts and OrderedDicts + # stay OrderedDicts. + dispatch = self._unstructure_func.dispatch + return mapping.__class__( + (dispatch(k.__class__)(k), dispatch(v.__class__)(v)) + for k, v in mapping.items() + ) + + # note: Use UnionType when 3.11 is released as + # the behaviour of @final is changed. This would + # affect how we can support UnionType in ._compat.py + def _unstructure_union(self, obj: Any) -> Any: + """ + Unstructure an object as a union. + + By default, just unstructures the instance. + """ + return self._unstructure_func.dispatch(obj.__class__)(obj) + + # Python primitives to classes. + + def _gen_structure_generic( + self, cl: type[T] + ) -> SimpleStructureHook[Mapping[str, Any], T]: + """Create and return a hook for structuring generics.""" + return make_dict_structure_fn( + cl, self, _cattrs_prefer_attrib_converters=self._prefer_attrib_converters + ) + + def _gen_attrs_union_structure( + self, cl: Any, use_literals: bool = True + ) -> Callable[[Any, type[T]], type[T] | None]: + """ + Generate a structuring function for a union of attrs classes (and maybe None). + + :param use_literals: Whether to consider literal fields. + """ + dis_fn = self._get_dis_func(cl, use_literals=use_literals) + has_none = NoneType in cl.__args__ + + if has_none: + + def structure_attrs_union(obj, _) -> cl: + if obj is None: + return None + return self.structure(obj, dis_fn(obj)) + + else: + + def structure_attrs_union(obj, _): + return self.structure(obj, dis_fn(obj)) + + return structure_attrs_union + + @staticmethod + def _structure_call(obj: Any, cl: type[T]) -> Any: + """Just call ``cl`` with the given ``obj``. + + This is just an optimization on the ``_structure_default`` case, when + we know we can skip the ``if`` s. Use for ``str``, ``bytes``, ``enum``, + etc. + """ + return cl(obj) + + @staticmethod + def _structure_simple_literal(val, type): + if val not in type.__args__: + raise Exception(f"{val} not in literal {type}") + return val + + @staticmethod + def _structure_enum_literal(val, type): + vals = {(x.value if isinstance(x, Enum) else x): x for x in type.__args__} + try: + return vals[val] + except KeyError: + raise Exception(f"{val} not in literal {type}") from None + + def _structure_newtype(self, val: UnstructuredValue, type) -> StructuredValue: + base = get_newtype_base(type) + return self.get_structure_hook(base)(val, base) + + def _structure_final_factory(self, type): + base = get_final_base(type) + res = self.get_structure_hook(base) + return lambda v, _, __base=base: res(v, __base) + + # Attrs classes. + + def structure_attrs_fromtuple(self, obj: tuple[Any, ...], cl: type[T]) -> T: + """Load an attrs class from a sequence (tuple).""" + conv_obj = [] # A list of converter parameters. + for a, value in zip(fields(cl), obj): + # We detect the type by the metadata. + converted = self._structure_attribute(a, value) + conv_obj.append(converted) + + return cl(*conv_obj) + + def _structure_attribute(self, a: Attribute | Field, value: Any) -> Any: + """Handle an individual attrs attribute.""" + type_ = a.type + attrib_converter = getattr(a, "converter", None) + if self._prefer_attrib_converters and attrib_converter: + # A attrib converter is defined on this attribute, and + # prefer_attrib_converters is set to give these priority over registered + # structure hooks. So, pass through the raw value, which attrs will flow + # into the converter + return value + if type_ is None: + # No type metadata. + return value + + try: + return self._structure_func.dispatch(type_)(value, type_) + except StructureHandlerNotFoundError: + if attrib_converter: + # Return the original value and fallback to using an attrib converter. + return value + raise + + def structure_attrs_fromdict(self, obj: Mapping[str, Any], cl: type[T]) -> T: + """Instantiate an attrs class from a mapping (dict).""" + # For public use. + + conv_obj = {} # Start with a fresh dict, to ignore extra keys. + for a in fields(cl): + try: + val = obj[a.name] + except KeyError: + continue + + # try .alias and .name because this code also supports dataclasses! + conv_obj[getattr(a, "alias", a.name)] = self._structure_attribute(a, val) + + return cl(**conv_obj) + + def _structure_deque(self, obj: Iterable[T], cl: Any) -> deque[T]: + """Convert an iterable to a potentially generic deque.""" + if is_bare(cl) or cl.__args__[0] in ANIES: + res = deque(obj) + else: + elem_type = cl.__args__[0] + handler = self._structure_func.dispatch(elem_type) + if self.detailed_validation: + errors = [] + res = deque() + ix = 0 # Avoid `enumerate` for performance. + for e in obj: + try: + res.append(handler(e, elem_type)) + except Exception as e: + msg = IterableValidationNote( + f"Structuring {cl} @ index {ix}", ix, elem_type + ) + e.__notes__ = [*getattr(e, "__notes__", []), msg] + errors.append(e) + finally: + ix += 1 + if errors: + raise IterableValidationError( + f"While structuring {cl!r}", errors, cl + ) + else: + res = deque(handler(e, elem_type) for e in obj) + return res + + def _structure_set( + self, obj: Iterable[T], cl: Any, structure_to: type = set + ) -> Set[T]: + """Convert an iterable into a potentially generic set.""" + if is_bare(cl) or cl.__args__[0] in ANIES: + return structure_to(obj) + elem_type = cl.__args__[0] + handler = self._structure_func.dispatch(elem_type) + if self.detailed_validation: + errors = [] + res = set() + ix = 0 + for e in obj: + try: + res.add(handler(e, elem_type)) + except Exception as exc: + msg = IterableValidationNote( + f"Structuring {structure_to.__name__} @ element {e!r}", + ix, + elem_type, + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + finally: + ix += 1 + if errors: + raise IterableValidationError(f"While structuring {cl!r}", errors, cl) + return res if structure_to is set else structure_to(res) + if structure_to is set: + return {handler(e, elem_type) for e in obj} + return structure_to([handler(e, elem_type) for e in obj]) + + def _structure_frozenset( + self, obj: Iterable[T], cl: Any + ) -> FrozenSetSubscriptable[T]: + """Convert an iterable into a potentially generic frozenset.""" + return self._structure_set(obj, cl, structure_to=frozenset) + + def _structure_dict(self, obj: Mapping[T, V], cl: Any) -> dict[T, V]: + """Convert a mapping into a potentially generic dict.""" + if is_bare(cl) or cl.__args__ == (Any, Any): + return dict(obj) + key_type, val_type = cl.__args__ + + if self.detailed_validation: + key_handler = self._structure_func.dispatch(key_type) + val_handler = self._structure_func.dispatch(val_type) + errors = [] + res = {} + + for k, v in obj.items(): + try: + value = val_handler(v, val_type) + except Exception as exc: + msg = IterableValidationNote( + f"Structuring mapping value @ key {k!r}", k, val_type + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + continue + + try: + key = key_handler(k, key_type) + res[key] = value + except Exception as exc: + msg = IterableValidationNote( + f"Structuring mapping key @ key {k!r}", k, key_type + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + + if errors: + raise IterableValidationError(f"While structuring {cl!r}", errors, cl) + return res + + if key_type in ANIES: + val_conv = self._structure_func.dispatch(val_type) + return {k: val_conv(v, val_type) for k, v in obj.items()} + if val_type in ANIES: + key_conv = self._structure_func.dispatch(key_type) + return {key_conv(k, key_type): v for k, v in obj.items()} + key_conv = self._structure_func.dispatch(key_type) + val_conv = self._structure_func.dispatch(val_type) + return {key_conv(k, key_type): val_conv(v, val_type) for k, v in obj.items()} + + def _structure_optional(self, obj, union): + if obj is None: + return None + union_params = union.__args__ + other = union_params[0] if union_params[1] is NoneType else union_params[1] + # We can't actually have a Union of a Union, so this is safe. + return self._structure_func.dispatch(other)(obj, other) + + def _structure_tuple(self, obj: Iterable, tup: type[T]) -> T: + """Deal with structuring into a tuple.""" + tup_params = None if tup in (Tuple, tuple) else tup.__args__ + has_ellipsis = tup_params and tup_params[-1] is Ellipsis + if tup_params is None or (has_ellipsis and tup_params[0] in ANIES): + # Just a Tuple. (No generic information.) + return tuple(obj) + if has_ellipsis: + # We're dealing with a homogenous tuple, tuple[int, ...] + tup_type = tup_params[0] + conv = self._structure_func.dispatch(tup_type) + if self.detailed_validation: + errors = [] + res = [] + ix = 0 + for e in obj: + try: + res.append(conv(e, tup_type)) + except Exception as exc: + msg = IterableValidationNote( + f"Structuring {tup} @ index {ix}", ix, tup_type + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + finally: + ix += 1 + if errors: + raise IterableValidationError( + f"While structuring {tup!r}", errors, tup + ) + return tuple(res) + return tuple(conv(e, tup_type) for e in obj) + + # We're dealing with a heterogenous tuple. + exp_len = len(tup_params) + if self.detailed_validation: + errors = [] + res = [] + for ix, (t, e) in enumerate(zip(tup_params, obj)): + try: + conv = self._structure_func.dispatch(t) + res.append(conv(e, t)) + except Exception as exc: + msg = IterableValidationNote( + f"Structuring {tup} @ index {ix}", ix, t + ) + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + if len(obj) != exp_len: + problem = "Not enough" if len(res) < exp_len else "Too many" + exc = ValueError(f"{problem} values in {obj!r} to structure as {tup!r}") + msg = f"Structuring {tup}" + exc.__notes__ = [*getattr(exc, "__notes__", []), msg] + errors.append(exc) + if errors: + raise IterableValidationError(f"While structuring {tup!r}", errors, tup) + return tuple(res) + + if len(obj) != exp_len: + problem = "Not enough" if len(obj) < len(tup_params) else "Too many" + raise ValueError(f"{problem} values in {obj!r} to structure as {tup!r}") + return tuple( + [self._structure_func.dispatch(t)(e, t) for t, e in zip(tup_params, obj)] + ) + + def _get_dis_func( + self, + union: Any, + use_literals: bool = True, + overrides: dict[str, AttributeOverride] | None = None, + ) -> Callable[[Any], type]: + """Fetch or try creating a disambiguation function for a union.""" + union_types = union.__args__ + if NoneType in union_types: + # We support unions of attrs classes and NoneType higher in the + # logic. + union_types = tuple(e for e in union_types if e is not NoneType) + + if not all(has(get_origin(e) or e) for e in union_types): + raise StructureHandlerNotFoundError( + "Only unions of attrs classes and dataclasses supported " + "currently. Register a structure hook manually.", + type_=union, + ) + + return create_default_dis_func( + self, + *union_types, + use_literals=use_literals, + overrides=overrides if overrides is not None else "from_converter", + ) + + def __deepcopy__(self, _) -> BaseConverter: + return self.copy() + + def copy( + self, + dict_factory: Callable[[], Any] | None = None, + unstruct_strat: UnstructureStrategy | None = None, + prefer_attrib_converters: bool | None = None, + detailed_validation: bool | None = None, + ) -> Self: + """Create a copy of the converter, keeping all existing custom hooks. + + :param detailed_validation: Whether to use a slightly slower mode for detailed + validation errors. + """ + res = self.__class__( + dict_factory if dict_factory is not None else self._dict_factory, + ( + unstruct_strat + if unstruct_strat is not None + else ( + UnstructureStrategy.AS_DICT + if self._unstructure_attrs == self.unstructure_attrs_asdict + else UnstructureStrategy.AS_TUPLE + ) + ), + ( + prefer_attrib_converters + if prefer_attrib_converters is not None + else self._prefer_attrib_converters + ), + ( + detailed_validation + if detailed_validation is not None + else self.detailed_validation + ), + ) + + self._unstructure_func.copy_to(res._unstructure_func, self._unstruct_copy_skip) + self._structure_func.copy_to(res._structure_func, self._struct_copy_skip) + + return res + + +class Converter(BaseConverter): + """A converter which generates specialized un/structuring functions.""" + + __slots__ = ( + "_unstruct_collection_overrides", + "forbid_extra_keys", + "omit_if_default", + "type_overrides", + ) + + def __init__( + self, + dict_factory: Callable[[], Any] = dict, + unstruct_strat: UnstructureStrategy = UnstructureStrategy.AS_DICT, + omit_if_default: bool = False, + forbid_extra_keys: bool = False, + type_overrides: Mapping[type, AttributeOverride] = {}, + unstruct_collection_overrides: Mapping[type, UnstructureHook] = {}, + prefer_attrib_converters: bool = False, + detailed_validation: bool = True, + unstructure_fallback_factory: HookFactory[UnstructureHook] = lambda _: identity, + structure_fallback_factory: HookFactory[StructureHook] = lambda t: raise_error( + None, t + ), + ): + """ + :param detailed_validation: Whether to use a slightly slower mode for detailed + validation errors. + :param unstructure_fallback_factory: A hook factory to be called when no + registered unstructuring hooks match. + :param structure_fallback_factory: A hook factory to be called when no + registered structuring hooks match. + + .. versionadded:: 23.2.0 *unstructure_fallback_factory* + .. versionadded:: 23.2.0 *structure_fallback_factory* + .. versionchanged:: 24.2.0 + The default `structure_fallback_factory` now raises errors for missing handlers + more eagerly, surfacing problems earlier. + """ + super().__init__( + dict_factory=dict_factory, + unstruct_strat=unstruct_strat, + prefer_attrib_converters=prefer_attrib_converters, + detailed_validation=detailed_validation, + unstructure_fallback_factory=unstructure_fallback_factory, + structure_fallback_factory=structure_fallback_factory, + ) + self.omit_if_default = omit_if_default + self.forbid_extra_keys = forbid_extra_keys + self.type_overrides = dict(type_overrides) + + unstruct_collection_overrides = { + get_origin(k) or k: v for k, v in unstruct_collection_overrides.items() + } + + self._unstruct_collection_overrides = unstruct_collection_overrides + + # Do a little post-processing magic to make things easier for users. + co = unstruct_collection_overrides + + # abc.Set overrides, if defined, apply to abc.MutableSets and sets + if OriginAbstractSet in co: + if OriginMutableSet not in co: + co[OriginMutableSet] = co[OriginAbstractSet] + if FrozenSetSubscriptable not in co: + co[FrozenSetSubscriptable] = co[OriginAbstractSet] + + # abc.MutableSet overrrides, if defined, apply to sets + if OriginMutableSet in co and set not in co: + co[set] = co[OriginMutableSet] + + # abc.Sequence overrides, if defined, can apply to MutableSequences, lists and + # tuples + if Sequence in co: + if MutableSequence not in co: + co[MutableSequence] = co[Sequence] + if tuple not in co: + co[tuple] = co[Sequence] + + # abc.MutableSequence overrides, if defined, can apply to lists + if MutableSequence in co: + if list not in co: + co[list] = co[MutableSequence] + if deque not in co: + co[deque] = co[MutableSequence] + + # abc.Mapping overrides, if defined, can apply to MutableMappings + if Mapping in co and MutableMapping not in co: + co[MutableMapping] = co[Mapping] + + # abc.MutableMapping overrides, if defined, can apply to dicts + if MutableMapping in co and dict not in co: + co[dict] = co[MutableMapping] + + # builtins.dict overrides, if defined, can apply to counters + if dict in co and Counter not in co: + co[Counter] = co[dict] + + if unstruct_strat is UnstructureStrategy.AS_DICT: + # Override the attrs handler. + self.register_unstructure_hook_factory( + has_with_generic, self.gen_unstructure_attrs_fromdict + ) + self.register_structure_hook_factory( + has_with_generic, self.gen_structure_attrs_fromdict + ) + self.register_unstructure_hook_factory( + is_annotated, self.gen_unstructure_annotated + ) + self.register_unstructure_hook_factory( + is_hetero_tuple, self.gen_unstructure_hetero_tuple + ) + self.register_unstructure_hook_factory(is_namedtuple)( + namedtuple_unstructure_factory + ) + self.register_unstructure_hook_factory( + is_sequence, self.gen_unstructure_iterable + ) + self.register_unstructure_hook_factory(is_mapping, self.gen_unstructure_mapping) + self.register_unstructure_hook_factory( + is_mutable_set, + lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=set), + ) + self.register_unstructure_hook_factory( + is_frozenset, + lambda cl: self.gen_unstructure_iterable(cl, unstructure_to=frozenset), + ) + self.register_unstructure_hook_factory( + is_optional, self.gen_unstructure_optional + ) + self.register_unstructure_hook_factory( + is_typeddict, self.gen_unstructure_typeddict + ) + self.register_unstructure_hook_factory( + lambda t: get_newtype_base(t) is not None, + lambda t: self.get_unstructure_hook(get_newtype_base(t)), + ) + + self.register_structure_hook_factory(is_annotated, self.gen_structure_annotated) + self.register_structure_hook_factory(is_mapping, self.gen_structure_mapping) + self.register_structure_hook_factory(is_counter, self.gen_structure_counter) + self.register_structure_hook_factory( + is_defaultdict, defaultdict_structure_factory + ) + self.register_structure_hook_factory(is_typeddict, self.gen_structure_typeddict) + self.register_structure_hook_factory( + lambda t: get_newtype_base(t) is not None, self.get_structure_newtype + ) + + # We keep these so we can more correctly copy the hooks. + self._struct_copy_skip = self._structure_func.get_num_fns() + self._unstruct_copy_skip = self._unstructure_func.get_num_fns() + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate + ) -> Callable[[AnyUnstructureHookFactory], AnyUnstructureHookFactory]: ... + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate, factory: UnstructureHookFactory + ) -> UnstructureHookFactory: ... + + @overload + def register_unstructure_hook_factory( + self, predicate: Predicate, factory: ExtendedUnstructureHookFactory[Converter] + ) -> ExtendedUnstructureHookFactory[Converter]: ... + + def register_unstructure_hook_factory(self, predicate, factory=None): + # This dummy wrapper is required due to how `@overload` works. + return super().register_unstructure_hook_factory(predicate, factory) + + @overload + def register_structure_hook_factory( + self, predicate: Predicate + ) -> Callable[[AnyStructureHookFactory], AnyStructureHookFactory]: ... + + @overload + def register_structure_hook_factory( + self, predicate: Predicate, factory: StructureHookFactory + ) -> StructureHookFactory: ... + + @overload + def register_structure_hook_factory( + self, predicate: Predicate, factory: ExtendedStructureHookFactory[Converter] + ) -> ExtendedStructureHookFactory[Converter]: ... + + def register_structure_hook_factory(self, predicate, factory=None): + # This dummy wrapper is required due to how `@overload` works. + return super().register_structure_hook_factory(predicate, factory) + + def get_structure_newtype(self, type: type[T]) -> Callable[[Any, Any], T]: + base = get_newtype_base(type) + handler = self.get_structure_hook(base) + return lambda v, _: handler(v, base) + + def gen_unstructure_annotated(self, type): + origin = type.__origin__ + return self.get_unstructure_hook(origin) + + def gen_structure_annotated(self, type) -> Callable: + """A hook factory for annotated types.""" + origin = type.__origin__ + hook = self.get_structure_hook(origin) + return lambda v, _: hook(v, origin) + + def gen_unstructure_typeddict(self, cl: Any) -> Callable[[dict], dict]: + """Generate a TypedDict unstructure function. + + Also apply converter-scored modifications. + """ + return make_typeddict_dict_unstruct_fn(cl, self) + + def gen_unstructure_attrs_fromdict( + self, cl: type[T] + ) -> Callable[[T], dict[str, Any]]: + origin = get_origin(cl) + attribs = fields(origin or cl) + if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs): + # PEP 563 annotations - need to be resolved. + resolve_types(cl) + attrib_overrides = { + a.name: self.type_overrides[a.type] + for a in attribs + if a.type in self.type_overrides + } + + return make_dict_unstructure_fn( + cl, self, _cattrs_omit_if_default=self.omit_if_default, **attrib_overrides + ) + + def gen_unstructure_optional(self, cl: type[T]) -> Callable[[T], Any]: + """Generate an unstructuring hook for optional types.""" + union_params = cl.__args__ + other = union_params[0] if union_params[1] is NoneType else union_params[1] + + if isinstance(other, TypeVar): + handler = self.unstructure + else: + handler = self.get_unstructure_hook(other) + + def unstructure_optional(val, _handler=handler): + return None if val is None else _handler(val) + + return unstructure_optional + + def gen_structure_typeddict(self, cl: Any) -> Callable[[dict, Any], dict]: + """Generate a TypedDict structure function. + + Also apply converter-scored modifications. + """ + return make_typeddict_dict_struct_fn( + cl, self, _cattrs_detailed_validation=self.detailed_validation + ) + + def gen_structure_attrs_fromdict( + self, cl: type[T] + ) -> Callable[[Mapping[str, Any], Any], T]: + attribs = fields(get_origin(cl) or cl if is_generic(cl) else cl) + if attrs_has(cl) and any(isinstance(a.type, str) for a in attribs): + # PEP 563 annotations - need to be resolved. + resolve_types(cl) + attrib_overrides = { + a.name: self.type_overrides[a.type] + for a in attribs + if a.type in self.type_overrides + } + return make_dict_structure_fn( + cl, + self, + _cattrs_forbid_extra_keys=self.forbid_extra_keys, + _cattrs_prefer_attrib_converters=self._prefer_attrib_converters, + _cattrs_detailed_validation=self.detailed_validation, + **attrib_overrides, + ) + + def gen_unstructure_iterable( + self, cl: Any, unstructure_to: Any = None + ) -> IterableUnstructureFn: + unstructure_to = self._unstruct_collection_overrides.get( + get_origin(cl) or cl, unstructure_to or list + ) + h = iterable_unstructure_factory(cl, self, unstructure_to=unstructure_to) + self._unstructure_func.register_cls_list([(cl, h)], direct=True) + return h + + def gen_unstructure_hetero_tuple( + self, cl: Any, unstructure_to: Any = None + ) -> HeteroTupleUnstructureFn: + unstructure_to = self._unstruct_collection_overrides.get( + get_origin(cl) or cl, unstructure_to or tuple + ) + h = make_hetero_tuple_unstructure_fn(cl, self, unstructure_to=unstructure_to) + self._unstructure_func.register_cls_list([(cl, h)], direct=True) + return h + + def gen_unstructure_mapping( + self, + cl: Any, + unstructure_to: Any = None, + key_handler: Callable[[Any, Any | None], Any] | None = None, + ) -> MappingUnstructureFn: + unstructure_to = self._unstruct_collection_overrides.get( + get_origin(cl) or cl, unstructure_to or dict + ) + h = mapping_unstructure_factory( + cl, self, unstructure_to=unstructure_to, key_handler=key_handler + ) + self._unstructure_func.register_cls_list([(cl, h)], direct=True) + return h + + def gen_structure_counter( + self, cl: type[CounterT] + ) -> SimpleStructureHook[Mapping[Any, Any], CounterT]: + h = mapping_structure_factory( + cl, + self, + structure_to=Counter, + val_type=int, + detailed_validation=self.detailed_validation, + ) + self._structure_func.register_cls_list([(cl, h)], direct=True) + return h + + def gen_structure_mapping( + self, cl: Any + ) -> SimpleStructureHook[Mapping[Any, Any], Any]: + structure_to = get_origin(cl) or cl + if structure_to in ( + MutableMapping, + AbcMutableMapping, + Mapping, + AbcMapping, + ): # These default to dicts + structure_to = dict + h = mapping_structure_factory( + cl, self, structure_to, detailed_validation=self.detailed_validation + ) + self._structure_func.register_cls_list([(cl, h)], direct=True) + return h + + def copy( + self, + dict_factory: Callable[[], Any] | None = None, + unstruct_strat: UnstructureStrategy | None = None, + omit_if_default: bool | None = None, + forbid_extra_keys: bool | None = None, + type_overrides: Mapping[type, AttributeOverride] | None = None, + unstruct_collection_overrides: Mapping[type, UnstructureHook] | None = None, + prefer_attrib_converters: bool | None = None, + detailed_validation: bool | None = None, + ) -> Self: + """Create a copy of the converter, keeping all existing custom hooks. + + :param detailed_validation: Whether to use a slightly slower mode for detailed + validation errors. + """ + res = self.__class__( + dict_factory if dict_factory is not None else self._dict_factory, + ( + unstruct_strat + if unstruct_strat is not None + else ( + UnstructureStrategy.AS_DICT + if self._unstructure_attrs == self.unstructure_attrs_asdict + else UnstructureStrategy.AS_TUPLE + ) + ), + omit_if_default if omit_if_default is not None else self.omit_if_default, + ( + forbid_extra_keys + if forbid_extra_keys is not None + else self.forbid_extra_keys + ), + type_overrides if type_overrides is not None else self.type_overrides, + ( + unstruct_collection_overrides + if unstruct_collection_overrides is not None + else self._unstruct_collection_overrides + ), + ( + prefer_attrib_converters + if prefer_attrib_converters is not None + else self._prefer_attrib_converters + ), + ( + detailed_validation + if detailed_validation is not None + else self.detailed_validation + ), + ) + + self._unstructure_func.copy_to( + res._unstructure_func, skip=self._unstruct_copy_skip + ) + self._structure_func.copy_to(res._structure_func, skip=self._struct_copy_skip) + + return res + + +GenConverter: TypeAlias = Converter diff --git a/server/libs/cattrs/disambiguators.py b/server/libs/cattrs/disambiguators.py new file mode 100644 index 0000000..6fc5d9d --- /dev/null +++ b/server/libs/cattrs/disambiguators.py @@ -0,0 +1,206 @@ +"""Utilities for union (sum type) disambiguation.""" + +from __future__ import annotations + +from collections import defaultdict +from collections.abc import Mapping +from dataclasses import MISSING +from functools import reduce +from operator import or_ +from typing import TYPE_CHECKING, Any, Callable, Literal, Union + +from attrs import NOTHING, Attribute, AttrsInstance + +from ._compat import ( + NoneType, + adapted_fields, + fields_dict, + get_args, + get_origin, + has, + is_literal, + is_union_type, +) +from .gen import AttributeOverride + +if TYPE_CHECKING: + from .converters import BaseConverter + +__all__ = ["create_default_dis_func", "is_supported_union"] + + +def is_supported_union(typ: Any) -> bool: + """Whether the type is a union of attrs classes or dataclasses.""" + return is_union_type(typ) and all( + e is NoneType or has(get_origin(e) or e) for e in typ.__args__ + ) + + +def create_default_dis_func( + converter: BaseConverter, + *classes: type[AttrsInstance], + use_literals: bool = True, + overrides: ( + dict[str, AttributeOverride] | Literal["from_converter"] + ) = "from_converter", +) -> Callable[[Mapping[Any, Any]], type[Any] | None]: + """Given attrs classes or dataclasses, generate a disambiguation function. + + The function is based on unique fields without defaults or unique values. + + :param use_literals: Whether to try using fields annotated as literals for + disambiguation. + :param overrides: Attribute overrides to apply. + + .. versionchanged:: 24.1.0 + Dataclasses are now supported. + """ + if len(classes) < 2: + raise ValueError("At least two classes required.") + + if overrides == "from_converter": + overrides = [ + getattr(converter.get_structure_hook(c), "overrides", {}) for c in classes + ] + else: + overrides = [overrides for _ in classes] + + # first, attempt for unique values + if use_literals: + # requirements for a discriminator field: + # (... TODO: a single fallback is OK) + # - it must always be enumerated + cls_candidates = [ + { + at.name + for at in adapted_fields(get_origin(cl) or cl) + if is_literal(at.type) + } + for cl in classes + ] + + # literal field names common to all members + discriminators: set[str] = cls_candidates[0] + for possible_discriminators in cls_candidates: + discriminators &= possible_discriminators + + best_result = None + best_discriminator = None + for discriminator in discriminators: + # maps Literal values (strings, ints...) to classes + mapping = defaultdict(list) + + for cl in classes: + for key in get_args( + fields_dict(get_origin(cl) or cl)[discriminator].type + ): + mapping[key].append(cl) + + if best_result is None or max(len(v) for v in mapping.values()) <= max( + len(v) for v in best_result.values() + ): + best_result = mapping + best_discriminator = discriminator + + if ( + best_result + and best_discriminator + and max(len(v) for v in best_result.values()) != len(classes) + ): + final_mapping = { + k: v[0] if len(v) == 1 else Union[tuple(v)] + for k, v in best_result.items() + } + + def dis_func(data: Mapping[Any, Any]) -> type | None: + if not isinstance(data, Mapping): + raise ValueError("Only input mappings are supported.") + return final_mapping[data[best_discriminator]] + + return dis_func + + # next, attempt for unique keys + + # NOTE: This could just as well work with just field availability and not + # uniqueness, returning Unions ... it doesn't do that right now. + cls_and_attrs = [ + (cl, *_usable_attribute_names(cl, override)) + for cl, override in zip(classes, overrides) + ] + # For each class, attempt to generate a single unique required field. + uniq_attrs_dict: dict[str, type] = {} + + # We start from classes with the largest number of unique fields + # so we can do easy picks first, making later picks easier. + cls_and_attrs.sort(key=lambda c_a: len(c_a[1]), reverse=True) + + fallback = None # If none match, try this. + + for cl, cl_reqs, back_map in cls_and_attrs: + # We do not have to consider classes we've already processed, since + # they will have been eliminated by the match dictionary already. + other_classes = [ + c_and_a + for c_and_a in cls_and_attrs + if c_and_a[0] is not cl and c_and_a[0] not in uniq_attrs_dict.values() + ] + other_reqs = reduce(or_, (c_a[1] for c_a in other_classes), set()) + uniq = cl_reqs - other_reqs + + # We want a unique attribute with no default. + cl_fields = fields_dict(get_origin(cl) or cl) + for maybe_renamed_attr_name in uniq: + orig_name = back_map[maybe_renamed_attr_name] + if cl_fields[orig_name].default in (NOTHING, MISSING): + break + else: + if fallback is None: + fallback = cl + continue + raise TypeError(f"{cl} has no usable non-default attributes") + uniq_attrs_dict[maybe_renamed_attr_name] = cl + + if fallback is None: + + def dis_func(data: Mapping[Any, Any]) -> type[AttrsInstance] | None: + if not isinstance(data, Mapping): + raise ValueError("Only input mappings are supported") + for k, v in uniq_attrs_dict.items(): + if k in data: + return v + raise ValueError("Couldn't disambiguate") + + else: + + def dis_func(data: Mapping[Any, Any]) -> type[AttrsInstance] | None: + if not isinstance(data, Mapping): + raise ValueError("Only input mappings are supported") + for k, v in uniq_attrs_dict.items(): + if k in data: + return v + return fallback + + return dis_func + + +create_uniq_field_dis_func = create_default_dis_func + + +def _overriden_name(at: Attribute, override: AttributeOverride | None) -> str: + if override is None or override.rename is None: + return at.name + return override.rename + + +def _usable_attribute_names( + cl: type[Any], overrides: dict[str, AttributeOverride] +) -> tuple[set[str], dict[str, str]]: + """Return renamed fields and a mapping to original field names.""" + res = set() + mapping = {} + + for at in adapted_fields(get_origin(cl) or cl): + res.add(n := _overriden_name(at, overrides.get(at.name))) + mapping[n] = at.name + + return res, mapping diff --git a/server/libs/cattrs/dispatch.py b/server/libs/cattrs/dispatch.py new file mode 100644 index 0000000..f98dc51 --- /dev/null +++ b/server/libs/cattrs/dispatch.py @@ -0,0 +1,193 @@ +from __future__ import annotations + +from functools import lru_cache, singledispatch +from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar + +from attrs import Factory, define + +from ._compat import TypeAlias +from .fns import Predicate + +if TYPE_CHECKING: + from .converters import BaseConverter + +TargetType: TypeAlias = Any +UnstructuredValue: TypeAlias = Any +StructuredValue: TypeAlias = Any + +StructureHook: TypeAlias = Callable[[UnstructuredValue, TargetType], StructuredValue] +UnstructureHook: TypeAlias = Callable[[StructuredValue], UnstructuredValue] + +Hook = TypeVar("Hook", StructureHook, UnstructureHook) +HookFactory: TypeAlias = Callable[[TargetType], Hook] + + +@define +class _DispatchNotFound: + """A dummy object to help signify a dispatch not found.""" + + +@define +class FunctionDispatch: + """ + FunctionDispatch is similar to functools.singledispatch, but + instead dispatches based on functions that take the type of the + first argument in the method, and return True or False. + + objects that help determine dispatch should be instantiated objects. + + :param converter: A converter to be used for factories that require converters. + + .. versionchanged:: 24.1.0 + Support for factories that require converters, hence this requires a + converter when creating. + """ + + _converter: BaseConverter + _handler_pairs: list[tuple[Predicate, Callable[[Any, Any], Any], bool, bool]] = ( + Factory(list) + ) + + def register( + self, + predicate: Predicate, + func: Callable[..., Any], + is_generator=False, + takes_converter=False, + ) -> None: + self._handler_pairs.insert(0, (predicate, func, is_generator, takes_converter)) + + def dispatch(self, typ: Any) -> Callable[..., Any] | None: + """ + Return the appropriate handler for the object passed. + """ + for can_handle, handler, is_generator, takes_converter in self._handler_pairs: + # can handle could raise an exception here + # such as issubclass being called on an instance. + # it's easier to just ignore that case. + try: + ch = can_handle(typ) + except Exception: # noqa: S112 + continue + if ch: + if is_generator: + if takes_converter: + return handler(typ, self._converter) + return handler(typ) + + return handler + return None + + def get_num_fns(self) -> int: + return len(self._handler_pairs) + + def copy_to(self, other: FunctionDispatch, skip: int = 0) -> None: + other._handler_pairs = self._handler_pairs[:-skip] + other._handler_pairs + + +@define(init=False) +class MultiStrategyDispatch(Generic[Hook]): + """ + MultiStrategyDispatch uses a combination of exact-match dispatch, + singledispatch, and FunctionDispatch. + + :param fallback_factory: A hook factory to be called when a hook cannot be + produced. + :param converter: A converter to be used for factories that require converters. + + .. versionchanged:: 23.2.0 + Fallbacks are now factories. + .. versionchanged:: 24.1.0 + Support for factories that require converters, hence this requires a + converter when creating. + """ + + _fallback_factory: HookFactory[Hook] + _direct_dispatch: dict[TargetType, Hook] + _function_dispatch: FunctionDispatch + _single_dispatch: Any + dispatch: Callable[[TargetType, BaseConverter], Hook] + + def __init__( + self, fallback_factory: HookFactory[Hook], converter: BaseConverter + ) -> None: + self._fallback_factory = fallback_factory + self._direct_dispatch = {} + self._function_dispatch = FunctionDispatch(converter) + self._single_dispatch = singledispatch(_DispatchNotFound) + self.dispatch = lru_cache(maxsize=None)(self.dispatch_without_caching) + + def dispatch_without_caching(self, typ: TargetType) -> Hook: + """Dispatch on the type but without caching the result.""" + try: + dispatch = self._single_dispatch.dispatch(typ) + if dispatch is not _DispatchNotFound: + return dispatch + except Exception: # noqa: S110 + pass + + direct_dispatch = self._direct_dispatch.get(typ) + if direct_dispatch is not None: + return direct_dispatch + + res = self._function_dispatch.dispatch(typ) + return res if res is not None else self._fallback_factory(typ) + + def register_cls_list(self, cls_and_handler, direct: bool = False) -> None: + """Register a class to direct or singledispatch.""" + for cls, handler in cls_and_handler: + if direct: + self._direct_dispatch[cls] = handler + else: + self._single_dispatch.register(cls, handler) + self.clear_direct() + self.dispatch.cache_clear() + + def register_func_list( + self, + pred_and_handler: list[ + tuple[Predicate, Any] + | tuple[Predicate, Any, bool] + | tuple[Predicate, Callable[[Any, BaseConverter], Any], Literal["extended"]] + ], + ): + """ + Register a predicate function to determine if the handler + should be used for the type. + + :param pred_and_handler: The list of predicates and their associated + handlers. If a handler is registered in `extended` mode, it's a + factory that requires a converter. + """ + for tup in pred_and_handler: + if len(tup) == 2: + func, handler = tup + self._function_dispatch.register(func, handler) + else: + func, handler, is_gen = tup + if is_gen == "extended": + self._function_dispatch.register( + func, handler, is_generator=is_gen, takes_converter=True + ) + else: + self._function_dispatch.register(func, handler, is_generator=is_gen) + self.clear_direct() + self.dispatch.cache_clear() + + def clear_direct(self) -> None: + """Clear the direct dispatch.""" + self._direct_dispatch.clear() + + def clear_cache(self) -> None: + """Clear all caches.""" + self._direct_dispatch.clear() + self.dispatch.cache_clear() + + def get_num_fns(self) -> int: + return self._function_dispatch.get_num_fns() + + def copy_to(self, other: MultiStrategyDispatch, skip: int = 0) -> None: + self._function_dispatch.copy_to(other._function_dispatch, skip=skip) + for cls, fn in self._single_dispatch.registry.items(): + other._single_dispatch.register(cls, fn) + other.clear_cache() diff --git a/server/libs/cattrs/errors.py b/server/libs/cattrs/errors.py new file mode 100644 index 0000000..4f9a737 --- /dev/null +++ b/server/libs/cattrs/errors.py @@ -0,0 +1,132 @@ +from collections.abc import Sequence +from typing import Any, Optional, Union + +from typing_extensions import Self + +from cattrs._compat import ExceptionGroup + + +class StructureHandlerNotFoundError(Exception): + """ + Error raised when structuring cannot find a handler for converting inputs into + :attr:`type_`. + """ + + def __init__(self, message: str, type_: type) -> None: + super().__init__(message) + self.type_ = type_ + + +class BaseValidationError(ExceptionGroup): + cl: type + + def __new__(cls, message: str, excs: Sequence[Exception], cl: type): + obj = super().__new__(cls, message, excs) + obj.cl = cl + return obj + + def derive(self, excs: Sequence[Exception]) -> Self: + return self.__class__(self.message, excs, self.cl) + + +class IterableValidationNote(str): + """Attached as a note to an exception when an iterable element fails structuring.""" + + index: Union[int, str] # Ints for list indices, strs for dict keys + type: Any + + def __new__( + cls, string: str, index: Union[int, str], type: Any + ) -> "IterableValidationNote": + instance = str.__new__(cls, string) + instance.index = index + instance.type = type + return instance + + def __getnewargs__(self) -> tuple[str, Union[int, str], Any]: + return (str(self), self.index, self.type) + + +class IterableValidationError(BaseValidationError): + """Raised when structuring an iterable.""" + + def group_exceptions( + self, + ) -> tuple[list[tuple[Exception, IterableValidationNote]], list[Exception]]: + """Split the exceptions into two groups: with and without validation notes.""" + excs_with_notes = [] + other_excs = [] + for subexc in self.exceptions: + if hasattr(subexc, "__notes__"): + for note in subexc.__notes__: + if note.__class__ is IterableValidationNote: + excs_with_notes.append((subexc, note)) + break + else: + other_excs.append(subexc) + else: + other_excs.append(subexc) + + return excs_with_notes, other_excs + + +class AttributeValidationNote(str): + """Attached as a note to an exception when an attribute fails structuring.""" + + name: str + type: Any + + def __new__(cls, string: str, name: str, type: Any) -> "AttributeValidationNote": + instance = str.__new__(cls, string) + instance.name = name + instance.type = type + return instance + + def __getnewargs__(self) -> tuple[str, str, Any]: + return (str(self), self.name, self.type) + + +class ClassValidationError(BaseValidationError): + """Raised when validating a class if any attributes are invalid.""" + + def group_exceptions( + self, + ) -> tuple[list[tuple[Exception, AttributeValidationNote]], list[Exception]]: + """Split the exceptions into two groups: with and without validation notes.""" + excs_with_notes = [] + other_excs = [] + for subexc in self.exceptions: + if hasattr(subexc, "__notes__"): + for note in subexc.__notes__: + if note.__class__ is AttributeValidationNote: + excs_with_notes.append((subexc, note)) + break + else: + other_excs.append(subexc) + else: + other_excs.append(subexc) + + return excs_with_notes, other_excs + + +class ForbiddenExtraKeysError(Exception): + """ + Raised when `forbid_extra_keys` is activated and such extra keys are detected + during structuring. + + The attribute `extra_fields` is a sequence of those extra keys, which were the + cause of this error, and `cl` is the class which was structured with those extra + keys. + """ + + def __init__( + self, message: Optional[str], cl: type, extra_fields: set[str] + ) -> None: + self.cl = cl + self.extra_fields = extra_fields + cln = cl.__name__ + + super().__init__( + message + or f"Extra fields in constructor for {cln}: {', '.join(extra_fields)}" + ) diff --git a/server/libs/cattrs/fns.py b/server/libs/cattrs/fns.py new file mode 100644 index 0000000..984c05e --- /dev/null +++ b/server/libs/cattrs/fns.py @@ -0,0 +1,22 @@ +"""Useful internal functions.""" + +from typing import Any, Callable, NoReturn, TypeVar + +from ._compat import TypeAlias +from .errors import StructureHandlerNotFoundError + +T = TypeVar("T") + +Predicate: TypeAlias = Callable[[Any], bool] +"""A predicate function determines if a type can be handled.""" + + +def identity(obj: T) -> T: + """The identity function.""" + return obj + + +def raise_error(_, cl: Any) -> NoReturn: + """At the bottom of the condition stack, we explode if we can't handle it.""" + msg = f"Unsupported type: {cl!r}. Register a structure hook for it." + raise StructureHandlerNotFoundError(msg, type_=cl) diff --git a/server/libs/cattrs/gen/__init__.py b/server/libs/cattrs/gen/__init__.py new file mode 100644 index 0000000..3afa3b9 --- /dev/null +++ b/server/libs/cattrs/gen/__init__.py @@ -0,0 +1,1048 @@ +from __future__ import annotations + +import re +from collections.abc import Callable, Iterable, Mapping +from typing import TYPE_CHECKING, Any, Final, Literal, TypeVar + +from attrs import NOTHING, Attribute, Factory +from typing_extensions import NoDefault + +from .._compat import ( + ANIES, + TypeAlias, + adapted_fields, + get_args, + get_origin, + is_annotated, + is_bare, + is_bare_final, + is_generic, +) +from .._generics import deep_copy_with +from ..dispatch import UnstructureHook +from ..errors import ( + AttributeValidationNote, + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, + IterableValidationNote, + StructureHandlerNotFoundError, +) +from ..fns import identity +from ..types import SimpleStructureHook +from ._consts import AttributeOverride, already_generating, neutral +from ._generics import generate_mapping +from ._lc import generate_unique_filename +from ._shared import find_structure_handler + +if TYPE_CHECKING: + from ..converters import BaseConverter + +__all__ = [ + "make_dict_structure_fn", + "make_dict_structure_fn_from_attrs", + "make_dict_unstructure_fn", + "make_dict_unstructure_fn_from_attrs", + "make_hetero_tuple_unstructure_fn", + "make_iterable_unstructure_fn", + "make_mapping_structure_fn", + "make_mapping_unstructure_fn", +] + + +def override( + omit_if_default: bool | None = None, + rename: str | None = None, + omit: bool | None = None, + struct_hook: Callable[[Any, Any], Any] | None = None, + unstruct_hook: Callable[[Any], Any] | None = None, +) -> AttributeOverride: + """Override how a particular field is handled. + + :param omit: Whether to skip the field or not. `None` means apply default handling. + """ + return AttributeOverride(omit_if_default, rename, omit, struct_hook, unstruct_hook) + + +T = TypeVar("T") + + +def make_dict_unstructure_fn_from_attrs( + attrs: list[Attribute], + cl: type[T], + converter: BaseConverter, + typevar_map: dict[str, Any] = {}, + _cattrs_omit_if_default: bool = False, + _cattrs_use_linecache: bool = True, + _cattrs_use_alias: bool = False, + _cattrs_include_init_false: bool = False, + **kwargs: AttributeOverride, +) -> Callable[[T], dict[str, Any]]: + """ + Generate a specialized dict unstructuring function for a list of attributes. + + Usually used as a building block by more specialized hook factories. + + Any provided overrides are attached to the generated function under the + `overrides` attribute. + + :param cl: The class for which the function is generated; used mostly for its name, + module name and qualname. + :param _cattrs_omit_if_default: if true, attributes equal to their default values + will be omitted in the result dictionary. + :param _cattrs_use_alias: If true, the attribute alias will be used as the + dictionary key by default. + :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` + will be included. + + .. versionadded:: 24.1.0 + """ + + fn_name = "unstructure_" + cl.__name__ + globs = {} + lines = [] + invocation_lines = [] + internal_arg_parts = {} + + for a in attrs: + attr_name = a.name + override = kwargs.get(attr_name, neutral) + if override.omit: + continue + if override.omit is None and not a.init and not _cattrs_include_init_false: + continue + if override.rename is None: + kn = attr_name if not _cattrs_use_alias else a.alias + else: + kn = override.rename + d = a.default + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = None + if override.unstruct_hook is not None: + handler = override.unstruct_hook + else: + if a.type is not None: + t = a.type + if isinstance(t, TypeVar): + if t.__name__ in typevar_map: + t = typevar_map[t.__name__] + else: + handler = converter.unstructure + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, typevar_map, cl) + + if handler is None: + if ( + is_bare_final(t) + and a.default is not NOTHING + and not isinstance(a.default, Factory) + ): + # This is a special case where we can use the + # type of the default to dispatch on. + t = a.default.__class__ + try: + handler = converter.get_unstructure_hook(t, cache_result=False) + except RecursionError: + # There's a circular reference somewhere down the line + handler = converter.unstructure + else: + handler = converter.unstructure + + is_identity = handler == identity + + if not is_identity: + unstruct_handler_name = f"__c_unstr_{attr_name}" + globs[unstruct_handler_name] = handler + internal_arg_parts[unstruct_handler_name] = handler + invoke = f"{unstruct_handler_name}(instance.{attr_name})" + else: + invoke = f"instance.{attr_name}" + + if d is not NOTHING and ( + (_cattrs_omit_if_default and override.omit_if_default is not False) + or override.omit_if_default + ): + def_name = f"__c_def_{attr_name}" + + if isinstance(d, Factory): + globs[def_name] = d.factory + internal_arg_parts[def_name] = d.factory + if d.takes_self: + lines.append(f" if instance.{attr_name} != {def_name}(instance):") + else: + lines.append(f" if instance.{attr_name} != {def_name}():") + lines.append(f" res['{kn}'] = {invoke}") + else: + globs[def_name] = d + internal_arg_parts[def_name] = d + lines.append(f" if instance.{attr_name} != {def_name}:") + lines.append(f" res['{kn}'] = {invoke}") + + else: + # No default or no override. + invocation_lines.append(f"'{kn}': {invoke},") + + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + if internal_arg_line: + internal_arg_line = f", {internal_arg_line}" + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = ( + [f"def {fn_name}(instance{internal_arg_line}):"] + + [" res = {"] + + [f" {line}" for line in invocation_lines] + + [" }"] + + lines + + [" return res"] + ) + script = "\n".join(total_lines) + fname = generate_unique_filename( + cl, "unstructure", lines=total_lines if _cattrs_use_linecache else [] + ) + + eval(compile(script, fname, "exec"), globs) + + res = globs[fn_name] + res.overrides = kwargs + + return res + + +def make_dict_unstructure_fn( + cl: type[T], + converter: BaseConverter, + _cattrs_omit_if_default: bool = False, + _cattrs_use_linecache: bool = True, + _cattrs_use_alias: bool = False, + _cattrs_include_init_false: bool = False, + **kwargs: AttributeOverride, +) -> Callable[[T], dict[str, Any]]: + """ + Generate a specialized dict unstructuring function for an attrs class or a + dataclass. + + Any provided overrides are attached to the generated function under the + `overrides` attribute. + + :param _cattrs_omit_if_default: if true, attributes equal to their default values + will be omitted in the result dictionary. + :param _cattrs_use_alias: If true, the attribute alias will be used as the + dictionary key by default. + :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` + will be included. + + .. versionadded:: 23.2.0 *_cattrs_use_alias* + .. versionadded:: 23.2.0 *_cattrs_include_init_false* + """ + origin = get_origin(cl) + attrs = adapted_fields(origin or cl) # type: ignore + + mapping = {} + if is_generic(cl): + mapping = generate_mapping(cl, mapping) + + if origin is not None: + cl = origin + + # We keep track of what we're generating to help with recursive + # class graphs. + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + if cl in working_set: + raise RecursionError() + + working_set.add(cl) + + try: + return make_dict_unstructure_fn_from_attrs( + attrs, + cl, + converter, + mapping, + _cattrs_omit_if_default=_cattrs_omit_if_default, + _cattrs_use_linecache=_cattrs_use_linecache, + _cattrs_use_alias=_cattrs_use_alias, + _cattrs_include_init_false=_cattrs_include_init_false, + **kwargs, + ) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + +def make_dict_structure_fn_from_attrs( + attrs: list[Attribute], + cl: type[T], + converter: BaseConverter, + typevar_map: dict[str, Any] = {}, + _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_linecache: bool = True, + _cattrs_prefer_attrib_converters: ( + bool | Literal["from_converter"] + ) = "from_converter", + _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_alias: bool = False, + _cattrs_include_init_false: bool = False, + **kwargs: AttributeOverride, +) -> SimpleStructureHook[Mapping[str, Any], T]: + """ + Generate a specialized dict structuring function for a list of attributes. + + Usually used as a building block by more specialized hook factories. + + Any provided overrides are attached to the generated function under the + `overrides` attribute. + + :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a + `ForbiddenExtraKeysError` if unknown keys are encountered. + :param _cattrs_use_linecache: Whether to store the source code in the Python + linecache. + :param _cattrs_prefer_attrib_converters: If an _attrs_ converter is present on a + field, use it instead of processing the field normally. + :param _cattrs_detailed_validation: Whether to use a slower mode that produces + more detailed errors. + :param _cattrs_use_alias: If true, the attribute alias will be used as the + dictionary key by default. + :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` + will be included. + + .. versionadded:: 24.1.0 + """ + + cl_name = cl.__name__ + fn_name = "structure_" + cl_name + + # We have generic parameters and need to generate a unique name for the function + for p in getattr(cl, "__parameters__", ()): + # This is nasty, I am not sure how best to handle `typing.List[str]` or + # `TClass[int, int]` as a parameter type here + try: + name_base = typevar_map[p.__name__] + except KeyError: + pn = p.__name__ + raise StructureHandlerNotFoundError( + f"Missing type for generic argument {pn}, specify it when structuring.", + p, + ) from None + name = getattr(name_base, "__name__", None) or str(name_base) + # `<>` can be present in lambdas + # `|` can be present in unions + name = re.sub(r"[\[\.\] ,<>]", "_", name) + name = re.sub(r"\|", "u", name) + fn_name += f"_{name}" + + internal_arg_parts = {"__cl": cl} + globs = {} + lines = [] + post_lines = [] + pi_lines = [] # post instantiation lines + invocation_lines = [] + + allowed_fields = set() + if _cattrs_forbid_extra_keys == "from_converter": + # BaseConverter doesn't have it so we're careful. + _cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False) + if _cattrs_detailed_validation == "from_converter": + _cattrs_detailed_validation = converter.detailed_validation + if _cattrs_prefer_attrib_converters == "from_converter": + _cattrs_prefer_attrib_converters = converter._prefer_attrib_converters + + if _cattrs_forbid_extra_keys: + globs["__c_a"] = allowed_fields + globs["__c_feke"] = ForbiddenExtraKeysError + + if _cattrs_detailed_validation: + lines.append(" res = {}") + lines.append(" errors = []") + invocation_lines.append("**res,") + internal_arg_parts["__c_cve"] = ClassValidationError + internal_arg_parts["__c_avn"] = AttributeValidationNote + for a in attrs: + an = a.name + override = kwargs.get(an, neutral) + if override.omit: + continue + if override.omit is None and not a.init and not _cattrs_include_init_false: + continue + t = a.type + if isinstance(t, TypeVar): + t = typevar_map.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, typevar_map, cl) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler( + a, t, converter, _cattrs_prefer_attrib_converters + ) + + struct_handler_name = f"__c_structure_{an}" + if handler is not None: + internal_arg_parts[struct_handler_name] = handler + + ian = a.alias + if override.rename is None: + kn = an if not _cattrs_use_alias else a.alias + else: + kn = override.rename + + allowed_fields.add(kn) + i = " " + + if not a.init: + if a.default is not NOTHING: + pi_lines.append(f"{i}if '{kn}' in o:") + i = f"{i} " + pi_lines.append(f"{i}try:") + i = f"{i} " + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t + if handler is not None: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + pi_lines.append( + f"{i}instance.{an} = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + pi_lines.append( + f"{i}instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + pi_lines.append(f"{i}instance.{an} = o['{kn}']") + i = i[:-2] + pi_lines.append(f"{i}except Exception as e:") + i = f"{i} " + pi_lines.append( + f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring class {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]' + ) + pi_lines.append(f"{i}errors.append(e)") + + else: + if a.default is not NOTHING: + lines.append(f"{i}if '{kn}' in o:") + i = f"{i} " + lines.append(f"{i}try:") + i = f"{i} " + type_name = f"__c_type_{an}" + internal_arg_parts[type_name] = t + if handler: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + lines.append( + f"{i}res['{ian}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + lines.append( + f"{i}res['{ian}'] = {struct_handler_name}(o['{kn}'], {type_name})" + ) + else: + lines.append(f"{i}res['{ian}'] = o['{kn}']") + i = i[:-2] + lines.append(f"{i}except Exception as e:") + i = f"{i} " + lines.append( + f'{i}e.__notes__ = getattr(e, \'__notes__\', []) + [__c_avn("Structuring class {cl.__qualname__} @ attribute {an}", "{an}", __c_type_{an})]' + ) + lines.append(f"{i}errors.append(e)") + + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = set(o.keys()) - __c_a", + " if unknown_fields:", + " errors.append(__c_feke('', __cl, unknown_fields))", + ] + + post_lines.append( + f" if errors: raise __c_cve('While structuring ' + {cl_name!r}, errors, __cl)" + ) + if not pi_lines: + instantiation_lines = ( + [" try:"] + + [" return __cl("] + + [f" {line}" for line in invocation_lines] + + [" )"] + + [ + f" except Exception as exc: raise __c_cve('While structuring ' + {cl_name!r}, [exc], __cl)" + ] + ) + else: + instantiation_lines = ( + [" try:"] + + [" instance = __cl("] + + [f" {line}" for line in invocation_lines] + + [" )"] + + [ + f" except Exception as exc: raise __c_cve('While structuring ' + {cl_name!r}, [exc], __cl)" + ] + ) + pi_lines.append(" return instance") + else: + non_required = [] + # The first loop deals with required args. + for a in attrs: + an = a.name + override = kwargs.get(an, neutral) + if override.omit: + continue + if override.omit is None and not a.init and not _cattrs_include_init_false: + continue + if a.default is not NOTHING: + non_required.append(a) + continue + t = a.type + if isinstance(t, TypeVar): + t = typevar_map.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, typevar_map, cl) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler( + a, t, converter, _cattrs_prefer_attrib_converters + ) + + if override.rename is None: + kn = an if not _cattrs_use_alias else a.alias + else: + kn = override.rename + allowed_fields.add(kn) + + if not a.init: + if handler is not None: + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + pi_line = f" instance.{an} = {struct_handler_name}(o['{kn}'])" + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + pi_line = ( + f" instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + pi_line = f" instance.{an} = o['{kn}']" + + pi_lines.append(pi_line) + else: + if handler: + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + invocation_line = f"{struct_handler_name}(o['{kn}'])," + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + invocation_line = f"{struct_handler_name}(o['{kn}'], {tn})," + else: + invocation_line = f"o['{kn}']," + + if a.kw_only: + invocation_line = f"{a.alias}={invocation_line}" + invocation_lines.append(invocation_line) + + # The second loop is for optional args. + if non_required: + invocation_lines.append("**res,") + lines.append(" res = {}") + + for a in non_required: + an = a.name + override = kwargs.get(an, neutral) + t = a.type + if isinstance(t, TypeVar): + t = typevar_map.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, typevar_map, cl) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler( + a, t, converter, _cattrs_prefer_attrib_converters + ) + + struct_handler_name = f"__c_structure_{an}" + internal_arg_parts[struct_handler_name] = handler + + if override.rename is None: + kn = an if not _cattrs_use_alias else a.alias + else: + kn = override.rename + allowed_fields.add(kn) + if not a.init: + pi_lines.append(f" if '{kn}' in o:") + if handler: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + pi_lines.append( + f" instance.{an} = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + pi_lines.append( + f" instance.{an} = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + pi_lines.append(f" instance.{an} = o['{kn}']") + else: + post_lines.append(f" if '{kn}' in o:") + if handler: + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + post_lines.append( + f" res['{a.alias}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{an}" + internal_arg_parts[tn] = t + post_lines.append( + f" res['{a.alias}'] = {struct_handler_name}(o['{kn}'], {tn})" + ) + else: + post_lines.append(f" res['{a.alias}'] = o['{kn}']") + if not pi_lines: + instantiation_lines = ( + [" return __cl("] + + [f" {line}" for line in invocation_lines] + + [" )"] + ) + else: + instantiation_lines = ( + [" instance = __cl("] + + [f" {line}" for line in invocation_lines] + + [" )"] + ) + pi_lines.append(" return instance") + + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = set(o.keys()) - __c_a", + " if unknown_fields:", + " raise __c_feke('', __cl, unknown_fields)", + ] + + # At the end, we create the function header. + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + globs.update(internal_arg_parts) + + total_lines = [ + f"def {fn_name}(o, _=__cl, {internal_arg_line}):", + *lines, + *post_lines, + *instantiation_lines, + *pi_lines, + ] + + script = "\n".join(total_lines) + fname = generate_unique_filename( + cl, "structure", lines=total_lines if _cattrs_use_linecache else [] + ) + + eval(compile(script, fname, "exec"), globs) + + res = globs[fn_name] + res.overrides = kwargs + + return res + + +def make_dict_structure_fn( + cl: type[T], + converter: BaseConverter, + _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_linecache: bool = True, + _cattrs_prefer_attrib_converters: ( + bool | Literal["from_converter"] + ) = "from_converter", + _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_alias: bool = False, + _cattrs_include_init_false: bool = False, + **kwargs: AttributeOverride, +) -> SimpleStructureHook[Mapping[str, Any], T]: + """ + Generate a specialized dict structuring function for an attrs class or + dataclass. + + Any provided overrides are attached to the generated function under the + `overrides` attribute. + + :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a + `ForbiddenExtraKeysError` if unknown keys are encountered. + :param _cattrs_use_linecache: Whether to store the source code in the Python + linecache. + :param _cattrs_prefer_attrib_converters: If an _attrs_ converter is present on a + field, use it instead of processing the field normally. + :param _cattrs_detailed_validation: Whether to use a slower mode that produces + more detailed errors. + :param _cattrs_use_alias: If true, the attribute alias will be used as the + dictionary key by default. + :param _cattrs_include_init_false: If true, _attrs_ fields marked as `init=False` + will be included. + + .. versionadded:: 23.2.0 *_cattrs_use_alias* + .. versionadded:: 23.2.0 *_cattrs_include_init_false* + .. versionchanged:: 23.2.0 + The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters + take their values from the given converter by default. + .. versionchanged:: 24.1.0 + The `_cattrs_prefer_attrib_converters` parameter takes its value from the given + converter by default. + """ + + mapping = {} + if is_generic(cl): + base = get_origin(cl) + mapping = generate_mapping(cl, mapping) + if base is not None: + cl = base + + for base in getattr(cl, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + + attrs = adapted_fields(cl) + + # We keep track of what we're generating to help with recursive + # class graphs. + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + else: + if cl in working_set: + raise RecursionError() + + working_set.add(cl) + + try: + return make_dict_structure_fn_from_attrs( + attrs, + cl, + converter, + mapping, + _cattrs_forbid_extra_keys=_cattrs_forbid_extra_keys, + _cattrs_use_linecache=_cattrs_use_linecache, + _cattrs_prefer_attrib_converters=_cattrs_prefer_attrib_converters, + _cattrs_detailed_validation=_cattrs_detailed_validation, + _cattrs_use_alias=_cattrs_use_alias, + _cattrs_include_init_false=_cattrs_include_init_false, + **kwargs, + ) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + +IterableUnstructureFn = Callable[[Iterable[Any]], Any] + + +#: A type alias for heterogeneous tuple unstructure hooks. +HeteroTupleUnstructureFn: TypeAlias = Callable[[tuple[Any, ...]], Any] + + +def make_hetero_tuple_unstructure_fn( + cl: Any, + converter: BaseConverter, + unstructure_to: Any = None, + type_args: tuple | None = None, +) -> HeteroTupleUnstructureFn: + """Generate a specialized unstructure function for a heterogenous tuple. + + :param type_args: If provided, override the type arguments. + """ + fn_name = "unstructure_tuple" + + type_args = get_args(cl) if type_args is None else type_args + + # We can do the dispatch here and now. + handlers = [converter.get_unstructure_hook(type_arg) for type_arg in type_args] + + globs = {f"__cattr_u_{i}": h for i, h in enumerate(handlers)} + if unstructure_to is not tuple: + globs["__cattr_seq_cl"] = unstructure_to or cl + lines = [] + + lines.append(f"def {fn_name}(tup):") + if unstructure_to is not tuple: + lines.append(" res = __cattr_seq_cl((") + else: + lines.append(" res = (") + for i in range(len(handlers)): + if handlers[i] == identity: + lines.append(f" tup[{i}],") + else: + lines.append(f" __cattr_u_{i}(tup[{i}]),") + + if unstructure_to is not tuple: + lines.append(" ))") + else: + lines.append(" )") + + total_lines = [*lines, " return res"] + + eval(compile("\n".join(total_lines), "", "exec"), globs) + + return globs[fn_name] + + +MappingUnstructureFn = Callable[[Mapping[Any, Any]], Any] + + +# This factory is here for backwards compatibility and circular imports. +def mapping_unstructure_factory( + cl: Any, + converter: BaseConverter, + unstructure_to: Any = None, + key_handler: Callable[[Any, Any | None], Any] | None = None, +) -> MappingUnstructureFn: + """Generate a specialized unstructure function for a mapping. + + :param unstructure_to: The class to unstructure to; defaults to the + same class as the mapping being unstructured. + """ + kh = key_handler or converter.unstructure + val_handler = converter.unstructure + + fn_name = "unstructure_mapping" + origin = cl + + # Let's try fishing out the type args. + if getattr(cl, "__args__", None) is not None: + args = get_args(cl) + if len(args) == 2: + key_arg, val_arg = args + else: + # Probably a Counter + key_arg, val_arg = args, Any + # We can do the dispatch here and now. + kh = key_handler or converter.get_unstructure_hook(key_arg, cache_result=False) + if kh == identity: + kh = None + + val_handler = converter.get_unstructure_hook(val_arg, cache_result=False) + if val_handler == identity: + val_handler = None + + origin = get_origin(cl) + + globs = {"__cattr_k_u": kh, "__cattr_v_u": val_handler} + + k_u = "__cattr_k_u(k)" if kh is not None else "k" + v_u = "__cattr_v_u(v)" if val_handler is not None else "v" + + lines = [f"def {fn_name}(mapping):"] + + if unstructure_to is dict or (unstructure_to is None and origin is dict): + if kh is None and val_handler is None: + # Simplest path. + return dict + + lines.append(f" return {{{k_u}: {v_u} for k, v in mapping.items()}}") + else: + globs["__cattr_mapping_cl"] = unstructure_to or cl + lines.append( + f" res = __cattr_mapping_cl(({k_u}, {v_u}) for k, v in mapping.items())" + ) + + lines = [*lines, " return res"] + + eval(compile("\n".join(lines), "", "exec"), globs) + + return globs[fn_name] + + +make_mapping_unstructure_fn: Final = mapping_unstructure_factory + + +# This factory is here for backwards compatibility and circular imports. +def mapping_structure_factory( + cl: type[T], + converter: BaseConverter, + structure_to: type = dict, + key_type=NOTHING, + val_type=NOTHING, + detailed_validation: bool | Literal["from_converter"] = "from_converter", +) -> SimpleStructureHook[Mapping[Any, Any], T]: + """Generate a specialized structure function for a mapping.""" + fn_name = "structure_mapping" + + if detailed_validation == "from_converter": + detailed_validation = converter.detailed_validation + + globs: dict[str, type] = {"__cattr_mapping_cl": structure_to} + + lines = [] + internal_arg_parts = {} + + # Let's try fishing out the type args. + if not is_bare(cl): + args = get_args(cl) + if len(args) == 2: + key_arg_cand, val_arg_cand = args + if key_type is NOTHING: + key_type = key_arg_cand + if val_type is NOTHING: + val_type = val_arg_cand + else: + if key_type is not NOTHING and val_type is NOTHING: + (val_type,) = args + elif key_type is NOTHING and val_type is not NOTHING: + (key_type,) = args + else: + # Probably a Counter + (key_type,) = args + val_type = Any + + is_bare_dict = val_type in ANIES and key_type in ANIES + if not is_bare_dict: + # We can do the dispatch here and now. + key_handler = converter.get_structure_hook(key_type, cache_result=False) + if key_handler == converter._structure_call: + key_handler = key_type + + val_handler = converter.get_structure_hook(val_type, cache_result=False) + if val_handler == converter._structure_call: + val_handler = val_type + + globs["__cattr_k_t"] = key_type + globs["__cattr_v_t"] = val_type + globs["__cattr_k_s"] = key_handler + globs["__cattr_v_s"] = val_handler + k_s = ( + "__cattr_k_s(k, __cattr_k_t)" + if key_handler != key_type + else "__cattr_k_s(k)" + ) + v_s = ( + "__cattr_v_s(v, __cattr_v_t)" + if val_handler != val_type + else "__cattr_v_s(v)" + ) + else: + is_bare_dict = True + + if is_bare_dict: + # No args, it's a bare dict. + lines.append(" res = dict(mapping)") + else: + if detailed_validation: + internal_arg_parts["IterableValidationError"] = IterableValidationError + internal_arg_parts["IterableValidationNote"] = IterableValidationNote + internal_arg_parts["val_type"] = ( + val_type if val_type is not NOTHING else Any + ) + internal_arg_parts["key_type"] = ( + key_type if key_type is not NOTHING else Any + ) + globs["enumerate"] = enumerate + + lines.append(" res = {}; errors = []") + lines.append(" for k, v in mapping.items():") + lines.append(" try:") + lines.append(f" value = {v_s}") + lines.append(" except Exception as e:") + lines.append( + " e.__notes__ = getattr(e, '__notes__', []) + [IterableValidationNote(f'Structuring mapping value @ key {k!r}', k, val_type)]" + ) + lines.append(" errors.append(e)") + lines.append(" continue") + lines.append(" try:") + lines.append(f" key = {k_s}") + lines.append(" res[key] = value") + lines.append(" except Exception as e:") + lines.append( + " e.__notes__ = getattr(e, '__notes__', []) + [IterableValidationNote(f'Structuring mapping key @ key {k!r}', k, key_type)]" + ) + lines.append(" errors.append(e)") + lines.append(" if errors:") + lines.append( + f" raise IterableValidationError('While structuring ' + {repr(cl)!r}, errors, __cattr_mapping_cl)" + ) + else: + lines.append(f" res = {{{k_s}: {v_s} for k, v in mapping.items()}}") + if structure_to is not dict: + lines.append(" res = __cattr_mapping_cl(res)") + + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + if internal_arg_line: + internal_arg_line = f", {internal_arg_line}" + for k, v in internal_arg_parts.items(): + globs[k] = v + + globs["cl"] = cl + def_line = f"def {fn_name}(mapping, cl=cl{internal_arg_line}):" + total_lines = [def_line, *lines, " return res"] + script = "\n".join(total_lines) + + eval(compile(script, "", "exec"), globs) + + return globs[fn_name] + + +make_mapping_structure_fn: Final = mapping_structure_factory + + +# This factory is here for backwards compatibility and circular imports. +def iterable_unstructure_factory( + cl: Any, converter: BaseConverter, unstructure_to: Any = None +) -> UnstructureHook: + """A hook factory for unstructuring iterables. + + :param unstructure_to: Force unstructuring to this type, if provided. + + .. versionchanged:: 24.2.0 + `typing.NoDefault` is now correctly handled as `Any`. + """ + handler = converter.unstructure + + # Let's try fishing out the type args + # Unspecified tuples have `__args__` as empty tuples, so guard + # against IndexError. + if getattr(cl, "__args__", None) not in (None, ()): + type_arg = cl.__args__[0] + if isinstance(type_arg, TypeVar): + type_arg = getattr(type_arg, "__default__", Any) + if type_arg is NoDefault: + type_arg = Any + handler = converter.get_unstructure_hook(type_arg, cache_result=False) + if handler == identity: + # Save ourselves the trouble of iterating over it all. + return unstructure_to or cl + + def unstructure_iterable(iterable, _seq_cl=unstructure_to or cl, _hook=handler): + return _seq_cl(_hook(i) for i in iterable) + + return unstructure_iterable + + +make_iterable_unstructure_fn: Final = iterable_unstructure_factory diff --git a/server/libs/cattrs/gen/_consts.py b/server/libs/cattrs/gen/_consts.py new file mode 100644 index 0000000..a6dcd03 --- /dev/null +++ b/server/libs/cattrs/gen/_consts.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from threading import local +from typing import Any, Callable + +from attrs import frozen + + +@frozen +class AttributeOverride: + omit_if_default: bool | None = None + rename: str | None = None + omit: bool | None = None # Omit the field completely. + struct_hook: Callable[[Any, Any], Any] | None = None # Structure hook to use. + unstruct_hook: Callable[[Any], Any] | None = None # Structure hook to use. + + +neutral = AttributeOverride() +already_generating = local() diff --git a/server/libs/cattrs/gen/_generics.py b/server/libs/cattrs/gen/_generics.py new file mode 100644 index 0000000..069c48c --- /dev/null +++ b/server/libs/cattrs/gen/_generics.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from typing import TypeVar + +from .._compat import get_args, get_origin, is_generic + + +def _tvar_has_default(tvar) -> bool: + """Does `tvar` have a default? + + In CPython 3.13+ and typing_extensions>=4.12.0: + - TypeVars have a `no_default()` method for detecting + if a TypeVar has a default + - TypeVars with `default=None` have `__default__` set to `None` + - TypeVars with no `default` parameter passed + have `__default__` set to `typing(_extensions).NoDefault + + On typing_exensions<4.12.0: + - TypeVars do not have a `no_default()` method for detecting + if a TypeVar has a default + - TypeVars with `default=None` have `__default__` set to `NoneType` + - TypeVars with no `default` parameter passed + have `__default__` set to `typing(_extensions).NoDefault + """ + try: + return tvar.has_default() + except AttributeError: + # compatibility for typing_extensions<4.12.0 + return getattr(tvar, "__default__", None) is not None + + +def generate_mapping(cl: type, old_mapping: dict[str, type] = {}) -> dict[str, type]: + """Generate a mapping of typevars to actual types for a generic class.""" + mapping = dict(old_mapping) + + origin = get_origin(cl) + + if origin is not None: + # To handle the cases where classes in the typing module are using + # the GenericAlias structure but aren't a Generic and hence + # end up in this function but do not have an `__parameters__` + # attribute. These classes are interface types, for example + # `typing.Hashable`. + parameters = getattr(get_origin(cl), "__parameters__", None) + if parameters is None: + return dict(old_mapping) + + for p, t in zip(parameters, get_args(cl)): + if isinstance(t, TypeVar): + continue + mapping[p.__name__] = t + + elif is_generic(cl): + # Origin is None, so this may be a subclass of a generic class. + orig_bases = cl.__orig_bases__ + for base in orig_bases: + if not hasattr(base, "__args__"): + continue + base_args = base.__args__ + if hasattr(base.__origin__, "__parameters__"): + base_params = base.__origin__.__parameters__ + elif any(_tvar_has_default(base_arg) for base_arg in base_args): + # TypeVar with a default e.g. PEP 696 + # https://www.python.org/dev/peps/pep-0696/ + # Extract the defaults for the TypeVars and insert + # them into the mapping + mapping_params = [ + (base_arg, base_arg.__default__) + for base_arg in base_args + if _tvar_has_default(base_arg) + ] + base_params, base_args = zip(*mapping_params) + else: + continue + + for param, arg in zip(base_params, base_args): + mapping[param.__name__] = arg + + return mapping diff --git a/server/libs/cattrs/gen/_lc.py b/server/libs/cattrs/gen/_lc.py new file mode 100644 index 0000000..71e8b61 --- /dev/null +++ b/server/libs/cattrs/gen/_lc.py @@ -0,0 +1,28 @@ +"""Line-cache functionality.""" + +import linecache + + +def generate_unique_filename(cls: type, func_name: str, lines: list[str] = []) -> str: + """ + Create a "filename" suitable for a function being generated. + + If *lines* are provided, insert them in the first free spot or stop + if a duplicate is found. + """ + extra = "" + count = 1 + + while True: + unique_filename = "".format( + func_name, cls.__module__, getattr(cls, "__qualname__", cls.__name__), extra + ) + if not lines: + return unique_filename + cache_line = (len("\n".join(lines)), None, lines, unique_filename) + if linecache.cache.setdefault(unique_filename, cache_line) == cache_line: + return unique_filename + + # Looks like this spot is taken. Try again. + count += 1 + extra = f"-{count}" diff --git a/server/libs/cattrs/gen/_shared.py b/server/libs/cattrs/gen/_shared.py new file mode 100644 index 0000000..904c774 --- /dev/null +++ b/server/libs/cattrs/gen/_shared.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from attrs import NOTHING, Attribute, Factory + +from .._compat import is_bare_final +from ..dispatch import StructureHook +from ..errors import StructureHandlerNotFoundError +from ..fns import raise_error + +if TYPE_CHECKING: + from ..converters import BaseConverter + + +def find_structure_handler( + a: Attribute, type: Any, c: BaseConverter, prefer_attrs_converters: bool = False +) -> StructureHook | None: + """Find the appropriate structure handler to use. + + Return `None` if no handler should be used. + """ + try: + if a.converter is not None and prefer_attrs_converters: + # If the user as requested to use attrib converters, use nothing + # so it falls back to that. + handler = None + elif ( + a.converter is not None and not prefer_attrs_converters and type is not None + ): + try: + handler = c.get_structure_hook(type, cache_result=False) + except StructureHandlerNotFoundError: + handler = None + else: + # The legacy way, should still work. + if handler == raise_error: + handler = None + elif type is not None: + if ( + is_bare_final(type) + and a.default is not NOTHING + and not isinstance(a.default, Factory) + ): + # This is a special case where we can use the + # type of the default to dispatch on. + type = a.default.__class__ + handler = c.get_structure_hook(type, cache_result=False) + if handler == c._structure_call: + # Finals can't really be used with _structure_call, so + # we wrap it so the rest of the toolchain doesn't get + # confused. + + def handler(v, _, _h=handler): + return _h(v, type) + + else: + handler = c.get_structure_hook(type, cache_result=False) + else: + handler = c.structure + return handler + except RecursionError: + # This means we're dealing with a reference cycle, so use late binding. + return c.structure diff --git a/server/libs/cattrs/gen/typeddicts.py b/server/libs/cattrs/gen/typeddicts.py new file mode 100644 index 0000000..bca38a5 --- /dev/null +++ b/server/libs/cattrs/gen/typeddicts.py @@ -0,0 +1,582 @@ +from __future__ import annotations + +import re +import sys +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Callable, Literal, TypeVar + +from attrs import NOTHING, Attribute +from typing_extensions import _TypedDictMeta + +try: + from inspect import get_annotations + + def get_annots(cl) -> dict[str, Any]: + return get_annotations(cl, eval_str=True) + +except ImportError: + # https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older + def get_annots(cl) -> dict[str, Any]: + return cl.__dict__.get("__annotations__", {}) + + +from .._compat import ( + get_full_type_hints, + get_notrequired_base, + get_origin, + is_annotated, + is_bare, + is_generic, +) +from .._generics import deep_copy_with +from ..errors import ( + AttributeValidationNote, + ClassValidationError, + ForbiddenExtraKeysError, + StructureHandlerNotFoundError, +) +from ..fns import identity +from . import AttributeOverride +from ._consts import already_generating, neutral +from ._generics import generate_mapping +from ._lc import generate_unique_filename +from ._shared import find_structure_handler + +if TYPE_CHECKING: + from ..converters import BaseConverter + +__all__ = ["make_dict_structure_fn", "make_dict_unstructure_fn"] + +T = TypeVar("T") + + +def make_dict_unstructure_fn( + cl: type[T], + converter: BaseConverter, + _cattrs_use_linecache: bool = True, + **kwargs: AttributeOverride, +) -> Callable[[T], dict[str, Any]]: + """ + Generate a specialized dict unstructuring function for a TypedDict. + + :param cl: A `TypedDict` class. + :param converter: A Converter instance to use for unstructuring nested fields. + :param kwargs: A mapping of field names to an `AttributeOverride`, for + customization. + :param _cattrs_detailed_validation: Whether to store the generated code in the + _linecache_, for easier debugging and better stack traces. + """ + origin = get_origin(cl) + attrs = _adapted_fields(origin or cl) # type: ignore + req_keys = _required_keys(origin or cl) + + mapping = {} + if is_generic(cl): + mapping = generate_mapping(cl, mapping) + + for base in getattr(origin, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + + # It's possible for origin to be None if this is a subclass + # of a generic class. + if origin is not None: + cl = origin + + cl_name = cl.__name__ + fn_name = "unstructure_typeddict_" + cl_name + globs = {} + lines = [] + internal_arg_parts = {} + + # We keep track of what we're generating to help with recursive + # class graphs. + try: + working_set = already_generating.working_set + except AttributeError: + working_set = set() + already_generating.working_set = working_set + if cl in working_set: + raise RecursionError() + working_set.add(cl) + + try: + # We want to short-circuit in certain cases and return the identity + # function. + # We short-circuit if all of these are true: + # * no attributes have been overridden + # * all attributes resolve to `converter._unstructure_identity` + for a in attrs: + attr_name = a.name + override = kwargs.get(attr_name, neutral) + if override != neutral: + break + handler = None + t = a.type + + if isinstance(t, TypeVar): + if t.__name__ in mapping: + t = mapping[t.__name__] + else: + # Unbound typevars use late binding. + handler = converter.unstructure + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping, cl) + + if handler is None: + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + try: + handler = converter.get_unstructure_hook(t) + except RecursionError: + # There's a circular reference somewhere down the line + handler = converter.unstructure + is_identity = handler == identity + if not is_identity: + break + else: + # We've not broken the loop. + return identity + + for ix, a in enumerate(attrs): + attr_name = a.name + override = kwargs.get(attr_name, neutral) + if override.omit: + lines.append(f" res.pop('{attr_name}', None)") + continue + if override.rename is not None: + # We also need to pop when renaming, since we're copying + # the original. + lines.append(f" res.pop('{attr_name}', None)") + kn = attr_name if override.rename is None else override.rename + attr_required = attr_name in req_keys + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = None + if override.unstruct_hook is not None: + handler = override.unstruct_hook + else: + t = a.type + + if isinstance(t, TypeVar): + if t.__name__ in mapping: + t = mapping[t.__name__] + else: + handler = converter.unstructure + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping, cl) + + if handler is None: + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + try: + handler = converter.get_unstructure_hook(t) + except RecursionError: + # There's a circular reference somewhere down the line + handler = converter.unstructure + + is_identity = handler == identity + + if not is_identity: + unstruct_handler_name = f"__c_unstr_{ix}" + globs[unstruct_handler_name] = handler + internal_arg_parts[unstruct_handler_name] = handler + invoke = f"{unstruct_handler_name}(instance['{attr_name}'])" + elif override.rename is None: + # We're not doing anything to this attribute, so + # it'll already be present in the input dict. + continue + else: + # Probably renamed, we just fetch it. + invoke = f"instance['{attr_name}']" + + if attr_required: + # No default or no override. + lines.append(f" res['{kn}'] = {invoke}") + else: + lines.append(f" if '{attr_name}' in instance: res['{kn}'] = {invoke}") + + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + if internal_arg_line: + internal_arg_line = f", {internal_arg_line}" + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = [ + f"def {fn_name}(instance{internal_arg_line}):", + " res = instance.copy()", + *lines, + " return res", + ] + script = "\n".join(total_lines) + + fname = generate_unique_filename( + cl, "unstructure", lines=total_lines if _cattrs_use_linecache else [] + ) + + eval(compile(script, fname, "exec"), globs) + finally: + working_set.remove(cl) + if not working_set: + del already_generating.working_set + + return globs[fn_name] + + +def make_dict_structure_fn( + cl: Any, + converter: BaseConverter, + _cattrs_forbid_extra_keys: bool | Literal["from_converter"] = "from_converter", + _cattrs_use_linecache: bool = True, + _cattrs_detailed_validation: bool | Literal["from_converter"] = "from_converter", + **kwargs: AttributeOverride, +) -> Callable[[dict, Any], Any]: + """Generate a specialized dict structuring function for typed dicts. + + :param cl: A `TypedDict` class. + :param converter: A Converter instance to use for structuring nested fields. + :param kwargs: A mapping of field names to an `AttributeOverride`, for + customization. + :param _cattrs_detailed_validation: Whether to use a slower mode that produces + more detailed errors. + :param _cattrs_forbid_extra_keys: Whether the structuring function should raise a + `ForbiddenExtraKeysError` if unknown keys are encountered. + :param _cattrs_detailed_validation: Whether to store the generated code in the + _linecache_, for easier debugging and better stack traces. + + .. versionchanged:: 23.2.0 + The `_cattrs_forbid_extra_keys` and `_cattrs_detailed_validation` parameters + take their values from the given converter by default. + """ + + mapping = {} + if is_generic(cl): + base = get_origin(cl) + mapping = generate_mapping(cl, mapping) + if base is not None: + # It's possible for this to be a subclass of a generic, + # so no origin. + cl = base + + for base in getattr(cl, "__orig_bases__", ()): + if is_generic(base) and not str(base).startswith("typing.Generic"): + mapping = generate_mapping(base, mapping) + break + + cl_name = cl.__name__ + fn_name = "structure_" + cl_name + + # We have generic parameters and need to generate a unique name for the function + for p in getattr(cl, "__parameters__", ()): + try: + name_base = mapping[p.__name__] + except KeyError: + pn = p.__name__ + raise StructureHandlerNotFoundError( + f"Missing type for generic argument {pn}, specify it when structuring.", + p, + ) from None + name = getattr(name_base, "__name__", None) or str(name_base) + # `<>` can be present in lambdas + # `|` can be present in unions + name = re.sub(r"[\[\.\] ,<>]", "_", name) + name = re.sub(r"\|", "u", name) + fn_name += f"_{name}" + + internal_arg_parts = {"__cl": cl} + globs = {} + lines = [] + post_lines = [] + + attrs = _adapted_fields(cl) + req_keys = _required_keys(cl) + + allowed_fields = set() + if _cattrs_forbid_extra_keys == "from_converter": + # BaseConverter doesn't have it so we're careful. + _cattrs_forbid_extra_keys = getattr(converter, "forbid_extra_keys", False) + if _cattrs_detailed_validation == "from_converter": + _cattrs_detailed_validation = converter.detailed_validation + + if _cattrs_forbid_extra_keys: + globs["__c_a"] = allowed_fields + globs["__c_feke"] = ForbiddenExtraKeysError + + if _cattrs_detailed_validation: + # When running under detailed validation, be extra careful about the + # input type so that the correct error is raised if the input isn't a dict. + internal_arg_parts["__c_mapping"] = Mapping + lines.append(" if not isinstance(o, __c_mapping):") + te = "TypeError(f'expected a mapping, not {o.__class__.__name__}')" + lines.append( + f" raise __c_cve('While structuring ' + {cl.__name__!r}, [{te}], __cl)" + ) + + lines.append(" res = o.copy()") + + if _cattrs_detailed_validation: + lines.append(" errors = []") + internal_arg_parts["__c_cve"] = ClassValidationError + internal_arg_parts["__c_avn"] = AttributeValidationNote + for ix, a in enumerate(attrs): + an = a.name + attr_required = an in req_keys + override = kwargs.get(an, neutral) + if override.omit: + continue + t = a.type + + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping, cl) + + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + + if is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping, cl) + + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + if override.struct_hook is not None: + # If the user has requested an override, just use that. + handler = override.struct_hook + else: + handler = find_structure_handler(a, t, converter) + + struct_handler_name = f"__c_structure_{ix}" + internal_arg_parts[struct_handler_name] = handler + + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + i = " " + if not attr_required: + lines.append(f"{i}if '{kn}' in o:") + i = f"{i} " + lines.append(f"{i}try:") + i = f"{i} " + + tn = f"__c_type_{ix}" + internal_arg_parts[tn] = t + + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'])") + else: + lines.append(f"{i}res['{an}'] = {struct_handler_name}(o['{kn}'], {tn})") + if override.rename is not None: + lines.append(f"{i}del res['{kn}']") + i = i[:-2] + lines.append(f"{i}except Exception as e:") + i = f"{i} " + lines.append( + f'{i}e.__notes__ = [*getattr(e, \'__notes__\', []), __c_avn("Structuring typeddict {cl.__qualname__} @ attribute {an}", "{an}", {tn})]' + ) + lines.append(f"{i}errors.append(e)") + + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = o.keys() - __c_a", + " if unknown_fields:", + " errors.append(__c_feke('', __cl, unknown_fields))", + ] + + post_lines.append( + f" if errors: raise __c_cve('While structuring ' + {cl.__name__!r}, errors, __cl)" + ) + else: + non_required = [] + + # The first loop deals with required args. + for ix, a in enumerate(attrs): + an = a.name + attr_required = an in req_keys + override = kwargs.get(an, neutral) + if override.omit: + continue + if not attr_required: + non_required.append((ix, a)) + continue + + t = a.type + + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping, cl) + + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + + if override.struct_hook is not None: + handler = override.struct_hook + else: + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = converter.get_structure_hook(t) + + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + + struct_handler_name = f"__c_structure_{ix}" + internal_arg_parts[struct_handler_name] = handler + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + invocation_line = f" res['{an}'] = {struct_handler_name}(o['{kn}'])" + else: + tn = f"__c_type_{ix}" + internal_arg_parts[tn] = t + invocation_line = ( + f" res['{an}'] = {struct_handler_name}(o['{kn}'], {tn})" + ) + + lines.append(invocation_line) + if override.rename is not None: + lines.append(f" del res['{override.rename}']") + + # The second loop is for optional args. + if non_required: + for ix, a in non_required: + an = a.name + override = kwargs.get(an, neutral) + t = a.type + + nrb = get_notrequired_base(t) + if nrb is not NOTHING: + t = nrb + + if isinstance(t, TypeVar): + t = mapping.get(t.__name__, t) + elif is_generic(t) and not is_bare(t) and not is_annotated(t): + t = deep_copy_with(t, mapping, cl) + + if override.struct_hook is not None: + handler = override.struct_hook + else: + # For each attribute, we try resolving the type here and now. + # If a type is manually overwritten, this function should be + # regenerated. + handler = converter.get_structure_hook(t) + + struct_handler_name = f"__c_structure_{ix}" + internal_arg_parts[struct_handler_name] = handler + + ian = an + kn = an if override.rename is None else override.rename + allowed_fields.add(kn) + post_lines.append(f" if '{kn}' in o:") + if handler == converter._structure_call: + internal_arg_parts[struct_handler_name] = t + post_lines.append( + f" res['{ian}'] = {struct_handler_name}(o['{kn}'])" + ) + else: + tn = f"__c_type_{ix}" + internal_arg_parts[tn] = t + post_lines.append( + f" res['{ian}'] = {struct_handler_name}(o['{kn}'], {tn})" + ) + if override.rename is not None: + lines.append(f" res.pop('{override.rename}', None)") + + if _cattrs_forbid_extra_keys: + post_lines += [ + " unknown_fields = o.keys() - __c_a", + " if unknown_fields:", + " raise __c_feke('', __cl, unknown_fields)", + ] + + # At the end, we create the function header. + internal_arg_line = ", ".join([f"{i}={i}" for i in internal_arg_parts]) + for k, v in internal_arg_parts.items(): + globs[k] = v + + total_lines = [ + f"def {fn_name}(o, _, {internal_arg_line}):", + *lines, + *post_lines, + " return res", + ] + + script = "\n".join(total_lines) + fname = generate_unique_filename( + cl, "structure", lines=total_lines if _cattrs_use_linecache else [] + ) + + eval(compile(script, fname, "exec"), globs) + return globs[fn_name] + + +def _adapted_fields(cls: Any) -> list[Attribute]: + annotations = get_annots(cls) + hints = get_full_type_hints(cls) + return [ + Attribute( + n, + NOTHING, + None, + False, + False, + False, + False, + False, + type=hints[n] if n in hints else annotations[n], + ) + for n, a in annotations.items() + ] + + +def _is_extensions_typeddict(cls) -> bool: + return cls.__class__ is _TypedDictMeta or ( + is_generic(cls) and (cls.__origin__.__class__ is _TypedDictMeta) + ) + + +if sys.version_info >= (3, 11): + + def _required_keys(cls: type) -> set[str]: + return cls.__required_keys__ + +else: + from typing_extensions import Annotated, NotRequired, get_args + + # Note that there is no `typing.Required` on 3.9 and 3.10, only in + # `typing_extensions`. Therefore, `typing.TypedDict` will not honor this + # annotation, only `typing_extensions.TypedDict`. + + def _required_keys(cls: type) -> set[str]: + """Our own processor for required keys.""" + if _is_extensions_typeddict(cls): + return cls.__required_keys__ + + # We vendor a part of the typing_extensions logic for + # gathering required keys. *sigh* + own_annotations = cls.__dict__.get("__annotations__", {}) + required_keys = set() + # On 3.9 - 3.10, typing.TypedDict doesn't put typeddict superclasses + # in the MRO, therefore we cannot handle non-required keys properly + # in some situations. Oh well. + for key in getattr(cls, "__required_keys__", []): + annotation_type = own_annotations[key] + annotation_origin = get_origin(annotation_type) + if annotation_origin is Annotated: + annotation_args = get_args(annotation_type) + if annotation_args: + annotation_type = annotation_args[0] + annotation_origin = get_origin(annotation_type) + + if annotation_origin is NotRequired: + pass + elif cls.__total__: + required_keys.add(key) + return required_keys diff --git a/server/libs/cattrs/literals.py b/server/libs/cattrs/literals.py new file mode 100644 index 0000000..badedda --- /dev/null +++ b/server/libs/cattrs/literals.py @@ -0,0 +1,11 @@ +from enum import Enum +from typing import Any + +from ._compat import is_literal + +__all__ = ["is_literal", "is_literal_containing_enums"] + + +def is_literal_containing_enums(type: Any) -> bool: + """Is this a literal containing at least one Enum?""" + return is_literal(type) and any(isinstance(val, Enum) for val in type.__args__) diff --git a/server/libs/cattrs/preconf/__init__.py b/server/libs/cattrs/preconf/__init__.py new file mode 100644 index 0000000..1b12ef9 --- /dev/null +++ b/server/libs/cattrs/preconf/__init__.py @@ -0,0 +1,55 @@ +import sys +from datetime import datetime +from enum import Enum +from typing import Any, Callable, TypeVar, get_args + +from .._compat import is_subclass +from ..converters import Converter, UnstructureHook +from ..fns import identity + +if sys.version_info[:2] < (3, 10): + from typing_extensions import ParamSpec +else: + from typing import ParamSpec + + +def validate_datetime(v, _): + if not isinstance(v, datetime): + raise Exception(f"Expected datetime, got {v}") + return v + + +T = TypeVar("T") +P = ParamSpec("P") + + +def wrap(_: Callable[P, Any]) -> Callable[[Callable[..., T]], Callable[P, T]]: + """Wrap a `Converter` `__init__` in a type-safe way.""" + + def impl(x: Callable[..., T]) -> Callable[P, T]: + return x + + return impl + + +def is_primitive_enum(type: Any, include_bare_enums: bool = False) -> bool: + """Is this a string or int enum that can be passed through?""" + return is_subclass(type, Enum) and ( + is_subclass(type, (str, int)) + or (include_bare_enums and type.mro()[1:] == Enum.mro()) + ) + + +def literals_with_enums_unstructure_factory( + typ: Any, converter: Converter +) -> UnstructureHook: + """An unstructure hook factory for literals containing enums. + + If all contained enums can be passed through (their unstructure hook is `identity`), + the entire literal can also be passed through. + """ + if all( + converter.get_unstructure_hook(type(arg)) == identity for arg in get_args(typ) + ): + return identity + return converter.unstructure diff --git a/server/libs/cattrs/preconf/bson.py b/server/libs/cattrs/preconf/bson.py new file mode 100644 index 0000000..4957489 --- /dev/null +++ b/server/libs/cattrs/preconf/bson.py @@ -0,0 +1,121 @@ +"""Preconfigured converters for bson.""" + +from base64 import b85decode, b85encode +from collections.abc import Set +from datetime import date, datetime +from typing import Any, TypeVar, Union + +from bson import DEFAULT_CODEC_OPTIONS, CodecOptions, Int64, ObjectId, decode, encode + +from .._compat import is_mapping, is_subclass +from ..cols import mapping_structure_factory +from ..converters import BaseConverter, Converter +from ..dispatch import StructureHook +from ..fns import identity +from ..literals import is_literal_containing_enums +from ..strategies import configure_union_passthrough +from . import ( + is_primitive_enum, + literals_with_enums_unstructure_factory, + validate_datetime, + wrap, +) + +T = TypeVar("T") + + +class Base85Bytes(bytes): + """A subclass to help with binary key encoding/decoding.""" + + +class BsonConverter(Converter): + def dumps( + self, + obj: Any, + unstructure_as: Any = None, + check_keys: bool = False, + codec_options: CodecOptions = DEFAULT_CODEC_OPTIONS, + ) -> bytes: + return encode( + self.unstructure(obj, unstructure_as=unstructure_as), + check_keys=check_keys, + codec_options=codec_options, + ) + + def loads( + self, + data: bytes, + cl: type[T], + codec_options: CodecOptions = DEFAULT_CODEC_OPTIONS, + ) -> T: + return self.structure(decode(data, codec_options=codec_options), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the bson library. + + * sets are serialized as lists + * byte mapping keys are base85-encoded into strings when unstructuring, and reverse + * non-string, non-byte mapping keys are coerced into strings when unstructuring + * a deserialization hook is registered for bson.ObjectId by default + * string and int enums are passed through when unstructuring + + .. versionchanged:: 24.2.0 + Enums are left to the library to unstructure, speeding them up. + """ + + def gen_unstructure_mapping(cl: Any, unstructure_to=None): + key_handler = str + args = getattr(cl, "__args__", None) + if args: + if is_subclass(args[0], str): + key_handler = None + elif is_subclass(args[0], bytes): + + def key_handler(k): + return b85encode(k).decode("utf8") + + return converter.gen_unstructure_mapping( + cl, unstructure_to=unstructure_to, key_handler=key_handler + ) + + def gen_structure_mapping(cl: Any) -> StructureHook: + args = getattr(cl, "__args__", None) + if args and is_subclass(args[0], bytes): + h = mapping_structure_factory(cl, converter, key_type=Base85Bytes) + else: + h = mapping_structure_factory(cl, converter) + return h + + converter.register_structure_hook(Base85Bytes, lambda v, _: b85decode(v)) + converter.register_unstructure_hook_factory(is_mapping, gen_unstructure_mapping) + converter.register_structure_hook_factory(is_mapping, gen_structure_mapping) + + converter.register_structure_hook(ObjectId, lambda v, _: ObjectId(v)) + configure_union_passthrough( + Union[str, bool, int, float, None, bytes, datetime, ObjectId, Int64], converter + ) + + # datetime inherits from date, so identity unstructure hook used + # here to prevent the date unstructure hook running. + converter.register_unstructure_hook(datetime, lambda v: v) + converter.register_structure_hook(datetime, validate_datetime) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + converter.register_unstructure_hook_func(is_primitive_enum, identity) + converter.register_unstructure_hook_factory( + is_literal_containing_enums, literals_with_enums_unstructure_factory + ) + + +@wrap(BsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> BsonConverter: + kwargs["unstruct_collection_overrides"] = { + Set: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = BsonConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/server/libs/cattrs/preconf/cbor2.py b/server/libs/cattrs/preconf/cbor2.py new file mode 100644 index 0000000..6341d89 --- /dev/null +++ b/server/libs/cattrs/preconf/cbor2.py @@ -0,0 +1,56 @@ +"""Preconfigured converters for cbor2.""" + +from collections.abc import Set +from datetime import date, datetime, timezone +from typing import Any, TypeVar, Union + +from cbor2 import dumps, loads + +from ..converters import BaseConverter, Converter +from ..fns import identity +from ..literals import is_literal_containing_enums +from ..strategies import configure_union_passthrough +from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap + +T = TypeVar("T") + + +class Cbor2Converter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T: + return self.structure(loads(data, **kwargs), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the cbor2 library. + + * datetimes are serialized as timestamp floats + * sets are serialized as lists + * string and int enums are passed through when unstructuring + """ + converter.register_unstructure_hook(datetime, lambda v: v.timestamp()) + converter.register_structure_hook( + datetime, lambda v, _: datetime.fromtimestamp(v, timezone.utc) + ) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + converter.register_unstructure_hook_func(is_primitive_enum, identity) + converter.register_unstructure_hook_factory( + is_literal_containing_enums, literals_with_enums_unstructure_factory + ) + configure_union_passthrough(Union[str, bool, int, float, None, bytes], converter) + + +@wrap(Cbor2Converter) +def make_converter(*args: Any, **kwargs: Any) -> Cbor2Converter: + kwargs["unstruct_collection_overrides"] = { + Set: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = Cbor2Converter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/server/libs/cattrs/preconf/json.py b/server/libs/cattrs/preconf/json.py new file mode 100644 index 0000000..199c574 --- /dev/null +++ b/server/libs/cattrs/preconf/json.py @@ -0,0 +1,69 @@ +"""Preconfigured converters for the stdlib json.""" + +from base64 import b85decode, b85encode +from collections.abc import Set +from datetime import date, datetime +from json import dumps, loads +from typing import Any, TypeVar, Union + +from .._compat import Counter +from ..converters import BaseConverter, Converter +from ..fns import identity +from ..literals import is_literal_containing_enums +from ..strategies import configure_union_passthrough +from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap + +__all__ = ["JsonConverter", "configure_converter", "make_converter"] + +T = TypeVar("T") + + +class JsonConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: Union[bytes, str], cl: type[T], **kwargs: Any) -> T: + return self.structure(loads(data, **kwargs), cl) + + +def configure_converter(converter: BaseConverter) -> None: + """ + Configure the converter for use with the stdlib json module. + + * bytes are serialized as base85 strings + * datetimes are serialized as ISO 8601 + * counters are serialized as dicts + * sets are serialized as lists + * string and int enums are passed through when unstructuring + * union passthrough is configured for unions of strings, bools, ints, + floats and None + + .. versionchanged:: 24.2.0 + Enums are left to the library to unstructure, speeding them up. + """ + converter.register_unstructure_hook( + bytes, lambda v: (b85encode(v) if v else b"").decode("utf8") + ) + converter.register_structure_hook(bytes, lambda v, _: b85decode(v)) + converter.register_unstructure_hook(datetime, lambda v: v.isoformat()) + converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v)) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + converter.register_unstructure_hook_factory( + is_literal_containing_enums, literals_with_enums_unstructure_factory + ) + converter.register_unstructure_hook_func(is_primitive_enum, identity) + configure_union_passthrough(Union[str, bool, int, float, None], converter) + + +@wrap(JsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> JsonConverter: + kwargs["unstruct_collection_overrides"] = { + Set: list, + Counter: dict, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = JsonConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/server/libs/cattrs/preconf/msgpack.py b/server/libs/cattrs/preconf/msgpack.py new file mode 100644 index 0000000..9287641 --- /dev/null +++ b/server/libs/cattrs/preconf/msgpack.py @@ -0,0 +1,65 @@ +"""Preconfigured converters for msgpack.""" + +from collections.abc import Set +from datetime import date, datetime, time, timezone +from typing import Any, TypeVar, Union + +from msgpack import dumps, loads + +from ..converters import BaseConverter, Converter +from ..fns import identity +from ..literals import is_literal_containing_enums +from ..strategies import configure_union_passthrough +from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap + +__all__ = ["MsgpackConverter", "configure_converter", "make_converter"] + +T = TypeVar("T") + + +class MsgpackConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T: + return self.structure(loads(data, **kwargs), cl) + + +def configure_converter(converter: BaseConverter) -> None: + """ + Configure the converter for use with the msgpack library. + + * datetimes are serialized as timestamp floats + * sets are serialized as lists + * string and int enums are passed through when unstructuring + + .. versionchanged:: 24.2.0 + Enums are left to the library to unstructure, speeding them up. + """ + converter.register_unstructure_hook(datetime, lambda v: v.timestamp()) + converter.register_structure_hook( + datetime, lambda v, _: datetime.fromtimestamp(v, timezone.utc) + ) + converter.register_unstructure_hook( + date, lambda v: datetime.combine(v, time(tzinfo=timezone.utc)).timestamp() + ) + converter.register_structure_hook( + date, lambda v, _: datetime.fromtimestamp(v, timezone.utc).date() + ) + converter.register_unstructure_hook_func(is_primitive_enum, identity) + converter.register_unstructure_hook_factory( + is_literal_containing_enums, literals_with_enums_unstructure_factory + ) + configure_union_passthrough(Union[str, bool, int, float, None, bytes], converter) + + +@wrap(MsgpackConverter) +def make_converter(*args: Any, **kwargs: Any) -> MsgpackConverter: + kwargs["unstruct_collection_overrides"] = { + Set: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = MsgpackConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/server/libs/cattrs/preconf/msgspec.py b/server/libs/cattrs/preconf/msgspec.py new file mode 100644 index 0000000..6274a32 --- /dev/null +++ b/server/libs/cattrs/preconf/msgspec.py @@ -0,0 +1,202 @@ +"""Preconfigured converters for msgspec.""" + +from __future__ import annotations + +from base64 import b64decode +from dataclasses import is_dataclass +from datetime import date, datetime +from enum import Enum +from functools import partial +from typing import Any, Callable, TypeVar, Union, get_type_hints + +from attrs import has as attrs_has +from attrs import resolve_types +from msgspec import Struct, convert, to_builtins +from msgspec.json import Encoder, decode + +from .._compat import fields, get_args, get_origin, is_bare, is_mapping, is_sequence +from ..cols import is_namedtuple +from ..converters import BaseConverter, Converter +from ..dispatch import UnstructureHook +from ..fns import identity +from ..gen import make_hetero_tuple_unstructure_fn +from ..literals import is_literal_containing_enums +from ..strategies import configure_union_passthrough +from . import literals_with_enums_unstructure_factory, wrap + +T = TypeVar("T") + +__all__ = ["MsgspecJsonConverter", "configure_converter", "make_converter"] + + +class MsgspecJsonConverter(Converter): + """A converter specialized for the _msgspec_ library.""" + + #: The msgspec encoder for dumping. + encoder: Encoder = Encoder() + + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes: + """Unstructure and encode `obj` into JSON bytes.""" + return self.encoder.encode( + self.unstructure(obj, unstructure_as=unstructure_as), **kwargs + ) + + def get_dumps_hook( + self, unstructure_as: Any, **kwargs: Any + ) -> Callable[[Any], bytes]: + """Produce a `dumps` hook for the given type.""" + unstruct_hook = self.get_unstructure_hook(unstructure_as) + if unstruct_hook in (identity, to_builtins): + return self.encoder.encode + return self.dumps + + def loads(self, data: bytes, cl: type[T], **kwargs: Any) -> T: + """Decode and structure `cl` from the provided JSON bytes.""" + return self.structure(decode(data, **kwargs), cl) + + def get_loads_hook(self, cl: type[T]) -> Callable[[bytes], T]: + """Produce a `loads` hook for the given type.""" + return partial(self.loads, cl=cl) + + +def configure_converter(converter: Converter) -> None: + """Configure the converter for the msgspec library. + + * bytes are serialized as base64 strings, directly by msgspec + * datetimes and dates are passed through to be serialized as RFC 3339 directly + * enums are passed through to msgspec directly + * union passthrough configured for str, bool, int, float and None + * bare, string and int enums are passed through when unstructuring + + .. versionchanged:: 24.2.0 + Enums are left to the library to unstructure, speeding them up. + """ + configure_passthroughs(converter) + + converter.register_unstructure_hook(Struct, to_builtins) + converter.register_unstructure_hook(Enum, identity) + + converter.register_structure_hook(Struct, convert) + converter.register_structure_hook(bytes, lambda v, _: b64decode(v)) + converter.register_structure_hook(datetime, lambda v, _: convert(v, datetime)) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + converter.register_unstructure_hook_factory( + is_literal_containing_enums, literals_with_enums_unstructure_factory + ) + configure_union_passthrough(Union[str, bool, int, float, None], converter) + + +@wrap(MsgspecJsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> MsgspecJsonConverter: + res = MsgspecJsonConverter(*args, **kwargs) + configure_converter(res) + return res + + +def configure_passthroughs(converter: Converter) -> None: + """Configure optimizing passthroughs. + + A passthrough is when we let msgspec handle something automatically. + + .. versionchanged:: 25.1.0 + Dataclasses with private attributes are now passed through. + """ + converter.register_unstructure_hook(bytes, to_builtins) + converter.register_unstructure_hook_factory(is_mapping, mapping_unstructure_factory) + converter.register_unstructure_hook_factory(is_sequence, seq_unstructure_factory) + converter.register_unstructure_hook_factory( + attrs_has, msgspec_attrs_unstructure_factory + ) + converter.register_unstructure_hook_factory( + is_dataclass, + partial(msgspec_attrs_unstructure_factory, msgspec_skips_private=False), + ) + converter.register_unstructure_hook_factory( + is_namedtuple, namedtuple_unstructure_factory + ) + + +def seq_unstructure_factory(type, converter: Converter) -> UnstructureHook: + """The msgspec unstructure hook factory for sequences.""" + if is_bare(type): + type_arg = Any + else: + args = get_args(type) + type_arg = args[0] + handler = converter.get_unstructure_hook(type_arg, cache_result=False) + + if handler in (identity, to_builtins): + return handler + return converter.gen_unstructure_iterable(type) + + +def mapping_unstructure_factory(type, converter: BaseConverter) -> UnstructureHook: + """The msgspec unstructure hook factory for mappings.""" + if is_bare(type): + key_arg = Any + val_arg = Any + key_handler = converter.get_unstructure_hook(key_arg, cache_result=False) + value_handler = converter.get_unstructure_hook(val_arg, cache_result=False) + else: + args = get_args(type) + if len(args) == 2: + key_arg, val_arg = args + else: + # Probably a Counter + key_arg, val_arg = args, Any + key_handler = converter.get_unstructure_hook(key_arg, cache_result=False) + value_handler = converter.get_unstructure_hook(val_arg, cache_result=False) + + if key_handler in (identity, to_builtins) and value_handler in ( + identity, + to_builtins, + ): + return to_builtins + return converter.gen_unstructure_mapping(type) + + +def msgspec_attrs_unstructure_factory( + type: Any, converter: Converter, msgspec_skips_private: bool = True +) -> UnstructureHook: + """Choose whether to use msgspec handling or our own. + + Args: + msgspec_skips_private: Whether the msgspec library skips unstructuring + private attributes, making us do the work. + """ + origin = get_origin(type) + attribs = fields(origin or type) + if attrs_has(type) and any(isinstance(a.type, str) for a in attribs): + resolve_types(type) + attribs = fields(origin or type) + + if msgspec_skips_private and any( + attr.name.startswith("_") + or ( + converter.get_unstructure_hook(attr.type, cache_result=False) + not in (identity, to_builtins) + ) + for attr in attribs + ): + return converter.gen_unstructure_attrs_fromdict(type) + + return to_builtins + + +def namedtuple_unstructure_factory( + type: type[tuple], converter: BaseConverter +) -> UnstructureHook: + """A hook factory for unstructuring namedtuples, modified for msgspec.""" + + if all( + converter.get_unstructure_hook(t) in (identity, to_builtins) + for t in get_type_hints(type).values() + ): + return identity + + return make_hetero_tuple_unstructure_fn( + type, + converter, + unstructure_to=tuple, + type_args=tuple(get_type_hints(type).values()), + ) diff --git a/server/libs/cattrs/preconf/orjson.py b/server/libs/cattrs/preconf/orjson.py new file mode 100644 index 0000000..0726ef0 --- /dev/null +++ b/server/libs/cattrs/preconf/orjson.py @@ -0,0 +1,108 @@ +"""Preconfigured converters for orjson.""" + +from base64 import b85decode, b85encode +from collections.abc import Set +from datetime import date, datetime +from enum import Enum +from functools import partial +from typing import Any, TypeVar, Union + +from orjson import dumps, loads + +from .._compat import is_subclass +from ..cols import is_mapping, is_namedtuple, namedtuple_unstructure_factory +from ..converters import Converter +from ..fns import identity +from ..literals import is_literal_containing_enums +from ..strategies import configure_union_passthrough +from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap + +__all__ = ["OrjsonConverter", "configure_converter", "make_converter"] + +T = TypeVar("T") + + +class OrjsonConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> bytes: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: Union[bytes, bytearray, memoryview, str], cl: type[T]) -> T: + return self.structure(loads(data), cl) + + +def configure_converter(converter: Converter) -> None: + """ + Configure the converter for use with the orjson library. + + * bytes are serialized as base85 strings + * datetimes and dates are passed through to be serialized as RFC 3339 by orjson + * typed namedtuples are serialized as lists + * sets are serialized as lists + * string enum mapping keys have special handling + * mapping keys are coerced into strings when unstructuring + * bare, string and int enums are passed through when unstructuring + + .. versionchanged:: 24.1.0 + Add support for typed namedtuples. + .. versionchanged:: 24.2.0 + Enums are left to the library to unstructure, speeding them up. + """ + converter.register_unstructure_hook( + bytes, lambda v: (b85encode(v) if v else b"").decode("utf8") + ) + converter.register_structure_hook(bytes, lambda v, _: b85decode(v)) + + converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v)) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + + def unstructure_mapping_factory(cl: Any, unstructure_to=None): + key_handler = str + args = getattr(cl, "__args__", None) + if args: + if is_subclass(args[0], str) and is_subclass(args[0], Enum): + + def key_handler(v): + return v.value + + else: + # It's possible the handler for the key type has been overridden. + # (For example base85 encoding for bytes.) + # In that case, we want to use the override. + + kh = converter.get_unstructure_hook(args[0]) + if kh != identity: + key_handler = kh + + return converter.gen_unstructure_mapping( + cl, unstructure_to=unstructure_to, key_handler=key_handler + ) + + converter._unstructure_func.register_func_list( + [ + (is_mapping, unstructure_mapping_factory, True), + ( + is_namedtuple, + partial(namedtuple_unstructure_factory, unstructure_to=tuple), + "extended", + ), + ] + ) + converter.register_unstructure_hook_func( + partial(is_primitive_enum, include_bare_enums=True), identity + ) + converter.register_unstructure_hook_factory( + is_literal_containing_enums, literals_with_enums_unstructure_factory + ) + configure_union_passthrough(Union[str, bool, int, float, None], converter) + + +@wrap(OrjsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> OrjsonConverter: + kwargs["unstruct_collection_overrides"] = { + Set: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = OrjsonConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/server/libs/cattrs/preconf/pyyaml.py b/server/libs/cattrs/preconf/pyyaml.py new file mode 100644 index 0000000..b1b8854 --- /dev/null +++ b/server/libs/cattrs/preconf/pyyaml.py @@ -0,0 +1,74 @@ +"""Preconfigured converters for pyyaml.""" + +from datetime import date, datetime +from functools import partial +from typing import Any, TypeVar, Union + +from yaml import safe_dump, safe_load + +from .._compat import FrozenSetSubscriptable +from ..cols import is_namedtuple, namedtuple_unstructure_factory +from ..converters import BaseConverter, Converter +from ..strategies import configure_union_passthrough +from . import validate_datetime, wrap + +__all__ = ["PyyamlConverter", "configure_converter", "make_converter"] + +T = TypeVar("T") + + +def validate_date(v: Any, _): + if not isinstance(v, date): + raise ValueError(f"Expected date, got {v}") + return v + + +class PyyamlConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str: + return safe_dump(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: str, cl: type[T]) -> T: + return self.structure(safe_load(data), cl) + + +def configure_converter(converter: BaseConverter) -> None: + """ + Configure the converter for use with the pyyaml library. + + * frozensets are serialized as lists + * string enums are converted into strings explicitly + * datetimes and dates are validated + * typed namedtuples are serialized as lists + + .. versionchanged:: 24.1.0 + Add support for typed namedtuples. + """ + converter.register_unstructure_hook( + str, lambda v: v if v.__class__ is str else v.value + ) + + # datetime inherits from date, so identity unstructure hook used + # here to prevent the date unstructure hook running. + converter.register_unstructure_hook(datetime, lambda v: v) + converter.register_structure_hook(datetime, validate_datetime) + converter.register_structure_hook(date, validate_date) + + converter.register_unstructure_hook_factory(is_namedtuple)( + partial(namedtuple_unstructure_factory, unstructure_to=tuple) + ) + + configure_union_passthrough( + Union[str, bool, int, float, None, bytes, datetime, date], converter + ) + + +@wrap(PyyamlConverter) +def make_converter(*args: Any, **kwargs: Any) -> PyyamlConverter: + kwargs["unstruct_collection_overrides"] = { + FrozenSetSubscriptable: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = PyyamlConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/server/libs/cattrs/preconf/tomlkit.py b/server/libs/cattrs/preconf/tomlkit.py new file mode 100644 index 0000000..802df9b --- /dev/null +++ b/server/libs/cattrs/preconf/tomlkit.py @@ -0,0 +1,89 @@ +"""Preconfigured converters for tomlkit.""" + +from base64 import b85decode, b85encode +from collections.abc import Set +from datetime import date, datetime +from enum import Enum +from operator import attrgetter +from typing import Any, TypeVar, Union + +from tomlkit import dumps, loads +from tomlkit.items import Float, Integer, String + +from .._compat import is_mapping, is_subclass +from ..converters import BaseConverter, Converter +from ..strategies import configure_union_passthrough +from . import validate_datetime, wrap + +__all__ = ["TomlkitConverter", "configure_converter", "make_converter"] + +T = TypeVar("T") +_enum_value_getter = attrgetter("_value_") + + +class TomlkitConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: str, cl: type[T]) -> T: + return self.structure(loads(data), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the tomlkit library. + + * bytes are serialized as base85 strings + * sets are serialized as lists + * tuples are serializas as lists + * mapping keys are coerced into strings when unstructuring + """ + converter.register_structure_hook(bytes, lambda v, _: b85decode(v)) + converter.register_unstructure_hook( + bytes, lambda v: (b85encode(v) if v else b"").decode("utf8") + ) + + def gen_unstructure_mapping(cl: Any, unstructure_to=None): + key_handler = str + args = getattr(cl, "__args__", None) + if args: + # Currently, tomlkit has inconsistent behavior on 3.11 + # so we paper over it here. + # https://github.com/sdispater/tomlkit/issues/237 + if is_subclass(args[0], str): + key_handler = _enum_value_getter if is_subclass(args[0], Enum) else None + elif is_subclass(args[0], bytes): + + def key_handler(k: bytes): + return b85encode(k).decode("utf8") + + return converter.gen_unstructure_mapping( + cl, unstructure_to=unstructure_to, key_handler=key_handler + ) + + converter._unstructure_func.register_func_list( + [(is_mapping, gen_unstructure_mapping, True)] + ) + + # datetime inherits from date, so identity unstructure hook used + # here to prevent the date unstructure hook running. + converter.register_unstructure_hook(datetime, lambda v: v) + converter.register_structure_hook(datetime, validate_datetime) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + configure_union_passthrough( + Union[str, String, bool, int, Integer, float, Float], converter + ) + + +@wrap(TomlkitConverter) +def make_converter(*args: Any, **kwargs: Any) -> TomlkitConverter: + kwargs["unstruct_collection_overrides"] = { + Set: list, + tuple: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = TomlkitConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/server/libs/cattrs/preconf/ujson.py b/server/libs/cattrs/preconf/ujson.py new file mode 100644 index 0000000..8f33061 --- /dev/null +++ b/server/libs/cattrs/preconf/ujson.py @@ -0,0 +1,66 @@ +"""Preconfigured converters for ujson.""" + +from base64 import b85decode, b85encode +from collections.abc import Set +from datetime import date, datetime +from typing import Any, AnyStr, TypeVar, Union + +from ujson import dumps, loads + +from ..converters import BaseConverter, Converter +from ..fns import identity +from ..literals import is_literal_containing_enums +from ..strategies import configure_union_passthrough +from . import is_primitive_enum, literals_with_enums_unstructure_factory, wrap + +__all__ = ["UjsonConverter", "configure_converter", "make_converter"] + +T = TypeVar("T") + + +class UjsonConverter(Converter): + def dumps(self, obj: Any, unstructure_as: Any = None, **kwargs: Any) -> str: + return dumps(self.unstructure(obj, unstructure_as=unstructure_as), **kwargs) + + def loads(self, data: AnyStr, cl: type[T], **kwargs: Any) -> T: + return self.structure(loads(data, **kwargs), cl) + + +def configure_converter(converter: BaseConverter): + """ + Configure the converter for use with the ujson library. + + * bytes are serialized as base64 strings + * datetimes are serialized as ISO 8601 + * sets are serialized as lists + * string and int enums are passed through when unstructuring + + .. versionchanged:: 24.2.0 + Enums are left to the library to unstructure, speeding them up. + """ + converter.register_unstructure_hook( + bytes, lambda v: (b85encode(v) if v else b"").decode("utf8") + ) + converter.register_structure_hook(bytes, lambda v, _: b85decode(v)) + + converter.register_unstructure_hook(datetime, lambda v: v.isoformat()) + converter.register_structure_hook(datetime, lambda v, _: datetime.fromisoformat(v)) + converter.register_unstructure_hook(date, lambda v: v.isoformat()) + converter.register_structure_hook(date, lambda v, _: date.fromisoformat(v)) + converter.register_unstructure_hook_func(is_primitive_enum, identity) + converter.register_unstructure_hook_factory( + is_literal_containing_enums, literals_with_enums_unstructure_factory + ) + configure_union_passthrough(Union[str, bool, int, float, None], converter) + + +@wrap(UjsonConverter) +def make_converter(*args: Any, **kwargs: Any) -> UjsonConverter: + kwargs["unstruct_collection_overrides"] = { + Set: list, + **kwargs.get("unstruct_collection_overrides", {}), + } + res = UjsonConverter(*args, **kwargs) + configure_converter(res) + + return res diff --git a/server/libs/cattrs/py.typed b/server/libs/cattrs/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/cattrs/strategies/__init__.py b/server/libs/cattrs/strategies/__init__.py new file mode 100644 index 0000000..9caf073 --- /dev/null +++ b/server/libs/cattrs/strategies/__init__.py @@ -0,0 +1,12 @@ +"""High level strategies for converters.""" + +from ._class_methods import use_class_methods +from ._subclasses import include_subclasses +from ._unions import configure_tagged_union, configure_union_passthrough + +__all__ = [ + "configure_tagged_union", + "configure_union_passthrough", + "include_subclasses", + "use_class_methods", +] diff --git a/server/libs/cattrs/strategies/_class_methods.py b/server/libs/cattrs/strategies/_class_methods.py new file mode 100644 index 0000000..80a3c90 --- /dev/null +++ b/server/libs/cattrs/strategies/_class_methods.py @@ -0,0 +1,64 @@ +"""Strategy for using class-specific (un)structuring methods.""" + +from inspect import signature +from typing import Any, Callable, Optional, TypeVar + +from .. import BaseConverter + +T = TypeVar("T") + + +def use_class_methods( + converter: BaseConverter, + structure_method_name: Optional[str] = None, + unstructure_method_name: Optional[str] = None, +) -> None: + """ + Configure the converter such that dedicated methods are used for (un)structuring + the instance of a class if such methods are available. The default (un)structuring + will be applied if such an (un)structuring methods cannot be found. + + :param converter: The `Converter` on which this strategy is applied. You can use + :class:`cattrs.BaseConverter` or any other derived class. + :param structure_method_name: Optional string with the name of the class method + which should be used for structuring. If not provided, no class method will be + used for structuring. + :param unstructure_method_name: Optional string with the name of the class method + which should be used for unstructuring. If not provided, no class method will + be used for unstructuring. + + If you want to (un)structured nested objects, just append a converter parameter + to your (un)structuring methods and you will receive the converter there. + + .. versionadded:: 23.2.0 + """ + + if structure_method_name: + + def make_class_method_structure(cl: type[T]) -> Callable[[Any, type[T]], T]: + fn = getattr(cl, structure_method_name) + n_parameters = len(signature(fn).parameters) + if n_parameters == 1: + return lambda v, _: fn(v) + if n_parameters == 2: + return lambda v, _: fn(v, converter) + raise TypeError("Provide a class method with one or two arguments.") + + converter.register_structure_hook_factory( + lambda t: hasattr(t, structure_method_name), make_class_method_structure + ) + + if unstructure_method_name: + + def make_class_method_unstructure(cl: type[T]) -> Callable[[T], T]: + fn = getattr(cl, unstructure_method_name) + n_parameters = len(signature(fn).parameters) + if n_parameters == 1: + return fn + if n_parameters == 2: + return lambda self_: fn(self_, converter) + raise TypeError("Provide a method with no or one argument.") + + converter.register_unstructure_hook_factory( + lambda t: hasattr(t, unstructure_method_name), make_class_method_unstructure + ) diff --git a/server/libs/cattrs/strategies/_subclasses.py b/server/libs/cattrs/strategies/_subclasses.py new file mode 100644 index 0000000..483a226 --- /dev/null +++ b/server/libs/cattrs/strategies/_subclasses.py @@ -0,0 +1,243 @@ +"""Strategies for customizing subclass behaviors.""" + +from __future__ import annotations + +import typing +from gc import collect +from typing import Any, Callable, TypeVar, Union + +from ..converters import BaseConverter +from ..gen import AttributeOverride, make_dict_structure_fn, make_dict_unstructure_fn +from ..gen._consts import already_generating + + +def _make_subclasses_tree(cl: type) -> list[type]: + # get class origin for accessing subclasses (see #648 for more info) + cls_origin = typing.get_origin(cl) or cl + return [cl] + [ + sscl + for scl in cls_origin.__subclasses__() + for sscl in _make_subclasses_tree(scl) + ] + + +def _has_subclasses(cl: type, given_subclasses: tuple[type, ...]) -> bool: + """Whether the given class has subclasses from `given_subclasses`.""" + actual = set(cl.__subclasses__()) + given = set(given_subclasses) + return bool(actual & given) + + +def _get_union_type(cl: type, given_subclasses_tree: tuple[type]) -> type | None: + actual_subclass_tree = tuple(_make_subclasses_tree(cl)) + class_tree = tuple(set(actual_subclass_tree) & set(given_subclasses_tree)) + return Union[class_tree] if len(class_tree) >= 2 else None + + +C = TypeVar("C", bound=BaseConverter) + + +def include_subclasses( + cl: type, + converter: C, + subclasses: tuple[type, ...] | None = None, + union_strategy: Callable[[Any, C], Any] | None = None, + overrides: dict[str, AttributeOverride] | None = None, +) -> None: + """ + Configure the converter so that the attrs/dataclass `cl` is un/structured as if it + was a union of itself and all its subclasses that are defined at the time when this + strategy is applied. + + :param cl: A base `attrs` or `dataclass` class. + :param converter: The `Converter` on which this strategy is applied. Do note that + the strategy does not work for a :class:`cattrs.BaseConverter`. + :param subclasses: A tuple of sublcasses whose ancestor is `cl`. If left as `None`, + subclasses are detected using recursively the `__subclasses__` method of `cl` + and its descendents. + :param union_strategy: A callable of two arguments passed by position + (`subclass_union`, `converter`) that defines the union strategy to use to + disambiguate the subclasses union. If `None` (the default), the automatic unique + field disambiguation is used which means that every single subclass + participating in the union must have an attribute name that does not exist in + any other sibling class. + :param overrides: a mapping of `cl` attribute names to overrides (instantiated with + :func:`cattrs.gen.override`) to customize un/structuring. + + .. versionadded:: 23.1.0 + .. versionchanged:: 24.1.0 + When overrides are not provided, hooks for individual classes are retrieved from + the converter instead of generated with no overrides, using converter defaults. + """ + # Due to https://github.com/python-attrs/attrs/issues/1047 + collect() + if subclasses is not None: + parent_subclass_tree = (cl, *subclasses) + else: + parent_subclass_tree = tuple(_make_subclasses_tree(cl)) + + if union_strategy is None: + _include_subclasses_without_union_strategy( + cl, converter, parent_subclass_tree, overrides + ) + else: + _include_subclasses_with_union_strategy( + converter, parent_subclass_tree, union_strategy, overrides + ) + + +def _include_subclasses_without_union_strategy( + cl, + converter: BaseConverter, + parent_subclass_tree: tuple[type, ...], + overrides: dict[str, AttributeOverride] | None, +): + # The iteration approach is required if subclasses are more than one level deep: + for cl in parent_subclass_tree: + # We re-create a reduced union type to handle the following case: + # + # converter.structure(d, as=Child) + # + # In the above, the `as=Child` argument will be transformed to a union type of + # itself and its subtypes, that way we guarantee that the returned object will + # not be the parent. + subclass_union = _get_union_type(cl, parent_subclass_tree) + + def cls_is_cl(cls, _cl=cl): + return cls is _cl + + if overrides is not None: + base_struct_hook = make_dict_structure_fn(cl, converter, **overrides) + base_unstruct_hook = make_dict_unstructure_fn(cl, converter, **overrides) + else: + base_struct_hook = converter.get_structure_hook(cl) + base_unstruct_hook = converter.get_unstructure_hook(cl) + + if subclass_union is None: + + def struct_hook(val: dict, _, _cl=cl, _base_hook=base_struct_hook) -> cl: + return _base_hook(val, _cl) + + else: + dis_fn = converter._get_dis_func(subclass_union, overrides=overrides) + + def struct_hook( + val: dict, + _, + _c=converter, + _cl=cl, + _base_hook=base_struct_hook, + _dis_fn=dis_fn, + ) -> cl: + """ + If val is disambiguated to the class `cl`, use its base hook. + + If val is disambiguated to a subclass, dispatch on its exact runtime + type. + """ + dis_cl = _dis_fn(val) + if dis_cl is _cl: + return _base_hook(val, _cl) + return _c.structure(val, dis_cl) + + def unstruct_hook( + val: parent_subclass_tree[0], + _c=converter, + _cl=cl, + _base_hook=base_unstruct_hook, + ) -> dict: + """ + If val is an instance of the class `cl`, use the hook. + + If val is an instance of a subclass, dispatch on its exact runtime type. + """ + if val.__class__ is _cl: + return _base_hook(val) + return _c.unstructure(val, unstructure_as=val.__class__) + + # This needs to use function dispatch, using singledispatch will again + # match A and all subclasses, which is not what we want. + converter.register_structure_hook_func(cls_is_cl, struct_hook) + converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook) + + +def _include_subclasses_with_union_strategy( + converter: C, + union_classes: tuple[type, ...], + union_strategy: Callable[[Any, C], Any], + overrides: dict[str, AttributeOverride] | None, +): + """ + This function is tricky because we're dealing with what is essentially a circular + reference. + + We need to generate a structure hook for a class that is both: + * specific for that particular class and its own fields + * but should handle specific functions for all its descendants too + + Hence the dance with registering below. + """ + + parent_classes = [cl for cl in union_classes if _has_subclasses(cl, union_classes)] + if not parent_classes: + return + + original_unstruct_hooks = {} + original_struct_hooks = {} + for cl in union_classes: + # In the first pass, every class gets its own unstructure function according to + # the overrides. + # We just generate the hooks, and do not register them. This allows us to + # manipulate the _already_generating set to force runtime dispatch. + already_generating.working_set = set(union_classes) - {cl} + try: + if overrides is not None: + unstruct_hook = make_dict_unstructure_fn(cl, converter, **overrides) + struct_hook = make_dict_structure_fn(cl, converter, **overrides) + else: + unstruct_hook = converter.get_unstructure_hook(cl, cache_result=False) + struct_hook = converter.get_structure_hook(cl, cache_result=False) + finally: + already_generating.working_set = set() + original_unstruct_hooks[cl] = unstruct_hook + original_struct_hooks[cl] = struct_hook + + # Now that's done, we can register all the hooks and generate the + # union handler. The union handler needs them. + final_union = Union[union_classes] # type: ignore + + for cl, hook in original_unstruct_hooks.items(): + + def cls_is_cl(cls, _cl=cl): + return cls is _cl + + converter.register_unstructure_hook_func(cls_is_cl, hook) + + for cl, hook in original_struct_hooks.items(): + + def cls_is_cl(cls, _cl=cl): + return cls is _cl + + converter.register_structure_hook_func(cls_is_cl, hook) + + union_strategy(final_union, converter) + unstruct_hook = converter.get_unstructure_hook(final_union) + struct_hook = converter.get_structure_hook(final_union) + + for cl in union_classes: + # In the second pass, we overwrite the hooks with the union hook. + + def cls_is_cl(cls, _cl=cl): + return cls is _cl + + converter.register_unstructure_hook_func(cls_is_cl, unstruct_hook) + subclasses = tuple([c for c in union_classes if issubclass(c, cl)]) + if len(subclasses) > 1: + u = Union[subclasses] # type: ignore + union_strategy(u, converter) + struct_hook = converter.get_structure_hook(u) + + def sh(payload: dict, _, _u=u, _s=struct_hook) -> cl: + return _s(payload, _u) + + converter.register_structure_hook_func(cls_is_cl, sh) diff --git a/server/libs/cattrs/strategies/_unions.py b/server/libs/cattrs/strategies/_unions.py new file mode 100644 index 0000000..57e132d --- /dev/null +++ b/server/libs/cattrs/strategies/_unions.py @@ -0,0 +1,264 @@ +from collections import defaultdict +from typing import Any, Callable, Union + +from attrs import NOTHING, NothingType + +from .. import BaseConverter +from .._compat import get_newtype_base, is_literal, is_subclass, is_union_type +from ..typealiases import is_type_alias + +__all__ = [ + "configure_tagged_union", + "configure_union_passthrough", + "default_tag_generator", +] + + +def default_tag_generator(typ: type) -> str: + """Return the class name.""" + return typ.__name__ + + +def configure_tagged_union( + union: Any, + converter: BaseConverter, + tag_generator: Callable[[type], str] = default_tag_generator, + tag_name: str = "_type", + default: Union[type, NothingType] = NOTHING, +) -> None: + """ + Configure the converter so that `union` (which should be a union, or a type alias + of one) is un/structured with the help of an additional piece of data in the + unstructured payload, the tag. + + :param converter: The converter to apply the strategy to. + :param tag_generator: A `tag_generator` function is used to map each + member of the union to a tag, which is then included in the + unstructured payload. The default tag generator returns the name of + the class. + :param tag_name: The key under which the tag will be set in the + unstructured payload. By default, `'_type'`. + :param default: An optional class to be used if the tag information + is not present when structuring. + + The tagged union strategy currently only works with the dict + un/structuring base strategy. + + .. versionadded:: 23.1.0 + + .. versionchanged:: 25.1 + Type aliases of unions are now also supported. + """ + if is_type_alias(union): + union = union.__value__ + args = union.__args__ + tag_to_hook = {} + exact_cl_unstruct_hooks = {} + for cl in args: + tag = tag_generator(cl) + struct_handler = converter.get_structure_hook(cl) + unstruct_handler = converter.get_unstructure_hook(cl) + + def structure_union_member(val: dict, _cl=cl, _h=struct_handler) -> cl: + return _h(val, _cl) + + def unstructure_union_member(val: union, _h=unstruct_handler) -> dict: + return _h(val) + + tag_to_hook[tag] = structure_union_member + exact_cl_unstruct_hooks[cl] = unstructure_union_member + + cl_to_tag = {cl: tag_generator(cl) for cl in args} + + if default is not NOTHING: + default_handler = converter.get_structure_hook(default) + + def structure_default(val: dict, _cl=default, _h=default_handler): + return _h(val, _cl) + + tag_to_hook = defaultdict(lambda: structure_default, tag_to_hook) + cl_to_tag = defaultdict(lambda: default, cl_to_tag) + + def unstructure_tagged_union( + val: union, + _exact_cl_unstruct_hooks=exact_cl_unstruct_hooks, + _cl_to_tag=cl_to_tag, + _tag_name=tag_name, + ) -> dict: + res = _exact_cl_unstruct_hooks[val.__class__](val) + res[_tag_name] = _cl_to_tag[val.__class__] + return res + + if default is NOTHING: + if getattr(converter, "forbid_extra_keys", False): + + def structure_tagged_union( + val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name + ) -> union: + val = val.copy() + return _tag_to_cl[val.pop(_tag_name)](val) + + else: + + def structure_tagged_union( + val: dict, _, _tag_to_cl=tag_to_hook, _tag_name=tag_name + ) -> union: + return _tag_to_cl[val[_tag_name]](val) + + else: + if getattr(converter, "forbid_extra_keys", False): + + def structure_tagged_union( + val: dict, + _, + _tag_to_hook=tag_to_hook, + _tag_name=tag_name, + _dh=default_handler, + _default=default, + ) -> union: + if _tag_name in val: + val = val.copy() + return _tag_to_hook[val.pop(_tag_name)](val) + return _dh(val, _default) + + else: + + def structure_tagged_union( + val: dict, + _, + _tag_to_hook=tag_to_hook, + _tag_name=tag_name, + _dh=default_handler, + _default=default, + ) -> union: + if _tag_name in val: + return _tag_to_hook[val[_tag_name]](val) + return _dh(val, _default) + + converter.register_unstructure_hook(union, unstructure_tagged_union) + converter.register_structure_hook(union, structure_tagged_union) + + +def configure_union_passthrough(union: Any, converter: BaseConverter) -> None: + """ + Configure the converter to support validating and passing through unions of the + provided types and their subsets. + + For example, all mature JSON libraries natively support producing unions of ints, + floats, Nones, and strings. Using this strategy, a converter can be configured + to efficiently validate and pass through unions containing these types. + + The most important point is that another library (in this example the JSON + library) handles producing the union, and the converter is configured to just + validate it. + + Literals of provided types are also supported, and are checked by value. + + NewTypes of provided types are also supported. + + The strategy is designed to be O(1) in execution time, and independent of the + ordering of types in the union. + + If the union contains a class and one or more of its subclasses, the subclasses + will also be included when validating the superclass. + + .. versionadded:: 23.2.0 + """ + args = set(union.__args__) + + def make_structure_native_union(exact_type: Any) -> Callable: + # `exact_type` is likely to be a subset of the entire configured union (`args`). + literal_values = { + v for t in exact_type.__args__ if is_literal(t) for v in t.__args__ + } + + # We have no idea what the actual type of `val` will be, so we can't + # use it blindly with an `in` check since it might not be hashable. + # So we do an additional check when handling literals. + # Note: do no use `literal_values` here, since {0, False} gets reduced to {0} + literal_classes = { + v.__class__ + for t in exact_type.__args__ + if is_literal(t) + for v in t.__args__ + } + + non_literal_classes = { + get_newtype_base(t) or t + for t in exact_type.__args__ + if not is_literal(t) and ((get_newtype_base(t) or t) in args) + } + + # We augment the set of allowed classes with any configured subclasses of + # the exact subclasses. + non_literal_classes |= { + a for a in args if any(is_subclass(a, c) for c in non_literal_classes) + } + + # We check for spillover - union types not handled by the strategy. + # If spillover exists and we fail to validate our types, we call + # further into the converter with the rest. + spillover = { + a + for a in exact_type.__args__ + if (get_newtype_base(a) or a) not in non_literal_classes + and not is_literal(a) + } + + if spillover: + spillover_type = ( + Union[tuple(spillover)] if len(spillover) > 1 else next(iter(spillover)) + ) + + def structure_native_union( + val: Any, + _: Any, + classes=non_literal_classes, + vals=literal_values, + converter=converter, + spillover=spillover_type, + ) -> exact_type: + if val.__class__ in literal_classes and val in vals: + return val + if val.__class__ in classes: + return val + return converter.structure(val, spillover) + + else: + + def structure_native_union( + val: Any, _: Any, classes=non_literal_classes, vals=literal_values + ) -> exact_type: + if val.__class__ in literal_classes and val in vals: + return val + if val.__class__ in classes: + return val + raise TypeError(f"{val} ({val.__class__}) not part of {_}") + + return structure_native_union + + def contains_native_union(exact_type: Any) -> bool: + """Can we handle this type?""" + if is_union_type(exact_type): + type_args = set(exact_type.__args__) + # We special case optionals, since they are very common + # and are handled a little more efficiently by default. + if len(type_args) == 2 and type(None) in type_args: + return False + + literal_classes = { + lit_arg.__class__ + for t in type_args + if is_literal(t) + for lit_arg in t.__args__ + } + non_literal_types = { + get_newtype_base(t) or t for t in type_args if not is_literal(t) + } + + return (literal_classes | non_literal_types) & args + return False + + converter.register_structure_hook_factory( + contains_native_union, make_structure_native_union + ) diff --git a/server/libs/cattrs/typealiases.py b/server/libs/cattrs/typealiases.py new file mode 100644 index 0000000..d3a20c4 --- /dev/null +++ b/server/libs/cattrs/typealiases.py @@ -0,0 +1,57 @@ +"""Utilities for type aliases.""" + +from __future__ import annotations + +import sys +from typing import TYPE_CHECKING, Any + +from ._compat import is_generic +from ._generics import deep_copy_with +from .dispatch import StructureHook +from .gen._generics import generate_mapping + +if TYPE_CHECKING: + from .converters import BaseConverter + +__all__ = ["get_type_alias_base", "is_type_alias", "type_alias_structure_factory"] + +if sys.version_info >= (3, 12): + from types import GenericAlias + from typing import TypeAliasType + + def is_type_alias(type: Any) -> bool: + """Is this a PEP 695 type alias?""" + return isinstance( + type.__origin__ if type.__class__ is GenericAlias else type, TypeAliasType + ) + +else: + + def is_type_alias(type: Any) -> bool: + """Is this a PEP 695 type alias?""" + return False + + +def get_type_alias_base(type: Any) -> Any: + """ + What is this a type alias of? + + Works only on 3.12+. + """ + return type.__value__ + + +def type_alias_structure_factory(type: Any, converter: BaseConverter) -> StructureHook: + base = get_type_alias_base(type) + if is_generic(type): + mapping = generate_mapping(type) + if base.__name__ in mapping: + # Probably just type T = T + base = mapping[base.__name__] + else: + base = deep_copy_with(base, mapping) + res = converter.get_structure_hook(base) + if res == converter._structure_call: + # we need to replace the type arg of `structure_call` + return lambda v, _, __base=base: __base(v) + return lambda v, _, __base=base: res(v, __base) diff --git a/server/libs/cattrs/types.py b/server/libs/cattrs/types.py new file mode 100644 index 0000000..a864cb9 --- /dev/null +++ b/server/libs/cattrs/types.py @@ -0,0 +1,12 @@ +from typing import Protocol, TypeVar + +__all__ = ["SimpleStructureHook"] + +In = TypeVar("In") +T = TypeVar("T") + + +class SimpleStructureHook(Protocol[In, T]): + """A structure hook with an optional (ignored) second argument.""" + + def __call__(self, _: In, /, cl=...) -> T: ... diff --git a/server/libs/cattrs/v.py b/server/libs/cattrs/v.py new file mode 100644 index 0000000..134c990 --- /dev/null +++ b/server/libs/cattrs/v.py @@ -0,0 +1,106 @@ +"""Cattrs validation.""" + +from typing import Callable, Union + +from .errors import ( + ClassValidationError, + ForbiddenExtraKeysError, + IterableValidationError, +) + +__all__ = ["format_exception", "transform_error"] + + +def format_exception(exc: BaseException, type: Union[type, None]) -> str: + """The default exception formatter, handling the most common exceptions. + + The following exceptions are handled specially: + + * `KeyErrors` (`required field missing`) + * `ValueErrors` (`invalid value for type, expected ` or just `invalid value`) + * `TypeErrors` (`invalid value for type, expected ` and a couple special + cases for iterables) + * `cattrs.ForbiddenExtraKeysError` + * some `AttributeErrors` (special cased for structing mappings) + """ + if isinstance(exc, KeyError): + res = "required field missing" + elif isinstance(exc, ValueError): + if type is not None: + tn = type.__name__ if hasattr(type, "__name__") else repr(type) + res = f"invalid value for type, expected {tn}" + else: + res = "invalid value" + elif isinstance(exc, TypeError): + if type is None: + if exc.args[0].endswith("object is not iterable"): + res = "invalid value for type, expected an iterable" + else: + res = f"invalid type ({exc})" + else: + tn = type.__name__ if hasattr(type, "__name__") else repr(type) + res = f"invalid value for type, expected {tn}" + elif isinstance(exc, ForbiddenExtraKeysError): + res = f"extra fields found ({', '.join(exc.extra_fields)})" + elif isinstance(exc, AttributeError) and exc.args[0].endswith( + "object has no attribute 'items'" + ): + # This was supposed to be a mapping (and have .items()) but it something else. + res = "expected a mapping" + else: + res = f"unknown error ({exc})" + + return res + + +def transform_error( + exc: Union[ClassValidationError, IterableValidationError, BaseException], + path: str = "$", + format_exception: Callable[ + [BaseException, Union[type, None]], str + ] = format_exception, +) -> list[str]: + """Transform an exception into a list of error messages. + + To get detailed error messages, the exception should be produced by a converter + with `detailed_validation` set. + + By default, the error messages are in the form of `{description} @ {path}`. + + While traversing the exception and subexceptions, the path is formed: + + * by appending `.{field_name}` for fields in classes + * by appending `[{int}]` for indices in iterables, like lists + * by appending `[{str}]` for keys in mappings, like dictionaries + + :param exc: The exception to transform into error messages. + :param path: The root path to use. + :param format_exception: A callable to use to transform `Exceptions` into + string descriptions of errors. + + .. versionadded:: 23.1.0 + """ + errors = [] + if isinstance(exc, IterableValidationError): + with_notes, without = exc.group_exceptions() + for exc, note in with_notes: + p = f"{path}[{note.index!r}]" + if isinstance(exc, (ClassValidationError, IterableValidationError)): + errors.extend(transform_error(exc, p, format_exception)) + else: + errors.append(f"{format_exception(exc, note.type)} @ {p}") + for exc in without: + errors.append(f"{format_exception(exc, None)} @ {path}") + elif isinstance(exc, ClassValidationError): + with_notes, without = exc.group_exceptions() + for exc, note in with_notes: + p = f"{path}.{note.name}" + if isinstance(exc, (ClassValidationError, IterableValidationError)): + errors.extend(transform_error(exc, p, format_exception)) + else: + errors.append(f"{format_exception(exc, note.type)} @ {p}") + for exc in without: + errors.append(f"{format_exception(exc, None)} @ {path}") + else: + errors.append(f"{format_exception(exc, None)} @ {path}") + return errors diff --git a/server/libs/lsprotocol-2023.0.1.dist-info/INSTALLER b/server/libs/lsprotocol-2023.0.1.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/server/libs/lsprotocol-2023.0.1.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/server/libs/lsprotocol-2023.0.1.dist-info/LICENSE b/server/libs/lsprotocol-2023.0.1.dist-info/LICENSE new file mode 100644 index 0000000..9e841e7 --- /dev/null +++ b/server/libs/lsprotocol-2023.0.1.dist-info/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/server/libs/lsprotocol-2023.0.1.dist-info/METADATA b/server/libs/lsprotocol-2023.0.1.dist-info/METADATA new file mode 100644 index 0000000..eb5f7d6 --- /dev/null +++ b/server/libs/lsprotocol-2023.0.1.dist-info/METADATA @@ -0,0 +1,65 @@ +Metadata-Version: 2.1 +Name: lsprotocol +Version: 2023.0.1 +Summary: Python implementation of the Language Server Protocol. +Author-email: Microsoft Corporation +Maintainer-email: Brett Cannon , Karthik Nadig +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Dist: attrs>=21.3.0 +Requires-Dist: cattrs!=23.2.1 +Project-URL: Issues, https://github.com/microsoft/lsprotocol/issues +Project-URL: Source, https://github.com/microsoft/lsprotocol + +# Language Server Protocol Types implementation for Python + +`lsprotocol` is a python implementation of object types used in the Language Server Protocol (LSP). This repository contains the code generator and the generated types for LSP. + +## Overview + +LSP is used by editors to communicate with various tools to enables services like code completion, documentation on hover, formatting, code analysis, etc. The intent of this library is to allow you to build on top of the types used by LSP. This repository will be kept up to date with the latest version of LSP as it is updated. + +## Installation + +`python -m pip install lsprotocol` + +## Usage + +### Using LSP types + +```python +from lsprotocol import types + +position = types.Position(line=10, character=3) +``` + +### Using built-in type converters + +```python +# test.py +import json +from lsprotocol import converters, types + +position = types.Position(line=10, character=3) +converter = converters.get_converter() +print(json.dumps(converter.unstructure(position, unstructure_as=types.Position))) +``` + +Output: + +```console +> python test.py +{"line": 10, "character": 3} +``` + diff --git a/server/libs/lsprotocol-2023.0.1.dist-info/RECORD b/server/libs/lsprotocol-2023.0.1.dist-info/RECORD new file mode 100644 index 0000000..7edecce --- /dev/null +++ b/server/libs/lsprotocol-2023.0.1.dist-info/RECORD @@ -0,0 +1,17 @@ +lsprotocol-2023.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +lsprotocol-2023.0.1.dist-info/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141 +lsprotocol-2023.0.1.dist-info/METADATA,sha256=oh7M_V0nCX-lx8MCik5z0_J8Wyd7ApJtdl30wWs4Tb8,2237 +lsprotocol-2023.0.1.dist-info/RECORD,, +lsprotocol-2023.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +lsprotocol-2023.0.1.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81 +lsprotocol/__init__.py,sha256=zoT6Do2JtGHGb7pOeKpahg4ocXIsSpyowjhOrhUhx8g,94 +lsprotocol/__pycache__/__init__.cpython-311.pyc,, +lsprotocol/__pycache__/_hooks.cpython-311.pyc,, +lsprotocol/__pycache__/converters.cpython-311.pyc,, +lsprotocol/__pycache__/types.cpython-311.pyc,, +lsprotocol/__pycache__/validators.cpython-311.pyc,, +lsprotocol/_hooks.py,sha256=PCTq4Ve_dDd02DMcWQ8afu9gj_oyX0B4nDOomPghqYs,41570 +lsprotocol/converters.py,sha256=404tQOVoZL31R9CBrDe6Gx9Nok5cRph3glKlnSx00fo,433 +lsprotocol/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +lsprotocol/types.py,sha256=nZYiI5ZvHEBkRYjtPd8tpqe0n2NpmdaCZ2RLwHxoMLs,454735 +lsprotocol/validators.py,sha256=5UMUmWhk52_Ps66_KFydjNkMfLNUsPH3wmV0Buv645s,1420 diff --git a/server/libs/lsprotocol-2023.0.1.dist-info/REQUESTED b/server/libs/lsprotocol-2023.0.1.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/lsprotocol-2023.0.1.dist-info/WHEEL b/server/libs/lsprotocol-2023.0.1.dist-info/WHEEL new file mode 100644 index 0000000..3b5e64b --- /dev/null +++ b/server/libs/lsprotocol-2023.0.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.9.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/server/libs/lsprotocol/__init__.py b/server/libs/lsprotocol/__init__.py new file mode 100644 index 0000000..5b7f7a9 --- /dev/null +++ b/server/libs/lsprotocol/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. diff --git a/server/libs/lsprotocol/_hooks.py b/server/libs/lsprotocol/_hooks.py new file mode 100644 index 0000000..3f51ce2 --- /dev/null +++ b/server/libs/lsprotocol/_hooks.py @@ -0,0 +1,1237 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. +import sys +from typing import Any, List, Optional, Tuple, Union + +import attrs +import cattrs + +from . import types as lsp_types + +LSPAny = lsp_types.LSPAny +OptionalPrimitive = Optional[Union[bool, int, str, float]] + +# Flag to ensure we only resolve forward references once. +_resolved_forward_references = False + + +def _resolve_forward_references() -> None: + """Resolve forward references for faster processing with cattrs.""" + global _resolved_forward_references + if not _resolved_forward_references: + + def _filter(p: Tuple[str, object]) -> bool: + return isinstance(p[1], type) and attrs.has(p[1]) + + # Creating a concrete list here because `resolve_types` mutates the provided map. + items = list(filter(_filter, lsp_types.ALL_TYPES_MAP.items())) + for _, value in items: + if isinstance(value, type): + attrs.resolve_types(value, lsp_types.ALL_TYPES_MAP, {}) # type: ignore + _resolved_forward_references = True + + +def register_hooks(converter: cattrs.Converter) -> cattrs.Converter: + _resolve_forward_references() + converter = _register_capabilities_hooks(converter) + converter = _register_required_structure_hooks(converter) + return _register_custom_property_hooks(converter) + + +def _register_capabilities_hooks(converter: cattrs.Converter) -> cattrs.Converter: + def _text_document_sync_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.TextDocumentSyncOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.TextDocumentSyncOptions) + + def _notebook_document_sync_hook( + object_: Any, _: type + ) -> Optional[ + Union[ + lsp_types.NotebookDocumentSyncRegistrationOptions, + lsp_types.NotebookDocumentSyncOptions, + ] + ]: + if object_ is None: + return None + if "id" in object_: + return converter.structure( + object_, lsp_types.NotebookDocumentSyncRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.NotebookDocumentSyncOptions) + + def _hover_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.HoverOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.HoverOptions) + + def _declaration_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.DeclarationRegistrationOptions, + lsp_types.DeclarationOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.DeclarationRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.DeclarationOptions) + + def _definition_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.DefinitionOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.DefinitionOptions) + + def _type_definition_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.TypeDefinitionRegistrationOptions, + lsp_types.TypeDefinitionOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.TypeDefinitionRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.TypeDefinitionOptions) + + def _implementation_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.ImplementationRegistrationOptions, + lsp_types.ImplementationOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.ImplementationRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.ImplementationOptions) + + def _references_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.ReferenceOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.ReferenceOptions) + + def _position_encoding_hook( + object_: Union[lsp_types.PositionEncodingKind, OptionalPrimitive], _: type + ) -> Union[lsp_types.PositionEncodingKind, OptionalPrimitive]: + return object_ + + def _document_highlight_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.DocumentHighlightOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.DocumentHighlightOptions) + + def _document_symbol_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.DocumentSymbolOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.DocumentSymbolOptions) + + def _code_action_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.CodeActionOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.CodeActionOptions) + + def _color_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.DocumentColorRegistrationOptions, + lsp_types.DocumentColorOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.DocumentColorRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.DocumentColorOptions) + + def _workspace_symbol_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.WorkspaceSymbolOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.WorkspaceSymbolOptions) + + def _document_formatting_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.DocumentFormattingOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.DocumentFormattingOptions) + + def _document_range_formatting_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.DocumentRangeFormattingOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.DocumentRangeFormattingOptions) + + def _rename_provider_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.RenameOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.RenameOptions) + + def _folding_range_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.FoldingRangeRegistrationOptions, + lsp_types.FoldingRangeOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.FoldingRangeRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.FoldingRangeOptions) + + def _selection_range_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.SelectionRangeRegistrationOptions, + lsp_types.SelectionRangeOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.SelectionRangeRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.SelectionRangeOptions) + + def _call_hierarchy_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.CallHierarchyRegistrationOptions, + lsp_types.CallHierarchyOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.CallHierarchyRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.CallHierarchyOptions) + + def _linked_editing_range_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.LinkedEditingRangeRegistrationOptions, + lsp_types.LinkedEditingRangeOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.LinkedEditingRangeRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.LinkedEditingRangeOptions) + + def _semantic_tokens_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.SemanticTokensRegistrationOptions, + lsp_types.SemanticTokensOptions, + ]: + if object_ is None: + return None + if "id" in object_: + return converter.structure( + object_, lsp_types.SemanticTokensRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.SemanticTokensOptions) + + def _moniker_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.MonikerRegistrationOptions, + lsp_types.MonikerOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure(object_, lsp_types.MonikerRegistrationOptions) + else: + return converter.structure(object_, lsp_types.MonikerOptions) + + def _type_hierarchy_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.TypeHierarchyRegistrationOptions, + lsp_types.TypeHierarchyOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.TypeHierarchyRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.TypeHierarchyOptions) + + def _inline_value_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.InlineValueRegistrationOptions, + lsp_types.InlineValueOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure( + object_, lsp_types.InlineValueRegistrationOptions + ) + else: + return converter.structure(object_, lsp_types.InlineValueOptions) + + def _inlay_hint_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.InlayHintRegistrationOptions, + lsp_types.InlayHintOptions, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if "id" in object_: + return converter.structure(object_, lsp_types.InlayHintRegistrationOptions) + else: + return converter.structure(object_, lsp_types.InlayHintOptions) + + def _inlay_hint_label_part_hook( + object_: Any, _: type + ) -> Union[str, List[lsp_types.InlayHintLabelPart]]: + if isinstance(object_, str): + return object_ + + return [ + converter.structure(item, lsp_types.InlayHintLabelPart) for item in object_ + ] + + def _diagnostic_provider_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.DiagnosticRegistrationOptions, + lsp_types.DiagnosticOptions, + ]: + if object_ is None: + return None + if "id" in object_: + return converter.structure(object_, lsp_types.DiagnosticRegistrationOptions) + else: + return converter.structure(object_, lsp_types.DiagnosticOptions) + + def _save_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.SaveOptions]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.SaveOptions) + + def _code_action_hook( + object_: Any, _: type + ) -> Union[lsp_types.Command, lsp_types.CodeAction]: + if "command" in object_: + return converter.structure(object_, lsp_types.Command) + else: + return converter.structure(object_, lsp_types.CodeAction) + + def _completion_list_hook( + object_: Any, _: type + ) -> Optional[Union[lsp_types.CompletionList, List[lsp_types.CompletionItem]]]: + if object_ is None: + return None + if isinstance(object_, list): + return [ + converter.structure(item, lsp_types.CompletionItem) for item in object_ + ] + else: + return converter.structure(object_, lsp_types.CompletionList) + + def _location_hook( + object_: Any, _: type + ) -> Optional[ + Union[ + lsp_types.Location, + List[lsp_types.Location], + List[lsp_types.LocationLink], + ] + ]: + if object_ is None: + return None + if isinstance(object_, list): + if len(object_) == 0: + return [] + if "targetUri" in object_[0]: + return [ + converter.structure(item, lsp_types.LocationLink) + for item in object_ + ] + else: + return [ + converter.structure(item, lsp_types.Location) for item in object_ + ] + else: + return converter.structure(object_, lsp_types.Location) + + def _symbol_hook( + object_: Any, _: type + ) -> Optional[ + Union[List[lsp_types.DocumentSymbol], List[lsp_types.SymbolInformation]] + ]: + if object_ is None: + return None + if isinstance(object_, list): + if len(object_) == 0: + return [] + if "location" in object_[0]: + return [ + converter.structure(item, lsp_types.SymbolInformation) + for item in object_ + ] + else: + return [ + converter.structure(item, lsp_types.DocumentSymbol) + for item in object_ + ] + else: + return None + + def _markup_content_hook( + object_: Any, _: type + ) -> Optional[ + Union[ + OptionalPrimitive, + lsp_types.MarkupContent, + lsp_types.MarkedString_Type1, + List[Union[OptionalPrimitive, lsp_types.MarkedString_Type1]], + ] + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + if isinstance(object_, list): + return [ + ( + item + if isinstance(item, (bool, int, str, float)) + else converter.structure(item, lsp_types.MarkedString_Type1) + ) + for item in object_ + ] + if "kind" in object_: + return converter.structure(object_, lsp_types.MarkupContent) + else: + return converter.structure(object_, lsp_types.MarkedString_Type1) + + def _document_edit_hook( + object_: Any, _: type + ) -> Optional[ + Union[ + lsp_types.TextDocumentEdit, + lsp_types.CreateFile, + lsp_types.RenameFile, + lsp_types.DeleteFile, + ] + ]: + if object_ is None: + return None + if "kind" in object_: + if object_["kind"] == "create": + return converter.structure(object_, lsp_types.CreateFile) + elif object_["kind"] == "rename": + return converter.structure(object_, lsp_types.RenameFile) + elif object_["kind"] == "delete": + return converter.structure(object_, lsp_types.DeleteFile) + else: + raise ValueError("Unknown edit kind: ", object_) + else: + return converter.structure(object_, lsp_types.TextDocumentEdit) + + def _semantic_tokens_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.SemanticTokensOptionsFullType1]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.SemanticTokensOptionsFullType1) + + def _semantic_tokens_capabilities_hook( + object_: Any, _: type + ) -> Union[ + OptionalPrimitive, + lsp_types.SemanticTokensClientCapabilitiesRequestsTypeFullType1, + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure( + object_, lsp_types.SemanticTokensClientCapabilitiesRequestsTypeFullType1 + ) + + def _code_action_kind_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.CodeActionKind]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.CodeActionKind) + + def _position_encoding_kind_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.PositionEncodingKind]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.PositionEncodingKind) + + def _folding_range_kind_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.FoldingRangeKind]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.FoldingRangeKind) + + def _semantic_token_types_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.SemanticTokenTypes]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.SemanticTokenTypes) + + def _semantic_token_modifiers_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.SemanticTokenModifiers]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.SemanticTokenModifiers) + + def _watch_kind_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.WatchKind]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.WatchKind) + + def _notebook_sync_option_selector_hook( + object_: Any, _: type + ) -> Union[ + lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType1, + lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType2, + ]: + if "notebook" in object_: + return converter.structure( + object_, lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType1 + ) + else: + return converter.structure( + object_, lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType2 + ) + + def _semantic_token_registration_options_hook( + object_: Any, _: type + ) -> Optional[ + Union[OptionalPrimitive, lsp_types.SemanticTokensRegistrationOptionsFullType1] + ]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure( + object_, lsp_types.SemanticTokensRegistrationOptionsFullType1 + ) + + def _inline_completion_provider_hook( + object_: Any, _: type + ) -> Optional[Union[OptionalPrimitive, lsp_types.InlineCompletionOptions]]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.InlineCompletionOptions) + + def _inline_completion_list_hook( + object_: Any, _: type + ) -> Optional[ + Union[lsp_types.InlineCompletionList, List[lsp_types.InlineCompletionItem]] + ]: + if object_ is None: + return None + if isinstance(object_, list): + return [ + converter.structure(item, lsp_types.InlineCompletionItem) + for item in object_ + ] + return converter.structure(object_, lsp_types.InlineCompletionList) + + def _string_value_hook( + object_: Any, _: type + ) -> Union[OptionalPrimitive, lsp_types.StringValue]: + if object_ is None: + return None + if isinstance(object_, (bool, int, str, float)): + return object_ + return converter.structure(object_, lsp_types.StringValue) + + def _symbol_list_hook( + object_: Any, _: type + ) -> Optional[ + Union[List[lsp_types.SymbolInformation], List[lsp_types.WorkspaceSymbol]] + ]: + if object_ is None: + return None + assert isinstance(object_, list) + if len(object_) == 0: + return [] + if "deprecated" in object_[0]: + return [ + converter.structure(item, lsp_types.SymbolInformation) + for item in object_ + ] + elif ("data" in object_[0]) or ("range" not in object_[0]["location"]): + return [ + converter.structure(item, lsp_types.WorkspaceSymbol) for item in object_ + ] + + return [ + converter.structure(item, lsp_types.SymbolInformation) for item in object_ + ] + + def _notebook_sync_registration_option_selector_hook( + object_: Any, _: type + ) -> Union[ + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1, + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2, + ]: + if "notebook" in object_: + return converter.structure( + object_, + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1, + ) + else: + return converter.structure( + object_, + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2, + ) + + structure_hooks = [ + ( + Optional[ + Union[lsp_types.TextDocumentSyncOptions, lsp_types.TextDocumentSyncKind] + ], + _text_document_sync_hook, + ), + ( + Optional[ + Union[ + lsp_types.NotebookDocumentSyncOptions, + lsp_types.NotebookDocumentSyncRegistrationOptions, + ] + ], + _notebook_document_sync_hook, + ), + (Optional[Union[bool, lsp_types.HoverOptions]], _hover_provider_hook), + ( + Optional[ + Union[ + bool, + lsp_types.DeclarationOptions, + lsp_types.DeclarationRegistrationOptions, + ] + ], + _declaration_provider_hook, + ), + (Optional[Union[bool, lsp_types.DefinitionOptions]], _definition_provider_hook), + ( + Optional[ + Union[ + bool, + lsp_types.TypeDefinitionOptions, + lsp_types.TypeDefinitionRegistrationOptions, + ] + ], + _type_definition_provider_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.ImplementationOptions, + lsp_types.ImplementationRegistrationOptions, + ] + ], + _implementation_provider_hook, + ), + (Optional[Union[bool, lsp_types.ReferenceOptions]], _references_provider_hook), + ( + Optional[Union[bool, lsp_types.DocumentHighlightOptions]], + _document_highlight_provider_hook, + ), + ( + Optional[Union[bool, lsp_types.DocumentSymbolOptions]], + _document_symbol_provider_hook, + ), + ( + Optional[Union[bool, lsp_types.CodeActionOptions]], + _code_action_provider_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.DocumentColorOptions, + lsp_types.DocumentColorRegistrationOptions, + ] + ], + _color_provider_hook, + ), + ( + Optional[Union[bool, lsp_types.WorkspaceSymbolOptions]], + _workspace_symbol_provider_hook, + ), + ( + Optional[Union[bool, lsp_types.DocumentFormattingOptions]], + _document_formatting_provider_hook, + ), + ( + Optional[Union[bool, lsp_types.DocumentRangeFormattingOptions]], + _document_range_formatting_provider_hook, + ), + (Optional[Union[bool, lsp_types.RenameOptions]], _rename_provider_hook), + ( + Optional[ + Union[ + bool, + lsp_types.FoldingRangeOptions, + lsp_types.FoldingRangeRegistrationOptions, + ] + ], + _folding_range_provider_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.SelectionRangeOptions, + lsp_types.SelectionRangeRegistrationOptions, + ] + ], + _selection_range_provider_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.CallHierarchyOptions, + lsp_types.CallHierarchyRegistrationOptions, + ] + ], + _call_hierarchy_provider_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.LinkedEditingRangeOptions, + lsp_types.LinkedEditingRangeRegistrationOptions, + ] + ], + _linked_editing_range_provider_hook, + ), + ( + Optional[ + Union[ + lsp_types.SemanticTokensOptions, + lsp_types.SemanticTokensRegistrationOptions, + ] + ], + _semantic_tokens_provider_hook, + ), + ( + Optional[ + Union[ + bool, lsp_types.MonikerOptions, lsp_types.MonikerRegistrationOptions + ] + ], + _moniker_provider_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.TypeHierarchyOptions, + lsp_types.TypeHierarchyRegistrationOptions, + ] + ], + _type_hierarchy_provider_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.InlineValueOptions, + lsp_types.InlineValueRegistrationOptions, + ] + ], + _inline_value_provider_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.InlayHintOptions, + lsp_types.InlayHintRegistrationOptions, + ] + ], + _inlay_hint_provider_hook, + ), + ( + Union[str, List[lsp_types.InlayHintLabelPart]], + _inlay_hint_label_part_hook, + ), + ( + Optional[ + Union[ + lsp_types.DiagnosticOptions, lsp_types.DiagnosticRegistrationOptions + ] + ], + _diagnostic_provider_hook, + ), + ( + Optional[Union[lsp_types.SaveOptions, bool]], + _save_hook, + ), + ( + Union[lsp_types.Command, lsp_types.CodeAction], + _code_action_hook, + ), + ( + Optional[Union[List[lsp_types.CompletionItem], lsp_types.CompletionList]], + _completion_list_hook, + ), + ( + Optional[ + Union[ + lsp_types.Location, + List[lsp_types.Location], + List[lsp_types.LocationLink], + ] + ], + _location_hook, + ), + ( + Optional[ + Union[List[lsp_types.SymbolInformation], List[lsp_types.DocumentSymbol]] + ], + _symbol_hook, + ), + ( + Union[ + lsp_types.MarkupContent, + str, + lsp_types.MarkedString_Type1, + List[Union[str, lsp_types.MarkedString_Type1]], + ], + _markup_content_hook, + ), + ( + Union[ + lsp_types.TextDocumentEdit, + lsp_types.CreateFile, + lsp_types.RenameFile, + lsp_types.DeleteFile, + ], + _document_edit_hook, + ), + ( + Optional[Union[bool, lsp_types.SemanticTokensOptionsFullType1]], + _semantic_tokens_hook, + ), + ( + Optional[ + Union[ + bool, + lsp_types.SemanticTokensClientCapabilitiesRequestsTypeFullType1, + ] + ], + _semantic_tokens_capabilities_hook, + ), + ( + Optional[Union[str, lsp_types.MarkupContent]], + _markup_content_hook, + ), + ( + Optional[Union[lsp_types.CodeActionKind, str]], + _code_action_kind_hook, + ), + ( + Union[lsp_types.CodeActionKind, str], + _code_action_kind_hook, + ), + ( + Union[lsp_types.PositionEncodingKind, str], + _position_encoding_kind_hook, + ), + ( + Optional[Union[lsp_types.FoldingRangeKind, str]], + _folding_range_kind_hook, + ), + ( + Union[lsp_types.FoldingRangeKind, str], + _folding_range_kind_hook, + ), + ( + Union[lsp_types.SemanticTokenTypes, str], + _semantic_token_types_hook, + ), + ( + Optional[Union[lsp_types.SemanticTokenTypes, str]], + _semantic_token_types_hook, + ), + ( + Union[lsp_types.SemanticTokenModifiers, str], + _semantic_token_modifiers_hook, + ), + ( + Optional[Union[lsp_types.SemanticTokenModifiers, str]], + _semantic_token_modifiers_hook, + ), + ( + Union[lsp_types.WatchKind, int], + _watch_kind_hook, + ), + ( + Optional[Union[lsp_types.WatchKind, int]], + _watch_kind_hook, + ), + ( + Union[ + lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType1, + lsp_types.NotebookDocumentSyncOptionsNotebookSelectorType2, + ], + _notebook_sync_option_selector_hook, + ), + ( + Optional[ + Union[ + lsp_types.PositionEncodingKind, + str, + ] + ], + _position_encoding_hook, + ), + ( + Optional[Union[bool, lsp_types.SemanticTokensRegistrationOptionsFullType1]], + _semantic_token_registration_options_hook, + ), + ( + Optional[Union[bool, lsp_types.InlineCompletionOptions]], + _inline_completion_provider_hook, + ), + ( + Optional[ + Union[ + lsp_types.InlineCompletionList, List[lsp_types.InlineCompletionItem] + ] + ], + _inline_completion_list_hook, + ), + ( + Union[str, lsp_types.StringValue], + _string_value_hook, + ), + ( + Optional[ + Union[ + List[lsp_types.SymbolInformation], List[lsp_types.WorkspaceSymbol] + ] + ], + _symbol_list_hook, + ), + ( + Union[ + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1, + lsp_types.NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2, + ], + _notebook_sync_registration_option_selector_hook, + ), + ] + for type_, hook in structure_hooks: + converter.register_structure_hook(type_, hook) + return converter + + +def _register_required_structure_hooks( + converter: cattrs.Converter, +) -> cattrs.Converter: + def _lsp_object_hook(object_: Any, type_: type) -> Any: + return object_ + + def _parameter_information_label_hook( + object_: Any, type: type + ) -> Union[str, Tuple[int, int]]: + if isinstance(object_, str): + return object_ + else: + return (int(object_[0]), int(object_[1])) + + def _text_document_filter_hook( + object_: Any, _: type + ) -> Union[ + str, + lsp_types.TextDocumentFilter_Type1, + lsp_types.TextDocumentFilter_Type2, + lsp_types.TextDocumentFilter_Type3, + lsp_types.NotebookCellTextDocumentFilter, + ]: + if isinstance(object_, str): + return str(object_) + elif "notebook" in object_: + return converter.structure( + object_, lsp_types.NotebookCellTextDocumentFilter + ) + elif "language" in object_: + return converter.structure(object_, lsp_types.TextDocumentFilter_Type1) + elif "scheme" in object_: + return converter.structure(object_, lsp_types.TextDocumentFilter_Type2) + else: + return converter.structure(object_, lsp_types.TextDocumentFilter_Type3) + + def _notebook_filter_hook( + object_: Any, _: type + ) -> Union[ + str, + lsp_types.NotebookDocumentFilter_Type1, + lsp_types.NotebookDocumentFilter_Type2, + lsp_types.NotebookDocumentFilter_Type3, + ]: + if isinstance(object_, str): + return str(object_) + elif "notebookType" in object_: + return converter.structure(object_, lsp_types.NotebookDocumentFilter_Type1) + elif "scheme" in object_: + return converter.structure(object_, lsp_types.NotebookDocumentFilter_Type2) + else: + return converter.structure(object_, lsp_types.NotebookDocumentFilter_Type3) + + # TODO: Remove the ignore after this issue with attrs is addressed in either attrs or mypy + NotebookSelectorItem = attrs.fields( + lsp_types.NotebookCellTextDocumentFilter + ).notebook.type + STRUCTURE_HOOKS = [ + (type(None), lambda object_, _type: object_), + (Optional[Union[int, str]], lambda object_, _type: object_), + (Union[int, str], lambda object_, _type: object_), + (lsp_types.LSPAny, _lsp_object_hook), + (Optional[Union[str, bool]], lambda object_, _type: object_), + (Optional[Union[bool, Any]], lambda object_, _type: object_), + ( + Union[ + lsp_types.TextDocumentFilter_Type1, + lsp_types.TextDocumentFilter_Type2, + lsp_types.TextDocumentFilter_Type3, + lsp_types.NotebookCellTextDocumentFilter, + ], + _text_document_filter_hook, + ), + (lsp_types.DocumentFilter, _text_document_filter_hook), + ( + Union[ + str, + lsp_types.NotebookDocumentFilter_Type1, + lsp_types.NotebookDocumentFilter_Type2, + lsp_types.NotebookDocumentFilter_Type3, + ], + _notebook_filter_hook, + ), + (NotebookSelectorItem, _notebook_filter_hook), + ( + Union[lsp_types.LSPObject, List["LSPAny"], str, int, float, bool, None], + _lsp_object_hook, + ), + ( + Union[ + lsp_types.LSPObject, List[lsp_types.LSPAny], str, int, float, bool, None + ], + _lsp_object_hook, + ), + ( + Union[str, Tuple[int, int]], + _parameter_information_label_hook, + ), + (lsp_types.LSPObject, _lsp_object_hook), + ] + + if sys.version_info > (3, 8): + STRUCTURE_HOOKS += [ + ( + Union[ + lsp_types.LSPObject, + List[ + Union[ + lsp_types.LSPObject, + List["LSPAny"], + str, + int, + float, + bool, + None, + ] + ], + str, + int, + float, + bool, + None, + ], + _lsp_object_hook, + ) + ] + + for type_, hook in STRUCTURE_HOOKS: + converter.register_structure_hook(type_, hook) + + return converter + + +def _register_custom_property_hooks(converter: cattrs.Converter) -> cattrs.Converter: + def _to_camel_case(name: str) -> str: + # TODO: when min Python becomes >= 3.9, then update this to: + # `return name.removesuffix("_")`. + new_name = name[:-1] if name.endswith("_") else name + parts = new_name.split("_") + return parts[0] + "".join(p.title() for p in parts[1:]) + + def _omit(cls: type, prop: str) -> bool: + special = lsp_types.is_special_property(cls, prop) + return not special + + def _with_custom_unstructure(cls: type) -> Any: + attributes = { + a.name: cattrs.gen.override( + rename=_to_camel_case(a.name), + omit_if_default=_omit(cls, a.name), + ) + for a in attrs.fields(cls) + } + return cattrs.gen.make_dict_unstructure_fn(cls, converter, **attributes) + + def _with_custom_structure(cls: type) -> Any: + attributes = { + a.name: cattrs.gen.override( + rename=_to_camel_case(a.name), + omit_if_default=_omit(cls, a.name), + ) + for a in attrs.fields(cls) + } + return cattrs.gen.make_dict_structure_fn(cls, converter, **attributes) + + converter.register_unstructure_hook_factory(attrs.has, _with_custom_unstructure) + converter.register_structure_hook_factory(attrs.has, _with_custom_structure) + return converter diff --git a/server/libs/lsprotocol/converters.py b/server/libs/lsprotocol/converters.py new file mode 100644 index 0000000..db12c65 --- /dev/null +++ b/server/libs/lsprotocol/converters.py @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from typing import Optional + +import cattrs + +from . import _hooks + + +def get_converter( + converter: Optional[cattrs.Converter] = None, +) -> cattrs.Converter: + """Adds cattrs hooks for LSP lsp_types to the given converter.""" + if converter is None: + converter = cattrs.Converter() + return _hooks.register_hooks(converter) diff --git a/server/libs/lsprotocol/py.typed b/server/libs/lsprotocol/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/lsprotocol/types.py b/server/libs/lsprotocol/types.py new file mode 100644 index 0000000..98b2d4b --- /dev/null +++ b/server/libs/lsprotocol/types.py @@ -0,0 +1,12898 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +# ****** THIS IS A GENERATED FILE, DO NOT EDIT. ****** +# Steps to generate: +# 1. Checkout https://github.com/microsoft/lsprotocol +# 2. Install nox: `python -m pip install nox` +# 3. Run command: `python -m nox --session build_lsp` + +import enum +import functools +from typing import Any, Dict, List, Optional, Tuple, Union + +import attrs + +from . import validators + +__lsp_version__ = "3.17.0" + + +@enum.unique +class SemanticTokenTypes(str, enum.Enum): + """A set of predefined token types. This set is not fixed + an clients can specify additional token types via the + corresponding client capabilities. + + @since 3.16.0""" + + # Since: 3.16.0 + Namespace = "namespace" + Type = "type" + """Represents a generic type. Acts as a fallback for types which can't be mapped to + a specific type like class or enum.""" + Class = "class" + Enum = "enum" + Interface = "interface" + Struct = "struct" + TypeParameter = "typeParameter" + Parameter = "parameter" + Variable = "variable" + Property = "property" + EnumMember = "enumMember" + Event = "event" + Function = "function" + Method = "method" + Macro = "macro" + Keyword = "keyword" + Modifier = "modifier" + Comment = "comment" + String = "string" + Number = "number" + Regexp = "regexp" + Operator = "operator" + Decorator = "decorator" + """@since 3.17.0""" + # Since: 3.17.0 + + +@enum.unique +class SemanticTokenModifiers(str, enum.Enum): + """A set of predefined token modifiers. This set is not fixed + an clients can specify additional token types via the + corresponding client capabilities. + + @since 3.16.0""" + + # Since: 3.16.0 + Declaration = "declaration" + Definition = "definition" + Readonly = "readonly" + Static = "static" + Deprecated = "deprecated" + Abstract = "abstract" + Async = "async" + Modification = "modification" + Documentation = "documentation" + DefaultLibrary = "defaultLibrary" + + +@enum.unique +class DocumentDiagnosticReportKind(str, enum.Enum): + """The document diagnostic report kinds. + + @since 3.17.0""" + + # Since: 3.17.0 + Full = "full" + """A diagnostic report with a full + set of problems.""" + Unchanged = "unchanged" + """A report indicating that the last + returned report is still accurate.""" + + +class ErrorCodes(int, enum.Enum): + """Predefined error codes.""" + + ParseError = -32700 + InvalidRequest = -32600 + MethodNotFound = -32601 + InvalidParams = -32602 + InternalError = -32603 + ServerNotInitialized = -32002 + """Error code indicating that a server received a notification or + request before the server has received the `initialize` request.""" + UnknownErrorCode = -32001 + + +class LSPErrorCodes(int, enum.Enum): + RequestFailed = -32803 + """A request failed but it was syntactically correct, e.g the + method name was known and the parameters were valid. The error + message should contain human readable information about why + the request failed. + + @since 3.17.0""" + # Since: 3.17.0 + ServerCancelled = -32802 + """The server cancelled the request. This error code should + only be used for requests that explicitly support being + server cancellable. + + @since 3.17.0""" + # Since: 3.17.0 + ContentModified = -32801 + """The server detected that the content of a document got + modified outside normal conditions. A server should + NOT send this error code if it detects a content change + in it unprocessed messages. The result even computed + on an older state might still be useful for the client. + + If a client decides that a result is not of any use anymore + the client should cancel the request.""" + RequestCancelled = -32800 + """The client has canceled a request and a server as detected + the cancel.""" + + +@enum.unique +class FoldingRangeKind(str, enum.Enum): + """A set of predefined range kinds.""" + + Comment = "comment" + """Folding range for a comment""" + Imports = "imports" + """Folding range for an import or include""" + Region = "region" + """Folding range for a region (e.g. `#region`)""" + + +@enum.unique +class SymbolKind(int, enum.Enum): + """A symbol kind.""" + + File = 1 + Module = 2 + Namespace = 3 + Package = 4 + Class = 5 + Method = 6 + Property = 7 + Field = 8 + Constructor = 9 + Enum = 10 + Interface = 11 + Function = 12 + Variable = 13 + Constant = 14 + String = 15 + Number = 16 + Boolean = 17 + Array = 18 + Object = 19 + Key = 20 + Null = 21 + EnumMember = 22 + Struct = 23 + Event = 24 + Operator = 25 + TypeParameter = 26 + + +@enum.unique +class SymbolTag(int, enum.Enum): + """Symbol tags are extra annotations that tweak the rendering of a symbol. + + @since 3.16""" + + # Since: 3.16 + Deprecated = 1 + """Render a symbol as obsolete, usually using a strike-out.""" + + +@enum.unique +class UniquenessLevel(str, enum.Enum): + """Moniker uniqueness level to define scope of the moniker. + + @since 3.16.0""" + + # Since: 3.16.0 + Document = "document" + """The moniker is only unique inside a document""" + Project = "project" + """The moniker is unique inside a project for which a dump got created""" + Group = "group" + """The moniker is unique inside the group to which a project belongs""" + Scheme = "scheme" + """The moniker is unique inside the moniker scheme.""" + Global = "global" + """The moniker is globally unique""" + + +@enum.unique +class MonikerKind(str, enum.Enum): + """The moniker kind. + + @since 3.16.0""" + + # Since: 3.16.0 + Import = "import" + """The moniker represent a symbol that is imported into a project""" + Export = "export" + """The moniker represents a symbol that is exported from a project""" + Local = "local" + """The moniker represents a symbol that is local to a project (e.g. a local + variable of a function, a class not visible outside the project, ...)""" + + +@enum.unique +class InlayHintKind(int, enum.Enum): + """Inlay hint kinds. + + @since 3.17.0""" + + # Since: 3.17.0 + Type = 1 + """An inlay hint that for a type annotation.""" + Parameter = 2 + """An inlay hint that is for a parameter.""" + + +@enum.unique +class MessageType(int, enum.Enum): + """The message type""" + + Error = 1 + """An error message.""" + Warning = 2 + """A warning message.""" + Info = 3 + """An information message.""" + Log = 4 + """A log message.""" + Debug = 5 + """A debug message. + + @since 3.18.0""" + # Since: 3.18.0 + + +@enum.unique +class TextDocumentSyncKind(int, enum.Enum): + """Defines how the host (editor) should sync + document changes to the language server.""" + + None_ = 0 + """Documents should not be synced at all.""" + Full = 1 + """Documents are synced by always sending the full content + of the document.""" + Incremental = 2 + """Documents are synced by sending the full content on open. + After that only incremental updates to the document are + send.""" + + +@enum.unique +class TextDocumentSaveReason(int, enum.Enum): + """Represents reasons why a text document is saved.""" + + Manual = 1 + """Manually triggered, e.g. by the user pressing save, by starting debugging, + or by an API call.""" + AfterDelay = 2 + """Automatic after a delay.""" + FocusOut = 3 + """When the editor lost focus.""" + + +@enum.unique +class CompletionItemKind(int, enum.Enum): + """The kind of a completion entry.""" + + Text = 1 + Method = 2 + Function = 3 + Constructor = 4 + Field = 5 + Variable = 6 + Class = 7 + Interface = 8 + Module = 9 + Property = 10 + Unit = 11 + Value = 12 + Enum = 13 + Keyword = 14 + Snippet = 15 + Color = 16 + File = 17 + Reference = 18 + Folder = 19 + EnumMember = 20 + Constant = 21 + Struct = 22 + Event = 23 + Operator = 24 + TypeParameter = 25 + + +@enum.unique +class CompletionItemTag(int, enum.Enum): + """Completion item tags are extra annotations that tweak the rendering of a completion + item. + + @since 3.15.0""" + + # Since: 3.15.0 + Deprecated = 1 + """Render a completion as obsolete, usually using a strike-out.""" + + +@enum.unique +class InsertTextFormat(int, enum.Enum): + """Defines whether the insert text in a completion item should be interpreted as + plain text or a snippet.""" + + PlainText = 1 + """The primary text to be inserted is treated as a plain string.""" + Snippet = 2 + """The primary text to be inserted is treated as a snippet. + + A snippet can define tab stops and placeholders with `$1`, `$2` + and `${3:foo}`. `$0` defines the final tab stop, it defaults to + the end of the snippet. Placeholders with equal identifiers are linked, + that is typing in one will update others too. + + See also: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#snippet_syntax""" + + +@enum.unique +class InsertTextMode(int, enum.Enum): + """How whitespace and indentation is handled during completion + item insertion. + + @since 3.16.0""" + + # Since: 3.16.0 + AsIs = 1 + """The insertion or replace strings is taken as it is. If the + value is multi line the lines below the cursor will be + inserted using the indentation defined in the string value. + The client will not apply any kind of adjustments to the + string.""" + AdjustIndentation = 2 + """The editor adjusts leading whitespace of new lines so that + they match the indentation up to the cursor of the line for + which the item is accepted. + + Consider a line like this: <2tabs><3tabs>foo. Accepting a + multi line completion item is indented using 2 tabs and all + following lines inserted will be indented using 2 tabs as well.""" + + +@enum.unique +class DocumentHighlightKind(int, enum.Enum): + """A document highlight kind.""" + + Text = 1 + """A textual occurrence.""" + Read = 2 + """Read-access of a symbol, like reading a variable.""" + Write = 3 + """Write-access of a symbol, like writing to a variable.""" + + +@enum.unique +class CodeActionKind(str, enum.Enum): + """A set of predefined code action kinds""" + + Empty = "" + """Empty kind.""" + QuickFix = "quickfix" + """Base kind for quickfix actions: 'quickfix'""" + Refactor = "refactor" + """Base kind for refactoring actions: 'refactor'""" + RefactorExtract = "refactor.extract" + """Base kind for refactoring extraction actions: 'refactor.extract' + + Example extract actions: + + - Extract method + - Extract function + - Extract variable + - Extract interface from class + - ...""" + RefactorInline = "refactor.inline" + """Base kind for refactoring inline actions: 'refactor.inline' + + Example inline actions: + + - Inline function + - Inline variable + - Inline constant + - ...""" + RefactorRewrite = "refactor.rewrite" + """Base kind for refactoring rewrite actions: 'refactor.rewrite' + + Example rewrite actions: + + - Convert JavaScript function to class + - Add or remove parameter + - Encapsulate field + - Make method static + - Move method to base class + - ...""" + Source = "source" + """Base kind for source actions: `source` + + Source code actions apply to the entire file.""" + SourceOrganizeImports = "source.organizeImports" + """Base kind for an organize imports source action: `source.organizeImports`""" + SourceFixAll = "source.fixAll" + """Base kind for auto-fix source actions: `source.fixAll`. + + Fix all actions automatically fix errors that have a clear fix that do not require user input. + They should not suppress errors or perform unsafe fixes such as generating new types or classes. + + @since 3.15.0""" + # Since: 3.15.0 + + +@enum.unique +class TraceValues(str, enum.Enum): + Off = "off" + """Turn tracing off.""" + Messages = "messages" + """Trace messages only.""" + Verbose = "verbose" + """Verbose message tracing.""" + + +@enum.unique +class MarkupKind(str, enum.Enum): + """Describes the content type that a client supports in various + result literals like `Hover`, `ParameterInfo` or `CompletionItem`. + + Please note that `MarkupKinds` must not start with a `$`. This kinds + are reserved for internal usage.""" + + PlainText = "plaintext" + """Plain text is supported as a content format""" + Markdown = "markdown" + """Markdown is supported as a content format""" + + +@enum.unique +class InlineCompletionTriggerKind(int, enum.Enum): + """Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + Invoked = 0 + """Completion was triggered explicitly by a user gesture.""" + Automatic = 1 + """Completion was triggered automatically while editing.""" + + +@enum.unique +class PositionEncodingKind(str, enum.Enum): + """A set of predefined position encoding kinds. + + @since 3.17.0""" + + # Since: 3.17.0 + Utf8 = "utf-8" + """Character offsets count UTF-8 code units (e.g. bytes).""" + Utf16 = "utf-16" + """Character offsets count UTF-16 code units. + + This is the default and must always be supported + by servers""" + Utf32 = "utf-32" + """Character offsets count UTF-32 code units. + + Implementation note: these are the same as Unicode codepoints, + so this `PositionEncodingKind` may also be used for an + encoding-agnostic representation of character offsets.""" + + +@enum.unique +class FileChangeType(int, enum.Enum): + """The file event type""" + + Created = 1 + """The file got created.""" + Changed = 2 + """The file got changed.""" + Deleted = 3 + """The file got deleted.""" + + +@enum.unique +class WatchKind(int, enum.Enum): + Create = 1 + """Interested in create events.""" + Change = 2 + """Interested in change events""" + Delete = 4 + """Interested in delete events""" + + +@enum.unique +class DiagnosticSeverity(int, enum.Enum): + """The diagnostic's severity.""" + + Error = 1 + """Reports an error.""" + Warning = 2 + """Reports a warning.""" + Information = 3 + """Reports an information.""" + Hint = 4 + """Reports a hint.""" + + +@enum.unique +class DiagnosticTag(int, enum.Enum): + """The diagnostic tags. + + @since 3.15.0""" + + # Since: 3.15.0 + Unnecessary = 1 + """Unused or unnecessary code. + + Clients are allowed to render diagnostics with this tag faded out instead of having + an error squiggle.""" + Deprecated = 2 + """Deprecated or obsolete code. + + Clients are allowed to rendered diagnostics with this tag strike through.""" + + +@enum.unique +class CompletionTriggerKind(int, enum.Enum): + """How a completion was triggered""" + + Invoked = 1 + """Completion was triggered by typing an identifier (24x7 code + complete), manual invocation (e.g Ctrl+Space) or via API.""" + TriggerCharacter = 2 + """Completion was triggered by a trigger character specified by + the `triggerCharacters` properties of the `CompletionRegistrationOptions`.""" + TriggerForIncompleteCompletions = 3 + """Completion was re-triggered as current completion list is incomplete""" + + +@enum.unique +class SignatureHelpTriggerKind(int, enum.Enum): + """How a signature help was triggered. + + @since 3.15.0""" + + # Since: 3.15.0 + Invoked = 1 + """Signature help was invoked manually by the user or by a command.""" + TriggerCharacter = 2 + """Signature help was triggered by a trigger character.""" + ContentChange = 3 + """Signature help was triggered by the cursor moving or by the document content changing.""" + + +@enum.unique +class CodeActionTriggerKind(int, enum.Enum): + """The reason why code actions were requested. + + @since 3.17.0""" + + # Since: 3.17.0 + Invoked = 1 + """Code actions were explicitly requested by the user or by an extension.""" + Automatic = 2 + """Code actions were requested automatically. + + This typically happens when current selection in a file changes, but can + also be triggered when file content changes.""" + + +@enum.unique +class FileOperationPatternKind(str, enum.Enum): + """A pattern kind describing if a glob pattern matches a file a folder or + both. + + @since 3.16.0""" + + # Since: 3.16.0 + File = "file" + """The pattern matches a file only.""" + Folder = "folder" + """The pattern matches a folder only.""" + + +@enum.unique +class NotebookCellKind(int, enum.Enum): + """A notebook cell kind. + + @since 3.17.0""" + + # Since: 3.17.0 + Markup = 1 + """A markup-cell is formatted source that is used for display.""" + Code = 2 + """A code-cell is source code.""" + + +@enum.unique +class ResourceOperationKind(str, enum.Enum): + Create = "create" + """Supports creating new files and folders.""" + Rename = "rename" + """Supports renaming existing files and folders.""" + Delete = "delete" + """Supports deleting existing files and folders.""" + + +@enum.unique +class FailureHandlingKind(str, enum.Enum): + Abort = "abort" + """Applying the workspace change is simply aborted if one of the changes provided + fails. All operations executed before the failing operation stay executed.""" + Transactional = "transactional" + """All operations are executed transactional. That means they either all + succeed or no changes at all are applied to the workspace.""" + TextOnlyTransactional = "textOnlyTransactional" + """If the workspace edit contains only textual file changes they are executed transactional. + If resource changes (create, rename or delete file) are part of the change the failure + handling strategy is abort.""" + Undo = "undo" + """The client tries to undo the operations already executed. But there is no + guarantee that this is succeeding.""" + + +@enum.unique +class PrepareSupportDefaultBehavior(int, enum.Enum): + Identifier = 1 + """The client's default behavior is to select the identifier + according the to language's syntax rule.""" + + +@enum.unique +class TokenFormat(str, enum.Enum): + Relative = "relative" + + +class LSPObject: + """LSP object definition. + @since 3.17.0""" + + # Since: 3.17.0 + pass + + +Definition = Union["Location", List["Location"]] +"""The definition of a symbol represented as one or many {@link Location locations}. +For most programming languages there is only one location at which a symbol is +defined. + +Servers should prefer returning `DefinitionLink` over `Definition` if supported +by the client.""" + + +DefinitionLink = Union["LocationLink", "LocationLink"] +"""Information about where a symbol is defined. + +Provides additional metadata over normal {@link Location location} definitions, including the range of +the defining symbol""" + + +LSPArray = List["LSPAny"] +"""LSP arrays. +@since 3.17.0""" +# Since: 3.17.0 + + +LSPAny = Union[Any, None] +"""The LSP any type. +Please note that strictly speaking a property with the value `undefined` +can't be converted into JSON preserving the property name. However for +convenience it is allowed and assumed that all these properties are +optional as well. +@since 3.17.0""" +# Since: 3.17.0 + + +Declaration = Union["Location", List["Location"]] +"""The declaration of a symbol representation as one or many {@link Location locations}.""" + + +DeclarationLink = Union["LocationLink", "LocationLink"] +"""Information about where a symbol is declared. + +Provides additional metadata over normal {@link Location location} declarations, including the range of +the declaring symbol. + +Servers should prefer returning `DeclarationLink` over `Declaration` if supported +by the client.""" + + +InlineValue = Union[ + "InlineValueText", "InlineValueVariableLookup", "InlineValueEvaluatableExpression" +] +"""Inline value information can be provided by different means: +- directly as a text value (class InlineValueText). +- as a name to use for a variable lookup (class InlineValueVariableLookup) +- as an evaluatable expression (class InlineValueEvaluatableExpression) +The InlineValue types combines all inline value types into one type. + +@since 3.17.0""" +# Since: 3.17.0 + + +DocumentDiagnosticReport = Union[ + "RelatedFullDocumentDiagnosticReport", "RelatedUnchangedDocumentDiagnosticReport" +] +"""The result of a document diagnostic pull request. A report can +either be a full report containing all diagnostics for the +requested document or an unchanged report indicating that nothing +has changed in terms of diagnostics in comparison to the last +pull request. + +@since 3.17.0""" +# Since: 3.17.0 + + +@attrs.define +class PrepareRenameResult_Type1: + range: "Range" = attrs.field() + + placeholder: str = attrs.field(validator=attrs.validators.instance_of(str)) + + +@attrs.define +class PrepareRenameResult_Type2: + default_behavior: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + + +PrepareRenameResult = Union[ + "Range", "PrepareRenameResult_Type1", "PrepareRenameResult_Type2" +] + + +DocumentSelector = List["DocumentFilter"] +"""A document selector is the combination of one or many document filters. + +@sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**/tsconfig.json' }]`; + +The use of a string as a document filter is deprecated @since 3.16.0.""" +# Since: 3.16.0. + + +ProgressToken = Union[int, str] + + +ChangeAnnotationIdentifier = str +"""An identifier to refer to a change annotation stored with a workspace edit.""" + + +WorkspaceDocumentDiagnosticReport = Union[ + "WorkspaceFullDocumentDiagnosticReport", + "WorkspaceUnchangedDocumentDiagnosticReport", +] +"""A workspace diagnostic document report. + +@since 3.17.0""" +# Since: 3.17.0 + + +@attrs.define +class TextDocumentContentChangeEvent_Type1: + range: "Range" = attrs.field() + """The range of the document that changed.""" + + text: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The new text for the provided range.""" + + range_length: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """The optional length of the range that got replaced. + + @deprecated use range instead.""" + + +@attrs.define +class TextDocumentContentChangeEvent_Type2: + text: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The new text of the whole document.""" + + +TextDocumentContentChangeEvent = Union[ + "TextDocumentContentChangeEvent_Type1", "TextDocumentContentChangeEvent_Type2" +] +"""An event describing a change to a text document. If only a text is provided +it is considered to be the full content of the document.""" + + +@attrs.define +class MarkedString_Type1: + language: str = attrs.field(validator=attrs.validators.instance_of(str)) + + value: str = attrs.field(validator=attrs.validators.instance_of(str)) + + +MarkedString = Union[str, "MarkedString_Type1"] +"""MarkedString can be used to render human readable text. It is either a markdown string +or a code-block that provides a language and a code snippet. The language identifier +is semantically equal to the optional language identifier in fenced code blocks in GitHub +issues. See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting + +The pair of a language and a value is an equivalent to markdown: +```${language} +${value} +``` + +Note that markdown strings will be sanitized - that means html will be escaped. +@deprecated use MarkupContent instead.""" + + +DocumentFilter = Union["TextDocumentFilter", "NotebookCellTextDocumentFilter"] +"""A document filter describes a top level text document or +a notebook cell document. + +@since 3.17.0 - proposed support for NotebookCellTextDocumentFilter.""" +# Since: 3.17.0 - proposed support for NotebookCellTextDocumentFilter. + + +GlobPattern = Union["Pattern", "RelativePattern"] +"""The glob pattern. Either a string pattern or a relative pattern. + +@since 3.17.0""" +# Since: 3.17.0 + + +@attrs.define +class TextDocumentFilter_Type1: + language: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A language id, like `typescript`.""" + + scheme: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.""" + + pattern: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.""" + + +@attrs.define +class TextDocumentFilter_Type2: + scheme: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.""" + + language: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A language id, like `typescript`.""" + + pattern: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.""" + + +@attrs.define +class TextDocumentFilter_Type3: + pattern: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.""" + + language: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A language id, like `typescript`.""" + + scheme: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.""" + + +TextDocumentFilter = Union[ + "TextDocumentFilter_Type1", "TextDocumentFilter_Type2", "TextDocumentFilter_Type3" +] +"""A document filter denotes a document by different properties like +the {@link TextDocument.languageId language}, the {@link Uri.scheme scheme} of +its resource, or a glob-pattern that is applied to the {@link TextDocument.fileName path}. + +Glob patterns can have the following syntax: +- `*` to match one or more characters in a path segment +- `?` to match on one character in a path segment +- `**` to match any number of path segments, including none +- `{}` to group sub patterns into an OR expression. (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files) +- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) + +@sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }` +@sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }` + +@since 3.17.0""" +# Since: 3.17.0 + + +@attrs.define +class NotebookDocumentFilter_Type1: + notebook_type: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The type of the enclosing notebook.""" + + scheme: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.""" + + pattern: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A glob pattern.""" + + +@attrs.define +class NotebookDocumentFilter_Type2: + scheme: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.""" + + notebook_type: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The type of the enclosing notebook.""" + + pattern: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A glob pattern.""" + + +@attrs.define +class NotebookDocumentFilter_Type3: + pattern: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A glob pattern.""" + + notebook_type: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The type of the enclosing notebook.""" + + scheme: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.""" + + +NotebookDocumentFilter = Union[ + "NotebookDocumentFilter_Type1", + "NotebookDocumentFilter_Type2", + "NotebookDocumentFilter_Type3", +] +"""A notebook document filter denotes a notebook document by +different properties. The properties will be match +against the notebook's URI (same as with documents) + +@since 3.17.0""" +# Since: 3.17.0 + + +Pattern = str +"""The glob pattern to watch relative to the base path. Glob patterns can have the following syntax: +- `*` to match one or more characters in a path segment +- `?` to match on one character in a path segment +- `**` to match any number of path segments, including none +- `{}` to group conditions (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files) +- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) +- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) + +@since 3.17.0""" +# Since: 3.17.0 + + +@attrs.define +class TextDocumentPositionParams: + """A parameter literal used in requests to pass a text document and a position inside that + document.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + +@attrs.define +class WorkDoneProgressParams: + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class PartialResultParams: + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class ImplementationParams: + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class Location: + """Represents a location inside a resource, such as a line + inside a text file.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + + range: "Range" = attrs.field() + + def __eq__(self, o: object) -> bool: + if not isinstance(o, Location): + return NotImplemented + return (self.uri == o.uri) and (self.range == o.range) + + def __repr__(self) -> str: + return f"{self.uri}:{self.range!r}" + + +@attrs.define +class TextDocumentRegistrationOptions: + """General text document registration options.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + +@attrs.define +class WorkDoneProgressOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class ImplementationOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class StaticRegistrationOptions: + """Static registration options to be returned in the initialize + request.""" + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class ImplementationRegistrationOptions: + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class TypeDefinitionParams: + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class TypeDefinitionOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class TypeDefinitionRegistrationOptions: + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class WorkspaceFolder: + """A workspace folder inside a client.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The associated URI for this workspace folder.""" + + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of the workspace folder. Used to refer to this + workspace folder in the user interface.""" + + +@attrs.define +class DidChangeWorkspaceFoldersParams: + """The parameters of a `workspace/didChangeWorkspaceFolders` notification.""" + + event: "WorkspaceFoldersChangeEvent" = attrs.field() + """The actual workspace folder change event.""" + + +@attrs.define +class ConfigurationParams: + """The parameters of a configuration request.""" + + items: List["ConfigurationItem"] = attrs.field() + + +@attrs.define +class DocumentColorParams: + """Parameters for a {@link DocumentColorRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class ColorInformation: + """Represents a color range from a document.""" + + range: "Range" = attrs.field() + """The range in the document where this color appears.""" + + color: "Color" = attrs.field() + """The actual color value for this color range.""" + + +@attrs.define +class DocumentColorOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentColorRegistrationOptions: + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class ColorPresentationParams: + """Parameters for a {@link ColorPresentationRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + color: "Color" = attrs.field() + """The color to request presentations for.""" + + range: "Range" = attrs.field() + """The range where the color would be inserted. Serves as a context.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class ColorPresentation: + label: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The label of this color presentation. It will be shown on the color + picker header. By default this is also the text that is inserted when selecting + this color presentation.""" + + text_edit: Optional["TextEdit"] = attrs.field(default=None) + """An {@link TextEdit edit} which is applied to a document when selecting + this presentation for the color. When `falsy` the {@link ColorPresentation.label label} + is used.""" + + additional_text_edits: Optional[List["TextEdit"]] = attrs.field(default=None) + """An optional array of additional {@link TextEdit text edits} that are applied when + selecting this color presentation. Edits must not overlap with the main {@link ColorPresentation.textEdit edit} nor with themselves.""" + + +@attrs.define +class FoldingRangeParams: + """Parameters for a {@link FoldingRangeRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class FoldingRange: + """Represents a folding range. To be valid, start and end line must be bigger than zero and smaller + than the number of lines in the document. Clients are free to ignore invalid ranges. + """ + + start_line: int = attrs.field(validator=validators.uinteger_validator) + """The zero-based start line of the range to fold. The folded area starts after the line's last character. + To be valid, the end must be zero or larger and smaller than the number of lines in the document.""" + + end_line: int = attrs.field(validator=validators.uinteger_validator) + """The zero-based end line of the range to fold. The folded area ends with the line's last character. + To be valid, the end must be zero or larger and smaller than the number of lines in the document.""" + + start_character: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.""" + + end_character: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.""" + + kind: Optional[Union[FoldingRangeKind, str]] = attrs.field(default=None) + """Describes the kind of the folding range such as `comment' or 'region'. The kind + is used to categorize folding ranges and used by commands like 'Fold all comments'. + See {@link FoldingRangeKind} for an enumeration of standardized kinds.""" + + collapsed_text: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The text that the client should show when the specified range is + collapsed. If not defined or not supported by the client, a default + will be chosen by the client. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class FoldingRangeOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class FoldingRangeRegistrationOptions: + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class DeclarationParams: + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class DeclarationOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DeclarationRegistrationOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class SelectionRangeParams: + """A parameter literal used in selection range requests.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + positions: List["Position"] = attrs.field() + """The positions inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class SelectionRange: + """A selection range represents a part of a selection hierarchy. A selection range + may have a parent selection range that contains it.""" + + range: "Range" = attrs.field() + """The {@link Range range} of this selection range.""" + + parent: Optional["SelectionRange"] = attrs.field(default=None) + """The parent selection range containing this range. Therefore `parent.range` must contain `this.range`.""" + + +@attrs.define +class SelectionRangeOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class SelectionRangeRegistrationOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class WorkDoneProgressCreateParams: + token: ProgressToken = attrs.field() + """The token to be used to report progress.""" + + +@attrs.define +class WorkDoneProgressCancelParams: + token: ProgressToken = attrs.field() + """The token to be used to report progress.""" + + +@attrs.define +class CallHierarchyPrepareParams: + """The parameter of a `textDocument/prepareCallHierarchy` request. + + @since 3.16.0""" + + # Since: 3.16.0 + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class CallHierarchyItem: + """Represents programming constructs like functions or constructors in the context + of call hierarchy. + + @since 3.16.0""" + + # Since: 3.16.0 + + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of this item.""" + + kind: SymbolKind = attrs.field() + """The kind of this item.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The resource identifier of this item.""" + + range: "Range" = attrs.field() + """The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code.""" + + selection_range: "Range" = attrs.field() + """The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function. + Must be contained by the {@link CallHierarchyItem.range `range`}.""" + + tags: Optional[List[SymbolTag]] = attrs.field(default=None) + """Tags for this item.""" + + detail: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """More detail for this item, e.g. the signature of a function.""" + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved between a call hierarchy prepare and + incoming calls or outgoing calls requests.""" + + +@attrs.define +class CallHierarchyOptions: + """Call hierarchy options used during static registration. + + @since 3.16.0""" + + # Since: 3.16.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class CallHierarchyRegistrationOptions: + """Call hierarchy options used during static or dynamic registration. + + @since 3.16.0""" + + # Since: 3.16.0 + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class CallHierarchyIncomingCallsParams: + """The parameter of a `callHierarchy/incomingCalls` request. + + @since 3.16.0""" + + # Since: 3.16.0 + + item: CallHierarchyItem = attrs.field() + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class CallHierarchyIncomingCall: + """Represents an incoming call, e.g. a caller of a method or constructor. + + @since 3.16.0""" + + # Since: 3.16.0 + + from_: CallHierarchyItem = attrs.field() + """The item that makes the call.""" + + from_ranges: List["Range"] = attrs.field() + """The ranges at which the calls appear. This is relative to the caller + denoted by {@link CallHierarchyIncomingCall.from `this.from`}.""" + + +@attrs.define +class CallHierarchyOutgoingCallsParams: + """The parameter of a `callHierarchy/outgoingCalls` request. + + @since 3.16.0""" + + # Since: 3.16.0 + + item: CallHierarchyItem = attrs.field() + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class CallHierarchyOutgoingCall: + """Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc. + + @since 3.16.0""" + + # Since: 3.16.0 + + to: CallHierarchyItem = attrs.field() + """The item that is called.""" + + from_ranges: List["Range"] = attrs.field() + """The range at which this item is called. This is the range relative to the caller, e.g the item + passed to {@link CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls `provideCallHierarchyOutgoingCalls`} + and not {@link CallHierarchyOutgoingCall.to `this.to`}.""" + + +@attrs.define +class SemanticTokensParams: + """@since 3.16.0""" + + # Since: 3.16.0 + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class SemanticTokens: + """@since 3.16.0""" + + # Since: 3.16.0 + + data: List[int] = attrs.field() + """The actual tokens.""" + + result_id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional result id. If provided and clients support delta updating + the client will include the result id in the next semantic token request. + A server can then instead of computing all semantic tokens again simply + send a delta.""" + + +@attrs.define +class SemanticTokensPartialResult: + """@since 3.16.0""" + + # Since: 3.16.0 + + data: List[int] = attrs.field() + + +@attrs.define +class SemanticTokensOptionsFullType1: + delta: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server supports deltas for full documents.""" + + +@attrs.define +class SemanticTokensOptions: + """@since 3.16.0""" + + # Since: 3.16.0 + + legend: "SemanticTokensLegend" = attrs.field() + """The legend used by the server""" + + range: Optional[Union[bool, Any]] = attrs.field(default=None) + """Server supports providing semantic tokens for a specific range + of a document.""" + + full: Optional[Union[bool, "SemanticTokensOptionsFullType1"]] = attrs.field( + default=None + ) + """Server supports providing semantic tokens for a full document.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class SemanticTokensRegistrationOptionsFullType1: + delta: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server supports deltas for full documents.""" + + +@attrs.define +class SemanticTokensRegistrationOptions: + """@since 3.16.0""" + + # Since: 3.16.0 + + legend: "SemanticTokensLegend" = attrs.field() + """The legend used by the server""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + range: Optional[Union[bool, Any]] = attrs.field(default=None) + """Server supports providing semantic tokens for a specific range + of a document.""" + + full: Optional[ + Union[bool, "SemanticTokensRegistrationOptionsFullType1"] + ] = attrs.field(default=None) + """Server supports providing semantic tokens for a full document.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class SemanticTokensDeltaParams: + """@since 3.16.0""" + + # Since: 3.16.0 + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + previous_result_id: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The result id of a previous response. The result Id can either point to a full response + or a delta response depending on what was received last.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class SemanticTokensDelta: + """@since 3.16.0""" + + # Since: 3.16.0 + + edits: List["SemanticTokensEdit"] = attrs.field() + """The semantic token edits to transform a previous result into a new result.""" + + result_id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + + +@attrs.define +class SemanticTokensDeltaPartialResult: + """@since 3.16.0""" + + # Since: 3.16.0 + + edits: List["SemanticTokensEdit"] = attrs.field() + + +@attrs.define +class SemanticTokensRangeParams: + """@since 3.16.0""" + + # Since: 3.16.0 + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + range: "Range" = attrs.field() + """The range the semantic tokens are requested for.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class ShowDocumentParams: + """Params to show a resource in the UI. + + @since 3.16.0""" + + # Since: 3.16.0 + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The uri to show.""" + + external: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Indicates to show the resource in an external program. + To show, for example, `https://code.visualstudio.com/` + in the default WEB browser set `external` to `true`.""" + + take_focus: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """An optional property to indicate whether the editor + showing the document should take focus or not. + Clients might ignore this property if an external + program is started.""" + + selection: Optional["Range"] = attrs.field(default=None) + """An optional selection range if the document is a text + document. Clients might ignore the property if an + external program is started or the file is not a text + file.""" + + +@attrs.define +class ShowDocumentResult: + """The result of a showDocument request. + + @since 3.16.0""" + + # Since: 3.16.0 + + success: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + """A boolean indicating if the show was successful.""" + + +@attrs.define +class LinkedEditingRangeParams: + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class LinkedEditingRanges: + """The result of a linked editing range request. + + @since 3.16.0""" + + # Since: 3.16.0 + + ranges: List["Range"] = attrs.field() + """A list of ranges that can be edited together. The ranges must have + identical length and contain identical text content. The ranges cannot overlap.""" + + word_pattern: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional word pattern (regular expression) that describes valid contents for + the given ranges. If no pattern is provided, the client configuration's word + pattern will be used.""" + + +@attrs.define +class LinkedEditingRangeOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class LinkedEditingRangeRegistrationOptions: + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class CreateFilesParams: + """The parameters sent in notifications/requests for user-initiated creation of + files. + + @since 3.16.0""" + + # Since: 3.16.0 + + files: List["FileCreate"] = attrs.field() + """An array of all files/folders created in this operation.""" + + +@attrs.define +class WorkspaceEdit: + """A workspace edit represents changes to many resources managed in the workspace. The edit + should either provide `changes` or `documentChanges`. If documentChanges are present + they are preferred over `changes` if the client can handle versioned document edits. + + Since version 3.13.0 a workspace edit can contain resource operations as well. If resource + operations are present clients need to execute the operations in the order in which they + are provided. So a workspace edit for example can consist of the following two changes: + (1) a create file a.txt and (2) a text document edit which insert text into file a.txt. + + An invalid sequence (e.g. (1) delete file a.txt and (2) insert text into file a.txt) will + cause failure of the operation. How the client recovers from the failure is described by + the client capability: `workspace.workspaceEdit.failureHandling`""" + + changes: Optional[Dict[str, List["TextEdit"]]] = attrs.field(default=None) + """Holds changes to existing resources.""" + + document_changes: Optional[ + List[Union["TextDocumentEdit", "CreateFile", "RenameFile", "DeleteFile"]] + ] = attrs.field(default=None) + """Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes + are either an array of `TextDocumentEdit`s to express changes to n different text documents + where each text document edit addresses a specific version of a text document. Or it can contain + above `TextDocumentEdit`s mixed with create, rename and delete file / folder operations. + + Whether a client supports versioned document edits is expressed via + `workspace.workspaceEdit.documentChanges` client capability. + + If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then + only plain `TextEdit`s using the `changes` property are supported.""" + + change_annotations: Optional[ + Dict[ChangeAnnotationIdentifier, "ChangeAnnotation"] + ] = attrs.field(default=None) + """A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and + delete file / folder operations. + + Whether clients honor this property depends on the client capability `workspace.changeAnnotationSupport`. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class FileOperationRegistrationOptions: + """The options to register for file operations. + + @since 3.16.0""" + + # Since: 3.16.0 + + filters: List["FileOperationFilter"] = attrs.field() + """The actual filters.""" + + +@attrs.define +class RenameFilesParams: + """The parameters sent in notifications/requests for user-initiated renames of + files. + + @since 3.16.0""" + + # Since: 3.16.0 + + files: List["FileRename"] = attrs.field() + """An array of all files/folders renamed in this operation. When a folder is renamed, only + the folder will be included, and not its children.""" + + +@attrs.define +class DeleteFilesParams: + """The parameters sent in notifications/requests for user-initiated deletes of + files. + + @since 3.16.0""" + + # Since: 3.16.0 + + files: List["FileDelete"] = attrs.field() + """An array of all files/folders deleted in this operation.""" + + +@attrs.define +class MonikerParams: + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class Moniker: + """Moniker definition to match LSIF 0.5 moniker definition. + + @since 3.16.0""" + + # Since: 3.16.0 + + scheme: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The scheme of the moniker. For example tsc or .Net""" + + identifier: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The identifier of the moniker. The value is opaque in LSIF however + schema owners are allowed to define the structure if they want.""" + + unique: UniquenessLevel = attrs.field() + """The scope in which the moniker is unique""" + + kind: Optional[MonikerKind] = attrs.field(default=None) + """The moniker kind if known.""" + + +@attrs.define +class MonikerOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class MonikerRegistrationOptions: + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class TypeHierarchyPrepareParams: + """The parameter of a `textDocument/prepareTypeHierarchy` request. + + @since 3.17.0""" + + # Since: 3.17.0 + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class TypeHierarchyItem: + """@since 3.17.0""" + + # Since: 3.17.0 + + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of this item.""" + + kind: SymbolKind = attrs.field() + """The kind of this item.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The resource identifier of this item.""" + + range: "Range" = attrs.field() + """The range enclosing this symbol not including leading/trailing whitespace + but everything else, e.g. comments and code.""" + + selection_range: "Range" = attrs.field() + """The range that should be selected and revealed when this symbol is being + picked, e.g. the name of a function. Must be contained by the + {@link TypeHierarchyItem.range `range`}.""" + + tags: Optional[List[SymbolTag]] = attrs.field(default=None) + """Tags for this item.""" + + detail: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """More detail for this item, e.g. the signature of a function.""" + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved between a type hierarchy prepare and + supertypes or subtypes requests. It could also be used to identify the + type hierarchy in the server, helping improve the performance on + resolving supertypes and subtypes.""" + + +@attrs.define +class TypeHierarchyOptions: + """Type hierarchy options used during static registration. + + @since 3.17.0""" + + # Since: 3.17.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class TypeHierarchyRegistrationOptions: + """Type hierarchy options used during static or dynamic registration. + + @since 3.17.0""" + + # Since: 3.17.0 + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class TypeHierarchySupertypesParams: + """The parameter of a `typeHierarchy/supertypes` request. + + @since 3.17.0""" + + # Since: 3.17.0 + + item: TypeHierarchyItem = attrs.field() + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class TypeHierarchySubtypesParams: + """The parameter of a `typeHierarchy/subtypes` request. + + @since 3.17.0""" + + # Since: 3.17.0 + + item: TypeHierarchyItem = attrs.field() + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class InlineValueParams: + """A parameter literal used in inline value requests. + + @since 3.17.0""" + + # Since: 3.17.0 + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + range: "Range" = attrs.field() + """The document range for which inline values should be computed.""" + + context: "InlineValueContext" = attrs.field() + """Additional information about the context in which inline values were + requested.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class InlineValueOptions: + """Inline value options used during static registration. + + @since 3.17.0""" + + # Since: 3.17.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class InlineValueRegistrationOptions: + """Inline value options used during static or dynamic registration. + + @since 3.17.0""" + + # Since: 3.17.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class InlayHintParams: + """A parameter literal used in inlay hint requests. + + @since 3.17.0""" + + # Since: 3.17.0 + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + range: "Range" = attrs.field() + """The document range for which inlay hints should be computed.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class InlayHint: + """Inlay hint information. + + @since 3.17.0""" + + # Since: 3.17.0 + + position: "Position" = attrs.field() + """The position of this hint.""" + + label: Union[str, List["InlayHintLabelPart"]] = attrs.field() + """The label of this hint. A human readable string or an array of + InlayHintLabelPart label parts. + + *Note* that neither the string nor the label part can be empty.""" + + kind: Optional[InlayHintKind] = attrs.field(default=None) + """The kind of this hint. Can be omitted in which case the client + should fall back to a reasonable default.""" + + text_edits: Optional[List["TextEdit"]] = attrs.field(default=None) + """Optional text edits that are performed when accepting this inlay hint. + + *Note* that edits are expected to change the document so that the inlay + hint (or its nearest variant) is now part of the document and the inlay + hint itself is now obsolete.""" + + tooltip: Optional[Union[str, "MarkupContent"]] = attrs.field(default=None) + """The tooltip text when you hover over this item.""" + + padding_left: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Render padding before the hint. + + Note: Padding should use the editor's background color, not the + background color of the hint itself. That means padding can be used + to visually align/separate an inlay hint.""" + + padding_right: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Render padding after the hint. + + Note: Padding should use the editor's background color, not the + background color of the hint itself. That means padding can be used + to visually align/separate an inlay hint.""" + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved on an inlay hint between + a `textDocument/inlayHint` and a `inlayHint/resolve` request.""" + + +@attrs.define +class InlayHintOptions: + """Inlay hint options used during static registration. + + @since 3.17.0""" + + # Since: 3.17.0 + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server provides support to resolve additional + information for an inlay hint item.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class InlayHintRegistrationOptions: + """Inlay hint options used during static or dynamic registration. + + @since 3.17.0""" + + # Since: 3.17.0 + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server provides support to resolve additional + information for an inlay hint item.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class DocumentDiagnosticParams: + """Parameters of the document diagnostic request. + + @since 3.17.0""" + + # Since: 3.17.0 + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + identifier: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The additional identifier provided during registration.""" + + previous_result_id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The result id of a previous response if provided.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class DocumentDiagnosticReportPartialResult: + """A partial result for a document diagnostic report. + + @since 3.17.0""" + + # Since: 3.17.0 + + related_documents: Dict[ + str, Union["FullDocumentDiagnosticReport", "UnchangedDocumentDiagnosticReport"] + ] = attrs.field() + + +@attrs.define +class DiagnosticServerCancellationData: + """Cancellation data returned from a diagnostic request. + + @since 3.17.0""" + + # Since: 3.17.0 + + retrigger_request: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + + +@attrs.define +class DiagnosticOptions: + """Diagnostic options. + + @since 3.17.0""" + + # Since: 3.17.0 + + inter_file_dependencies: bool = attrs.field( + validator=attrs.validators.instance_of(bool) + ) + """Whether the language has inter file dependencies meaning that + editing code in one file can result in a different diagnostic + set in another file. Inter file dependencies are common for + most programming languages and typically uncommon for linters.""" + + workspace_diagnostics: bool = attrs.field( + validator=attrs.validators.instance_of(bool) + ) + """The server provides support for workspace diagnostics as well.""" + + identifier: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional identifier under which the diagnostics are + managed by the client.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DiagnosticRegistrationOptions: + """Diagnostic registration options. + + @since 3.17.0""" + + # Since: 3.17.0 + + inter_file_dependencies: bool = attrs.field( + validator=attrs.validators.instance_of(bool) + ) + """Whether the language has inter file dependencies meaning that + editing code in one file can result in a different diagnostic + set in another file. Inter file dependencies are common for + most programming languages and typically uncommon for linters.""" + + workspace_diagnostics: bool = attrs.field( + validator=attrs.validators.instance_of(bool) + ) + """The server provides support for workspace diagnostics as well.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + identifier: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional identifier under which the diagnostics are + managed by the client.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class WorkspaceDiagnosticParams: + """Parameters of the workspace diagnostic request. + + @since 3.17.0""" + + # Since: 3.17.0 + + previous_result_ids: List["PreviousResultId"] = attrs.field() + """The currently known diagnostic reports with their + previous result ids.""" + + identifier: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The additional identifier provided during registration.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class WorkspaceDiagnosticReport: + """A workspace diagnostic report. + + @since 3.17.0""" + + # Since: 3.17.0 + + items: List[WorkspaceDocumentDiagnosticReport] = attrs.field() + + +@attrs.define +class WorkspaceDiagnosticReportPartialResult: + """A partial result for a workspace diagnostic report. + + @since 3.17.0""" + + # Since: 3.17.0 + + items: List[WorkspaceDocumentDiagnosticReport] = attrs.field() + + +@attrs.define +class DidOpenNotebookDocumentParams: + """The params sent in an open notebook document notification. + + @since 3.17.0""" + + # Since: 3.17.0 + + notebook_document: "NotebookDocument" = attrs.field() + """The notebook document that got opened.""" + + cell_text_documents: List["TextDocumentItem"] = attrs.field() + """The text documents that represent the content + of a notebook cell.""" + + +@attrs.define +class DidChangeNotebookDocumentParams: + """The params sent in a change notebook document notification. + + @since 3.17.0""" + + # Since: 3.17.0 + + notebook_document: "VersionedNotebookDocumentIdentifier" = attrs.field() + """The notebook document that did change. The version number points + to the version after all provided changes have been applied. If + only the text document content of a cell changes the notebook version + doesn't necessarily have to change.""" + + change: "NotebookDocumentChangeEvent" = attrs.field() + """The actual changes to the notebook document. + + The changes describe single state changes to the notebook document. + So if there are two changes c1 (at array index 0) and c2 (at array + index 1) for a notebook in state S then c1 moves the notebook from + S to S' and c2 from S' to S''. So c1 is computed on the state S and + c2 is computed on the state S'. + + To mirror the content of a notebook using change events use the following approach: + - start with the same initial content + - apply the 'notebookDocument/didChange' notifications in the order you receive them. + - apply the `NotebookChangeEvent`s in a single notification in the order + you receive them.""" + + +@attrs.define +class DidSaveNotebookDocumentParams: + """The params sent in a save notebook document notification. + + @since 3.17.0""" + + # Since: 3.17.0 + + notebook_document: "NotebookDocumentIdentifier" = attrs.field() + """The notebook document that got saved.""" + + +@attrs.define +class DidCloseNotebookDocumentParams: + """The params sent in a close notebook document notification. + + @since 3.17.0""" + + # Since: 3.17.0 + + notebook_document: "NotebookDocumentIdentifier" = attrs.field() + """The notebook document that got closed.""" + + cell_text_documents: List["TextDocumentIdentifier"] = attrs.field() + """The text documents that represent the content + of a notebook cell that got closed.""" + + +@attrs.define +class InlineCompletionParams: + """A parameter literal used in inline completion requests. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + context: "InlineCompletionContext" = attrs.field() + """Additional information about the context in which inline completions were + requested.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class InlineCompletionList: + """Represents a collection of {@link InlineCompletionItem inline completion items} to be presented in the editor. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + items: List["InlineCompletionItem"] = attrs.field() + """The inline completion items""" + + +@attrs.define +class InlineCompletionItem: + """An inline completion item represents a text snippet that is proposed inline to complete text that is being typed. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + insert_text: Union[str, "StringValue"] = attrs.field() + """The text to replace the range with. Must be set.""" + + filter_text: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A text that is used to decide if this inline completion should be shown. When `falsy` the {@link InlineCompletionItem.insertText} is used.""" + + range: Optional["Range"] = attrs.field(default=None) + """The range to replace. Must begin and end on the same line.""" + + command: Optional["Command"] = attrs.field(default=None) + """An optional {@link Command} that is executed *after* inserting this completion.""" + + +@attrs.define +class InlineCompletionOptions: + """Inline completion options used during static registration. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class InlineCompletionRegistrationOptions: + """Inline completion options used during static or dynamic registration. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class RegistrationParams: + registrations: List["Registration"] = attrs.field() + + +@attrs.define +class UnregistrationParams: + unregisterations: List["Unregistration"] = attrs.field() + + +@attrs.define +class InitializeParamsClientInfoType: + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of the client as defined by the client.""" + + version: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The client's version as defined by the client.""" + + +@attrs.define +class _InitializeParams: + """The initialize parameters""" + + capabilities: "ClientCapabilities" = attrs.field() + """The capabilities provided by the client (editor or tool)""" + + process_id: Optional[Union[int, None]] = attrs.field(default=None) + """The process Id of the parent process that started + the server. + + Is `null` if the process has not been started by another process. + If the parent process is not alive then the server should exit.""" + + client_info: Optional["InitializeParamsClientInfoType"] = attrs.field(default=None) + """Information about the client + + @since 3.15.0""" + # Since: 3.15.0 + + locale: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The locale the client is currently showing the user interface + in. This must not necessarily be the locale of the operating + system. + + Uses IETF language tags as the value's syntax + (See https://en.wikipedia.org/wiki/IETF_language_tag) + + @since 3.16.0""" + # Since: 3.16.0 + + root_path: Optional[Union[str, None]] = attrs.field(default=None) + """The rootPath of the workspace. Is null + if no folder is open. + + @deprecated in favour of rootUri.""" + + root_uri: Optional[Union[str, None]] = attrs.field(default=None) + """The rootUri of the workspace. Is null if no + folder is open. If both `rootPath` and `rootUri` are set + `rootUri` wins. + + @deprecated in favour of workspaceFolders.""" + + initialization_options: Optional[LSPAny] = attrs.field(default=None) + """User provided initialization options.""" + + trace: Optional[TraceValues] = attrs.field(default=None) + """The initial trace setting. If omitted trace is disabled ('off').""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class WorkspaceFoldersInitializeParams: + workspace_folders: Optional[Union[List[WorkspaceFolder], None]] = attrs.field( + default=None + ) + """The workspace folders configured in the client when the server starts. + + This property is only available if the client supports workspace folders. + It can be `null` if the client supports workspace folders but none are + configured. + + @since 3.6.0""" + # Since: 3.6.0 + + +@attrs.define +class InitializeParams: + capabilities: "ClientCapabilities" = attrs.field() + """The capabilities provided by the client (editor or tool)""" + + process_id: Optional[Union[int, None]] = attrs.field(default=None) + """The process Id of the parent process that started + the server. + + Is `null` if the process has not been started by another process. + If the parent process is not alive then the server should exit.""" + + client_info: Optional["InitializeParamsClientInfoType"] = attrs.field(default=None) + """Information about the client + + @since 3.15.0""" + # Since: 3.15.0 + + locale: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The locale the client is currently showing the user interface + in. This must not necessarily be the locale of the operating + system. + + Uses IETF language tags as the value's syntax + (See https://en.wikipedia.org/wiki/IETF_language_tag) + + @since 3.16.0""" + # Since: 3.16.0 + + root_path: Optional[Union[str, None]] = attrs.field(default=None) + """The rootPath of the workspace. Is null + if no folder is open. + + @deprecated in favour of rootUri.""" + + root_uri: Optional[Union[str, None]] = attrs.field(default=None) + """The rootUri of the workspace. Is null if no + folder is open. If both `rootPath` and `rootUri` are set + `rootUri` wins. + + @deprecated in favour of workspaceFolders.""" + + initialization_options: Optional[LSPAny] = attrs.field(default=None) + """User provided initialization options.""" + + trace: Optional[TraceValues] = attrs.field(default=None) + """The initial trace setting. If omitted trace is disabled ('off').""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + workspace_folders: Optional[Union[List[WorkspaceFolder], None]] = attrs.field( + default=None + ) + """The workspace folders configured in the client when the server starts. + + This property is only available if the client supports workspace folders. + It can be `null` if the client supports workspace folders but none are + configured. + + @since 3.6.0""" + # Since: 3.6.0 + + +@attrs.define +class InitializeResultServerInfoType: + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of the server as defined by the server.""" + + version: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The server's version as defined by the server.""" + + +@attrs.define +class InitializeResult: + """The result returned from an initialize request.""" + + capabilities: "ServerCapabilities" = attrs.field() + """The capabilities the language server provides.""" + + server_info: Optional["InitializeResultServerInfoType"] = attrs.field(default=None) + """Information about the server. + + @since 3.15.0""" + # Since: 3.15.0 + + +@attrs.define +class InitializeError: + """The data type of the ResponseError if the + initialize request fails.""" + + retry: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + """Indicates whether the client execute the following retry logic: + (1) show the message provided by the ResponseError to the user + (2) user selects retry or cancel + (3) if user selected retry the initialize method is sent again.""" + + +@attrs.define +class InitializedParams: + pass + + +@attrs.define +class DidChangeConfigurationParams: + """The parameters of a change configuration notification.""" + + settings: LSPAny = attrs.field() + """The actual changed settings""" + + +@attrs.define +class DidChangeConfigurationRegistrationOptions: + section: Optional[Union[str, List[str]]] = attrs.field(default=None) + + +@attrs.define +class ShowMessageParams: + """The parameters of a notification message.""" + + type: MessageType = attrs.field() + """The message type. See {@link MessageType}""" + + message: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The actual message.""" + + +@attrs.define +class ShowMessageRequestParams: + type: MessageType = attrs.field() + """The message type. See {@link MessageType}""" + + message: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The actual message.""" + + actions: Optional[List["MessageActionItem"]] = attrs.field(default=None) + """The message action items to present.""" + + +@attrs.define +class MessageActionItem: + title: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A short title like 'Retry', 'Open Log' etc.""" + + +@attrs.define +class LogMessageParams: + """The log message parameters.""" + + type: MessageType = attrs.field() + """The message type. See {@link MessageType}""" + + message: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The actual message.""" + + +@attrs.define +class DidOpenTextDocumentParams: + """The parameters sent in an open text document notification""" + + text_document: "TextDocumentItem" = attrs.field() + """The document that was opened.""" + + +@attrs.define +class DidChangeTextDocumentParams: + """The change text document notification's parameters.""" + + text_document: "VersionedTextDocumentIdentifier" = attrs.field() + """The document that did change. The version number points + to the version after all provided content changes have + been applied.""" + + content_changes: List[TextDocumentContentChangeEvent] = attrs.field() + """The actual content changes. The content changes describe single state changes + to the document. So if there are two content changes c1 (at array index 0) and + c2 (at array index 1) for a document in state S then c1 moves the document from + S to S' and c2 from S' to S''. So c1 is computed on the state S and c2 is computed + on the state S'. + + To mirror the content of a document using change events use the following approach: + - start with the same initial content + - apply the 'textDocument/didChange' notifications in the order you receive them. + - apply the `TextDocumentContentChangeEvent`s in a single notification in the order + you receive them.""" + + +@attrs.define +class TextDocumentChangeRegistrationOptions: + """Describe options to be used when registered for text document change events.""" + + sync_kind: TextDocumentSyncKind = attrs.field() + """How documents are synced to the server.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + +@attrs.define +class DidCloseTextDocumentParams: + """The parameters sent in a close text document notification""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document that was closed.""" + + +@attrs.define +class DidSaveTextDocumentParams: + """The parameters sent in a save text document notification""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document that was saved.""" + + text: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """Optional the content when saved. Depends on the includeText value + when the save notification was requested.""" + + +@attrs.define +class SaveOptions: + """Save options.""" + + include_text: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client is supposed to include the content on save.""" + + +@attrs.define +class TextDocumentSaveRegistrationOptions: + """Save registration options.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + include_text: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client is supposed to include the content on save.""" + + +@attrs.define +class WillSaveTextDocumentParams: + """The parameters sent in a will save text document notification.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document that will be saved.""" + + reason: TextDocumentSaveReason = attrs.field() + """The 'TextDocumentSaveReason'.""" + + +@attrs.define +class TextEdit: + """A text edit applicable to a text document.""" + + range: "Range" = attrs.field() + """The range of the text document to be manipulated. To insert + text into a document create a range where start === end.""" + + new_text: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The string to be inserted. For delete operations use an + empty string.""" + + +@attrs.define +class DidChangeWatchedFilesParams: + """The watched files change notification's parameters.""" + + changes: List["FileEvent"] = attrs.field() + """The actual file events.""" + + +@attrs.define +class DidChangeWatchedFilesRegistrationOptions: + """Describe options to be used when registered for text document change events.""" + + watchers: List["FileSystemWatcher"] = attrs.field() + """The watchers to register.""" + + +@attrs.define +class PublishDiagnosticsParams: + """The publish diagnostic notification's parameters.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The URI for which diagnostic information is reported.""" + + diagnostics: List["Diagnostic"] = attrs.field() + """An array of diagnostic information items.""" + + version: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.integer_validator), default=None + ) + """Optional the version number of the document the diagnostics are published for. + + @since 3.15.0""" + # Since: 3.15.0 + + +@attrs.define +class CompletionParams: + """Completion parameters""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + context: Optional["CompletionContext"] = attrs.field(default=None) + """The completion context. This is only available it the client specifies + to send this using the client capability `textDocument.completion.contextSupport === true`""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class CompletionItem: + """A completion item represents a text snippet that is + proposed to complete text that is being typed.""" + + label: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The label of this completion item. + + The label property is also by default the text that + is inserted when selecting this completion. + + If label details are provided the label itself should + be an unqualified name of the completion item.""" + + label_details: Optional["CompletionItemLabelDetails"] = attrs.field(default=None) + """Additional details for the label + + @since 3.17.0""" + # Since: 3.17.0 + + kind: Optional[CompletionItemKind] = attrs.field(default=None) + """The kind of this completion item. Based of the kind + an icon is chosen by the editor.""" + + tags: Optional[List[CompletionItemTag]] = attrs.field(default=None) + """Tags for this completion item. + + @since 3.15.0""" + # Since: 3.15.0 + + detail: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A human-readable string with additional information + about this item, like type or symbol information.""" + + documentation: Optional[Union[str, "MarkupContent"]] = attrs.field(default=None) + """A human-readable string that represents a doc-comment.""" + + deprecated: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Indicates if this item is deprecated. + @deprecated Use `tags` instead.""" + + preselect: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Select this item when showing. + + *Note* that only one completion item can be selected and that the + tool / client decides which item that is. The rule is that the *first* + item of those that match best is selected.""" + + sort_text: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A string that should be used when comparing this item + with other items. When `falsy` the {@link CompletionItem.label label} + is used.""" + + filter_text: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A string that should be used when filtering a set of + completion items. When `falsy` the {@link CompletionItem.label label} + is used.""" + + insert_text: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A string that should be inserted into a document when selecting + this completion. When `falsy` the {@link CompletionItem.label label} + is used. + + The `insertText` is subject to interpretation by the client side. + Some tools might not take the string literally. For example + VS Code when code complete is requested in this example + `con` and a completion item with an `insertText` of + `console` is provided it will only insert `sole`. Therefore it is + recommended to use `textEdit` instead since it avoids additional client + side interpretation.""" + + insert_text_format: Optional[InsertTextFormat] = attrs.field(default=None) + """The format of the insert text. The format applies to both the + `insertText` property and the `newText` property of a provided + `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`. + + Please note that the insertTextFormat doesn't apply to + `additionalTextEdits`.""" + + insert_text_mode: Optional[InsertTextMode] = attrs.field(default=None) + """How whitespace and indentation is handled during completion + item insertion. If not provided the clients default value depends on + the `textDocument.completion.insertTextMode` client capability. + + @since 3.16.0""" + # Since: 3.16.0 + + text_edit: Optional[Union[TextEdit, "InsertReplaceEdit"]] = attrs.field( + default=None + ) + """An {@link TextEdit edit} which is applied to a document when selecting + this completion. When an edit is provided the value of + {@link CompletionItem.insertText insertText} is ignored. + + Most editors support two different operations when accepting a completion + item. One is to insert a completion text and the other is to replace an + existing text with a completion text. Since this can usually not be + predetermined by a server it can report both ranges. Clients need to + signal support for `InsertReplaceEdits` via the + `textDocument.completion.insertReplaceSupport` client capability + property. + + *Note 1:* The text edit's range as well as both ranges from an insert + replace edit must be a [single line] and they must contain the position + at which completion has been requested. + *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range + must be a prefix of the edit's replace range, that means it must be + contained and starting at the same position. + + @since 3.16.0 additional type `InsertReplaceEdit`""" + # Since: 3.16.0 additional type `InsertReplaceEdit` + + text_edit_text: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The edit text used if the completion item is part of a CompletionList and + CompletionList defines an item default for the text edit range. + + Clients will only honor this property if they opt into completion list + item defaults using the capability `completionList.itemDefaults`. + + If not provided and a list's default range is provided the label + property is used as a text. + + @since 3.17.0""" + # Since: 3.17.0 + + additional_text_edits: Optional[List[TextEdit]] = attrs.field(default=None) + """An optional array of additional {@link TextEdit text edits} that are applied when + selecting this completion. Edits must not overlap (including the same insert position) + with the main {@link CompletionItem.textEdit edit} nor with themselves. + + Additional text edits should be used to change text unrelated to the current cursor position + (for example adding an import statement at the top of the file if the completion item will + insert an unqualified type).""" + + commit_characters: Optional[List[str]] = attrs.field(default=None) + """An optional set of characters that when pressed while this completion is active will accept it first and + then type that character. *Note* that all commit characters should have `length=1` and that superfluous + characters will be ignored.""" + + command: Optional["Command"] = attrs.field(default=None) + """An optional {@link Command command} that is executed *after* inserting this completion. *Note* that + additional modifications to the current document should be described with the + {@link CompletionItem.additionalTextEdits additionalTextEdits}-property.""" + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved on a completion item between a + {@link CompletionRequest} and a {@link CompletionResolveRequest}.""" + + +@attrs.define +class CompletionListItemDefaultsTypeEditRangeType1: + insert: "Range" = attrs.field() + + replace: "Range" = attrs.field() + + +@attrs.define +class CompletionListItemDefaultsType: + commit_characters: Optional[List[str]] = attrs.field(default=None) + """A default commit character set. + + @since 3.17.0""" + # Since: 3.17.0 + + edit_range: Optional[ + Union["Range", "CompletionListItemDefaultsTypeEditRangeType1"] + ] = attrs.field(default=None) + """A default edit range. + + @since 3.17.0""" + # Since: 3.17.0 + + insert_text_format: Optional[InsertTextFormat] = attrs.field(default=None) + """A default insert text format. + + @since 3.17.0""" + # Since: 3.17.0 + + insert_text_mode: Optional[InsertTextMode] = attrs.field(default=None) + """A default insert text mode. + + @since 3.17.0""" + # Since: 3.17.0 + + data: Optional[LSPAny] = attrs.field(default=None) + """A default data value. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class CompletionList: + """Represents a collection of {@link CompletionItem completion items} to be presented + in the editor.""" + + is_incomplete: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + """This list it not complete. Further typing results in recomputing this list. + + Recomputed lists have all their items replaced (not appended) in the + incomplete completion sessions.""" + + items: List[CompletionItem] = attrs.field() + """The completion items.""" + + item_defaults: Optional["CompletionListItemDefaultsType"] = attrs.field( + default=None + ) + """In many cases the items of an actual completion result share the same + value for properties like `commitCharacters` or the range of a text + edit. A completion list can therefore define item defaults which will + be used if a completion item itself doesn't specify the value. + + If a completion list specifies a default value and a completion item + also specifies a corresponding value the one from the item is used. + + Servers are only allowed to return default values if the client + signals support for this via the `completionList.itemDefaults` + capability. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class CompletionOptionsCompletionItemType: + label_details_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server has support for completion item label + details (see also `CompletionItemLabelDetails`) when + receiving a completion item in a resolve call. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class CompletionOptions: + """Completion options.""" + + trigger_characters: Optional[List[str]] = attrs.field(default=None) + """Most tools trigger completion request automatically without explicitly requesting + it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user + starts to type an identifier. For example if the user types `c` in a JavaScript file + code complete will automatically pop up present `console` besides others as a + completion item. Characters that make up identifiers don't need to be listed here. + + If code complete should automatically be trigger on characters not being valid inside + an identifier (for example `.` in JavaScript) list them in `triggerCharacters`.""" + + all_commit_characters: Optional[List[str]] = attrs.field(default=None) + """The list of all possible characters that commit a completion. This field can be used + if clients don't support individual commit characters per completion item. See + `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport` + + If a server provides both `allCommitCharacters` and commit characters on an individual + completion item the ones on the completion item win. + + @since 3.2.0""" + # Since: 3.2.0 + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server provides support to resolve additional + information for a completion item.""" + + completion_item: Optional["CompletionOptionsCompletionItemType"] = attrs.field( + default=None + ) + """The server supports the following `CompletionItem` specific + capabilities. + + @since 3.17.0""" + # Since: 3.17.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class CompletionRegistrationOptionsCompletionItemType: + label_details_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server has support for completion item label + details (see also `CompletionItemLabelDetails`) when + receiving a completion item in a resolve call. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class CompletionRegistrationOptions: + """Registration options for a {@link CompletionRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + trigger_characters: Optional[List[str]] = attrs.field(default=None) + """Most tools trigger completion request automatically without explicitly requesting + it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user + starts to type an identifier. For example if the user types `c` in a JavaScript file + code complete will automatically pop up present `console` besides others as a + completion item. Characters that make up identifiers don't need to be listed here. + + If code complete should automatically be trigger on characters not being valid inside + an identifier (for example `.` in JavaScript) list them in `triggerCharacters`.""" + + all_commit_characters: Optional[List[str]] = attrs.field(default=None) + """The list of all possible characters that commit a completion. This field can be used + if clients don't support individual commit characters per completion item. See + `ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport` + + If a server provides both `allCommitCharacters` and commit characters on an individual + completion item the ones on the completion item win. + + @since 3.2.0""" + # Since: 3.2.0 + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server provides support to resolve additional + information for a completion item.""" + + completion_item: Optional[ + "CompletionRegistrationOptionsCompletionItemType" + ] = attrs.field(default=None) + """The server supports the following `CompletionItem` specific + capabilities. + + @since 3.17.0""" + # Since: 3.17.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class HoverParams: + """Parameters for a {@link HoverRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class Hover: + """The result of a hover request.""" + + contents: Union["MarkupContent", MarkedString, List[MarkedString]] = attrs.field() + """The hover's content""" + + range: Optional["Range"] = attrs.field(default=None) + """An optional range inside the text document that is used to + visualize the hover, e.g. by changing the background color.""" + + +@attrs.define +class HoverOptions: + """Hover options.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class HoverRegistrationOptions: + """Registration options for a {@link HoverRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class SignatureHelpParams: + """Parameters for a {@link SignatureHelpRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + context: Optional["SignatureHelpContext"] = attrs.field(default=None) + """The signature help context. This is only available if the client specifies + to send this using the client capability `textDocument.signatureHelp.contextSupport === true` + + @since 3.15.0""" + # Since: 3.15.0 + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class SignatureHelp: + """Signature help represents the signature of something + callable. There can be multiple signature but only one + active and only one active parameter.""" + + signatures: List["SignatureInformation"] = attrs.field() + """One or more signatures.""" + + active_signature: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """The active signature. If omitted or the value lies outside the + range of `signatures` the value defaults to zero or is ignored if + the `SignatureHelp` has no signatures. + + Whenever possible implementors should make an active decision about + the active signature and shouldn't rely on a default value. + + In future version of the protocol this property might become + mandatory to better express this.""" + + active_parameter: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """The active parameter of the active signature. If omitted or the value + lies outside the range of `signatures[activeSignature].parameters` + defaults to 0 if the active signature has parameters. If + the active signature has no parameters it is ignored. + In future version of the protocol this property might become + mandatory to better express the active parameter if the + active signature does have any.""" + + +@attrs.define +class SignatureHelpOptions: + """Server Capabilities for a {@link SignatureHelpRequest}.""" + + trigger_characters: Optional[List[str]] = attrs.field(default=None) + """List of characters that trigger signature help automatically.""" + + retrigger_characters: Optional[List[str]] = attrs.field(default=None) + """List of characters that re-trigger signature help. + + These trigger characters are only active when signature help is already showing. All trigger characters + are also counted as re-trigger characters. + + @since 3.15.0""" + # Since: 3.15.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class SignatureHelpRegistrationOptions: + """Registration options for a {@link SignatureHelpRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + trigger_characters: Optional[List[str]] = attrs.field(default=None) + """List of characters that trigger signature help automatically.""" + + retrigger_characters: Optional[List[str]] = attrs.field(default=None) + """List of characters that re-trigger signature help. + + These trigger characters are only active when signature help is already showing. All trigger characters + are also counted as re-trigger characters. + + @since 3.15.0""" + # Since: 3.15.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DefinitionParams: + """Parameters for a {@link DefinitionRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class DefinitionOptions: + """Server Capabilities for a {@link DefinitionRequest}.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DefinitionRegistrationOptions: + """Registration options for a {@link DefinitionRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class ReferenceParams: + """Parameters for a {@link ReferencesRequest}.""" + + context: "ReferenceContext" = attrs.field() + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class ReferenceOptions: + """Reference options.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class ReferenceRegistrationOptions: + """Registration options for a {@link ReferencesRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentHighlightParams: + """Parameters for a {@link DocumentHighlightRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class DocumentHighlight: + """A document highlight is a range inside a text document which deserves + special attention. Usually a document highlight is visualized by changing + the background color of its range.""" + + range: "Range" = attrs.field() + """The range this highlight applies to.""" + + kind: Optional[DocumentHighlightKind] = attrs.field(default=None) + """The highlight kind, default is {@link DocumentHighlightKind.Text text}.""" + + +@attrs.define +class DocumentHighlightOptions: + """Provider options for a {@link DocumentHighlightRequest}.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentHighlightRegistrationOptions: + """Registration options for a {@link DocumentHighlightRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentSymbolParams: + """Parameters for a {@link DocumentSymbolRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class BaseSymbolInformation: + """A base for all symbol information.""" + + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of this symbol.""" + + kind: SymbolKind = attrs.field() + """The kind of this symbol.""" + + tags: Optional[List[SymbolTag]] = attrs.field(default=None) + """Tags for this symbol. + + @since 3.16.0""" + # Since: 3.16.0 + + container_name: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The name of the symbol containing this symbol. This information is for + user interface purposes (e.g. to render a qualifier in the user interface + if necessary). It can't be used to re-infer a hierarchy for the document + symbols.""" + + +@attrs.define +class SymbolInformation: + """Represents information about programming constructs like variables, classes, + interfaces etc.""" + + location: Location = attrs.field() + """The location of this symbol. The location's range is used by a tool + to reveal the location in the editor. If the symbol is selected in the + tool the range's start information is used to position the cursor. So + the range usually spans more than the actual symbol's name and does + normally include things like visibility modifiers. + + The range doesn't have to denote a node range in the sense of an abstract + syntax tree. It can therefore not be used to re-construct a hierarchy of + the symbols.""" + + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of this symbol.""" + + kind: SymbolKind = attrs.field() + """The kind of this symbol.""" + + deprecated: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Indicates if this symbol is deprecated. + + @deprecated Use tags instead""" + + tags: Optional[List[SymbolTag]] = attrs.field(default=None) + """Tags for this symbol. + + @since 3.16.0""" + # Since: 3.16.0 + + container_name: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The name of the symbol containing this symbol. This information is for + user interface purposes (e.g. to render a qualifier in the user interface + if necessary). It can't be used to re-infer a hierarchy for the document + symbols.""" + + +@attrs.define +class DocumentSymbol: + """Represents programming constructs like variables, classes, interfaces etc. + that appear in a document. Document symbols can be hierarchical and they + have two ranges: one that encloses its definition and one that points to + its most interesting range, e.g. the range of an identifier.""" + + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of this symbol. Will be displayed in the user interface and therefore must not be + an empty string or a string only consisting of white spaces.""" + + kind: SymbolKind = attrs.field() + """The kind of this symbol.""" + + range: "Range" = attrs.field() + """The range enclosing this symbol not including leading/trailing whitespace but everything else + like comments. This information is typically used to determine if the clients cursor is + inside the symbol to reveal in the symbol in the UI.""" + + selection_range: "Range" = attrs.field() + """The range that should be selected and revealed when this symbol is being picked, e.g the name of a function. + Must be contained by the `range`.""" + + detail: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """More detail for this symbol, e.g the signature of a function.""" + + tags: Optional[List[SymbolTag]] = attrs.field(default=None) + """Tags for this document symbol. + + @since 3.16.0""" + # Since: 3.16.0 + + deprecated: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Indicates if this symbol is deprecated. + + @deprecated Use tags instead""" + + children: Optional[List["DocumentSymbol"]] = attrs.field(default=None) + """Children of this symbol, e.g. properties of a class.""" + + +@attrs.define +class DocumentSymbolOptions: + """Provider options for a {@link DocumentSymbolRequest}.""" + + label: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A human-readable string that is shown when multiple outlines trees + are shown for the same document. + + @since 3.16.0""" + # Since: 3.16.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentSymbolRegistrationOptions: + """Registration options for a {@link DocumentSymbolRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + label: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A human-readable string that is shown when multiple outlines trees + are shown for the same document. + + @since 3.16.0""" + # Since: 3.16.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class CodeActionParams: + """The parameters of a {@link CodeActionRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document in which the command was invoked.""" + + range: "Range" = attrs.field() + """The range for which the command was invoked.""" + + context: "CodeActionContext" = attrs.field() + """Context carrying additional information.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class Command: + """Represents a reference to a command. Provides a title which + will be used to represent a command in the UI and, optionally, + an array of arguments which will be passed to the command handler + function when invoked.""" + + title: str = attrs.field(validator=attrs.validators.instance_of(str)) + """Title of the command, like `save`.""" + + command: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The identifier of the actual command handler.""" + + arguments: Optional[List[LSPAny]] = attrs.field(default=None) + """Arguments that the command handler should be + invoked with.""" + + +@attrs.define +class CodeActionDisabledType: + reason: str = attrs.field(validator=attrs.validators.instance_of(str)) + """Human readable description of why the code action is currently disabled. + + This is displayed in the code actions UI.""" + + +@attrs.define +class CodeAction: + """A code action represents a change that can be performed in code, e.g. to fix a problem or + to refactor code. + + A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed. + """ + + title: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A short, human-readable, title for this code action.""" + + kind: Optional[Union[CodeActionKind, str]] = attrs.field(default=None) + """The kind of the code action. + + Used to filter code actions.""" + + diagnostics: Optional[List["Diagnostic"]] = attrs.field(default=None) + """The diagnostics that this code action resolves.""" + + is_preferred: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted + by keybindings. + + A quick fix should be marked preferred if it properly addresses the underlying error. + A refactoring should be marked preferred if it is the most reasonable choice of actions to take. + + @since 3.15.0""" + # Since: 3.15.0 + + disabled: Optional["CodeActionDisabledType"] = attrs.field(default=None) + """Marks that the code action cannot currently be applied. + + Clients should follow the following guidelines regarding disabled code actions: + + - Disabled code actions are not shown in automatic [lightbulbs](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) + code action menus. + + - Disabled actions are shown as faded out in the code action menu when the user requests a more specific type + of code action, such as refactorings. + + - If the user has a [keybinding](https://code.visualstudio.com/docs/editor/refactoring#_keybindings-for-code-actions) + that auto applies a code action and only disabled code actions are returned, the client should show the user an + error message with `reason` in the editor. + + @since 3.16.0""" + # Since: 3.16.0 + + edit: Optional[WorkspaceEdit] = attrs.field(default=None) + """The workspace edit this code action performs.""" + + command: Optional[Command] = attrs.field(default=None) + """A command this code action executes. If a code action + provides an edit and a command, first the edit is + executed and then the command.""" + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved on a code action between + a `textDocument/codeAction` and a `codeAction/resolve` request. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class CodeActionOptions: + """Provider options for a {@link CodeActionRequest}.""" + + code_action_kinds: Optional[List[Union[CodeActionKind, str]]] = attrs.field( + default=None + ) + """CodeActionKinds that this server may return. + + The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server + may list out every specific kind they provide.""" + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server provides support to resolve additional + information for a code action. + + @since 3.16.0""" + # Since: 3.16.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class CodeActionRegistrationOptions: + """Registration options for a {@link CodeActionRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + code_action_kinds: Optional[List[Union[CodeActionKind, str]]] = attrs.field( + default=None + ) + """CodeActionKinds that this server may return. + + The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server + may list out every specific kind they provide.""" + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server provides support to resolve additional + information for a code action. + + @since 3.16.0""" + # Since: 3.16.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class WorkspaceSymbolParams: + """The parameters of a {@link WorkspaceSymbolRequest}.""" + + query: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A query string to filter symbols by. Clients may send an empty + string here to request all symbols.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class WorkspaceSymbolLocationType1: + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + + +@attrs.define +class WorkspaceSymbol: + """A special workspace symbol that supports locations without a range. + + See also SymbolInformation. + + @since 3.17.0""" + + # Since: 3.17.0 + + location: Union[Location, "WorkspaceSymbolLocationType1"] = attrs.field() + """The location of the symbol. Whether a server is allowed to + return a location without a range depends on the client + capability `workspace.symbol.resolveSupport`. + + See SymbolInformation#location for more details.""" + + name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of this symbol.""" + + kind: SymbolKind = attrs.field() + """The kind of this symbol.""" + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved on a workspace symbol between a + workspace symbol request and a workspace symbol resolve request.""" + + tags: Optional[List[SymbolTag]] = attrs.field(default=None) + """Tags for this symbol. + + @since 3.16.0""" + # Since: 3.16.0 + + container_name: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The name of the symbol containing this symbol. This information is for + user interface purposes (e.g. to render a qualifier in the user interface + if necessary). It can't be used to re-infer a hierarchy for the document + symbols.""" + + +@attrs.define +class WorkspaceSymbolOptions: + """Server capabilities for a {@link WorkspaceSymbolRequest}.""" + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server provides support to resolve additional + information for a workspace symbol. + + @since 3.17.0""" + # Since: 3.17.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class WorkspaceSymbolRegistrationOptions: + """Registration options for a {@link WorkspaceSymbolRequest}.""" + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server provides support to resolve additional + information for a workspace symbol. + + @since 3.17.0""" + # Since: 3.17.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class CodeLensParams: + """The parameters of a {@link CodeLensRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document to request code lens for.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class CodeLens: + """A code lens represents a {@link Command command} that should be shown along with + source text, like the number of references, a way to run tests, etc. + + A code lens is _unresolved_ when no command is associated to it. For performance + reasons the creation of a code lens and resolving should be done in two stages.""" + + range: "Range" = attrs.field() + """The range in which this code lens is valid. Should only span a single line.""" + + command: Optional[Command] = attrs.field(default=None) + """The command this code lens represents.""" + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved on a code lens item between + a {@link CodeLensRequest} and a {@link CodeLensResolveRequest}""" + + +@attrs.define +class CodeLensOptions: + """Code Lens provider options of a {@link CodeLensRequest}.""" + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Code lens has a resolve provider as well.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class CodeLensRegistrationOptions: + """Registration options for a {@link CodeLensRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Code lens has a resolve provider as well.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentLinkParams: + """The parameters of a {@link DocumentLinkRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document to provide document links for.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + partial_result_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report partial results (e.g. streaming) to + the client.""" + + +@attrs.define +class DocumentLink: + """A document link is a range in a text document that links to an internal or external resource, like another + text document or a web site.""" + + range: "Range" = attrs.field() + """The range this link applies to.""" + + target: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The uri this link points to. If missing a resolve request is sent later.""" + + tooltip: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The tooltip text when you hover over this link. + + If a tooltip is provided, is will be displayed in a string that includes instructions on how to + trigger the link, such as `{0} (ctrl + click)`. The specific instructions vary depending on OS, + user settings, and localization. + + @since 3.15.0""" + # Since: 3.15.0 + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved on a document link between a + DocumentLinkRequest and a DocumentLinkResolveRequest.""" + + +@attrs.define +class DocumentLinkOptions: + """Provider options for a {@link DocumentLinkRequest}.""" + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Document links have a resolve provider as well.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentLinkRegistrationOptions: + """Registration options for a {@link DocumentLinkRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + resolve_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Document links have a resolve provider as well.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentFormattingParams: + """The parameters of a {@link DocumentFormattingRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document to format.""" + + options: "FormattingOptions" = attrs.field() + """The format options.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class DocumentFormattingOptions: + """Provider options for a {@link DocumentFormattingRequest}.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentFormattingRegistrationOptions: + """Registration options for a {@link DocumentFormattingRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentRangeFormattingParams: + """The parameters of a {@link DocumentRangeFormattingRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document to format.""" + + range: "Range" = attrs.field() + """The range to format""" + + options: "FormattingOptions" = attrs.field() + """The format options""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class DocumentRangeFormattingOptions: + """Provider options for a {@link DocumentRangeFormattingRequest}.""" + + ranges_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the server supports formatting multiple ranges at once. + + @since 3.18.0 + @proposed""" + # Since: 3.18.0 + # Proposed + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentRangeFormattingRegistrationOptions: + """Registration options for a {@link DocumentRangeFormattingRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + ranges_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the server supports formatting multiple ranges at once. + + @since 3.18.0 + @proposed""" + # Since: 3.18.0 + # Proposed + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class DocumentRangesFormattingParams: + """The parameters of a {@link DocumentRangesFormattingRequest}. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document to format.""" + + ranges: List["Range"] = attrs.field() + """The ranges to format""" + + options: "FormattingOptions" = attrs.field() + """The format options""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class DocumentOnTypeFormattingParams: + """The parameters of a {@link DocumentOnTypeFormattingRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document to format.""" + + position: "Position" = attrs.field() + """The position around which the on type formatting should happen. + This is not necessarily the exact position where the character denoted + by the property `ch` got typed.""" + + ch: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The character that has been typed that triggered the formatting + on type request. That is not necessarily the last character that + got inserted into the document since the client could auto insert + characters as well (e.g. like automatic brace completion).""" + + options: "FormattingOptions" = attrs.field() + """The formatting options.""" + + +@attrs.define +class DocumentOnTypeFormattingOptions: + """Provider options for a {@link DocumentOnTypeFormattingRequest}.""" + + first_trigger_character: str = attrs.field( + validator=attrs.validators.instance_of(str) + ) + """A character on which formatting should be triggered, like `{`.""" + + more_trigger_character: Optional[List[str]] = attrs.field(default=None) + """More trigger characters.""" + + +@attrs.define +class DocumentOnTypeFormattingRegistrationOptions: + """Registration options for a {@link DocumentOnTypeFormattingRequest}.""" + + first_trigger_character: str = attrs.field( + validator=attrs.validators.instance_of(str) + ) + """A character on which formatting should be triggered, like `{`.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + more_trigger_character: Optional[List[str]] = attrs.field(default=None) + """More trigger characters.""" + + +@attrs.define +class RenameParams: + """The parameters of a {@link RenameRequest}.""" + + text_document: "TextDocumentIdentifier" = attrs.field() + """The document to rename.""" + + position: "Position" = attrs.field() + """The position at which this request was sent.""" + + new_name: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The new name of the symbol. If the given name is not valid the + request must return a {@link ResponseError} with an + appropriate message set.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class RenameOptions: + """Provider options for a {@link RenameRequest}.""" + + prepare_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Renames should be checked and tested before being executed. + + @since version 3.12.0""" + # Since: version 3.12.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class RenameRegistrationOptions: + """Registration options for a {@link RenameRequest}.""" + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + prepare_provider: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Renames should be checked and tested before being executed. + + @since version 3.12.0""" + # Since: version 3.12.0 + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class PrepareRenameParams: + text_document: "TextDocumentIdentifier" = attrs.field() + """The text document.""" + + position: "Position" = attrs.field() + """The position inside the text document.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class ExecuteCommandParams: + """The parameters of a {@link ExecuteCommandRequest}.""" + + command: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The identifier of the actual command handler.""" + + arguments: Optional[List[LSPAny]] = attrs.field(default=None) + """Arguments that the command should be invoked with.""" + + work_done_token: Optional[ProgressToken] = attrs.field(default=None) + """An optional token that a server can use to report work done progress.""" + + +@attrs.define +class ExecuteCommandOptions: + """The server capabilities of a {@link ExecuteCommandRequest}.""" + + commands: List[str] = attrs.field() + """The commands to be executed on the server""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class ExecuteCommandRegistrationOptions: + """Registration options for a {@link ExecuteCommandRequest}.""" + + commands: List[str] = attrs.field() + """The commands to be executed on the server""" + + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + +@attrs.define +class ApplyWorkspaceEditParams: + """The parameters passed via an apply workspace edit request.""" + + edit: WorkspaceEdit = attrs.field() + """The edits to apply.""" + + label: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional label of the workspace edit. This label is + presented in the user interface for example on an undo + stack to undo the workspace edit.""" + + +@attrs.define +class ApplyWorkspaceEditResult: + """The result returned from the apply workspace edit request. + + @since 3.17 renamed from ApplyWorkspaceEditResponse""" + + # Since: 3.17 renamed from ApplyWorkspaceEditResponse + + applied: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + """Indicates whether the edit was applied or not.""" + + failure_reason: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional textual description for why the edit was not applied. + This may be used by the server for diagnostic logging or to provide + a suitable error for a request that triggered the edit.""" + + failed_change: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """Depending on the client's failure handling strategy `failedChange` might + contain the index of the change that failed. This property is only available + if the client signals a `failureHandlingStrategy` in its client capabilities.""" + + +@attrs.define +class WorkDoneProgressBegin: + title: str = attrs.field(validator=attrs.validators.instance_of(str)) + """Mandatory title of the progress operation. Used to briefly inform about + the kind of operation being performed. + + Examples: "Indexing" or "Linking dependencies".""" + + kind: str = attrs.field(validator=attrs.validators.in_(["begin"]), default="begin") + + cancellable: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Controls if a cancel button should show to allow the user to cancel the + long running operation. Clients that don't support cancellation are allowed + to ignore the setting.""" + + message: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """Optional, more detailed associated progress message. Contains + complementary information to the `title`. + + Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + If unset, the previous progress message (if any) is still valid.""" + + percentage: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """Optional progress percentage to display (value 100 is considered 100%). + If not provided infinite progress is assumed and clients are allowed + to ignore the `percentage` value in subsequent in report notifications. + + The value should be steadily rising. Clients are free to ignore values + that are not following this rule. The value range is [0, 100].""" + + +@attrs.define +class WorkDoneProgressReport: + kind: str = attrs.field( + validator=attrs.validators.in_(["report"]), default="report" + ) + + cancellable: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Controls enablement state of a cancel button. + + Clients that don't support cancellation or don't support controlling the button's + enablement state are allowed to ignore the property.""" + + message: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """Optional, more detailed associated progress message. Contains + complementary information to the `title`. + + Examples: "3/25 files", "project/src/module2", "node_modules/some_dep". + If unset, the previous progress message (if any) is still valid.""" + + percentage: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """Optional progress percentage to display (value 100 is considered 100%). + If not provided infinite progress is assumed and clients are allowed + to ignore the `percentage` value in subsequent in report notifications. + + The value should be steadily rising. Clients are free to ignore values + that are not following this rule. The value range is [0, 100]""" + + +@attrs.define +class WorkDoneProgressEnd: + kind: str = attrs.field(validator=attrs.validators.in_(["end"]), default="end") + + message: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """Optional, a final message indicating to for example indicate the outcome + of the operation.""" + + +@attrs.define +class SetTraceParams: + value: TraceValues = attrs.field() + + +@attrs.define +class LogTraceParams: + message: str = attrs.field(validator=attrs.validators.instance_of(str)) + + verbose: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + + +@attrs.define +class CancelParams: + id: Union[int, str] = attrs.field() + """The request id to cancel.""" + + +@attrs.define +class ProgressParams: + token: ProgressToken = attrs.field() + """The progress token provided by the client or server.""" + + value: LSPAny = attrs.field() + """The progress data.""" + + +@attrs.define +class LocationLink: + """Represents the connection of two locations. Provides additional metadata over normal {@link Location locations}, + including an origin range.""" + + target_uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The target resource identifier of this link.""" + + target_range: "Range" = attrs.field() + """The full target range of this link. If the target for example is a symbol then target range is the + range enclosing this symbol not including leading/trailing whitespace but everything else + like comments. This information is typically used to highlight the range in the editor.""" + + target_selection_range: "Range" = attrs.field() + """The range that should be selected and revealed when this link is being followed, e.g the name of a function. + Must be contained by the `targetRange`. See also `DocumentSymbol#range`""" + + origin_selection_range: Optional["Range"] = attrs.field(default=None) + """Span of the origin of this link. + + Used as the underlined span for mouse interaction. Defaults to the word range at + the definition position.""" + + +@attrs.define +class Range: + """A range in a text document expressed as (zero-based) start and end positions. + + If you want to specify a range that contains a line including the line ending + character(s) then use an end position denoting the start of the next line. + For example: + ```ts + { + start: { line: 5, character: 23 } + end : { line 6, character : 0 } + } + ```""" + + start: "Position" = attrs.field() + """The range's start position.""" + + end: "Position" = attrs.field() + """The range's end position.""" + + def __eq__(self, o: object) -> bool: + if not isinstance(o, Range): + return NotImplemented + return (self.start == o.start) and (self.end == o.end) + + def __repr__(self) -> str: + return f"{self.start!r}-{self.end!r}" + + +@attrs.define +class WorkspaceFoldersChangeEvent: + """The workspace folder change event.""" + + added: List[WorkspaceFolder] = attrs.field() + """The array of added workspace folders""" + + removed: List[WorkspaceFolder] = attrs.field() + """The array of the removed workspace folders""" + + +@attrs.define +class ConfigurationItem: + scope_uri: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The scope to get the configuration section for.""" + + section: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The configuration section asked for.""" + + +@attrs.define +class TextDocumentIdentifier: + """A literal to identify a text document in the client.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The text document's uri.""" + + +@attrs.define +class Color: + """Represents a color in RGBA space.""" + + red: float = attrs.field(validator=attrs.validators.instance_of(float)) + """The red component of this color in the range [0-1].""" + + green: float = attrs.field(validator=attrs.validators.instance_of(float)) + """The green component of this color in the range [0-1].""" + + blue: float = attrs.field(validator=attrs.validators.instance_of(float)) + """The blue component of this color in the range [0-1].""" + + alpha: float = attrs.field(validator=attrs.validators.instance_of(float)) + """The alpha component of this color in the range [0-1].""" + + +@attrs.define +@functools.total_ordering +class Position: + """Position in a text document expressed as zero-based line and character + offset. Prior to 3.17 the offsets were always based on a UTF-16 string + representation. So a string of the form `a𐐀b` the character offset of the + character `a` is 0, the character offset of `𐐀` is 1 and the character + offset of b is 3 since `𐐀` is represented using two code units in UTF-16. + Since 3.17 clients and servers can agree on a different string encoding + representation (e.g. UTF-8). The client announces it's supported encoding + via the client capability [`general.positionEncodings`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#clientCapabilities). + The value is an array of position encodings the client supports, with + decreasing preference (e.g. the encoding at index `0` is the most preferred + one). To stay backwards compatible the only mandatory encoding is UTF-16 + represented via the string `utf-16`. The server can pick one of the + encodings offered by the client and signals that encoding back to the + client via the initialize result's property + [`capabilities.positionEncoding`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#serverCapabilities). If the string value + `utf-16` is missing from the client's capability `general.positionEncodings` + servers can safely assume that the client supports UTF-16. If the server + omits the position encoding in its initialize result the encoding defaults + to the string value `utf-16`. Implementation considerations: since the + conversion from one encoding into another requires the content of the + file / line the conversion is best done where the file is read which is + usually on the server side. + + Positions are line end character agnostic. So you can not specify a position + that denotes `\r|\n` or `\n|` where `|` represents the character offset. + + @since 3.17.0 - support for negotiated position encoding.""" + + # Since: 3.17.0 - support for negotiated position encoding. + + line: int = attrs.field(validator=validators.uinteger_validator) + """Line position in a document (zero-based). + + If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document. + If a line number is negative, it defaults to 0.""" + + character: int = attrs.field(validator=validators.uinteger_validator) + """Character offset on a line in a document (zero-based). + + The meaning of this offset is determined by the negotiated + `PositionEncodingKind`. + + If the character value is greater than the line length it defaults back to the + line length.""" + + def __eq__(self, o: object) -> bool: + if not isinstance(o, Position): + return NotImplemented + return (self.line, self.character) == (o.line, o.character) + + def __gt__(self, o: "Position") -> bool: + if not isinstance(o, Position): + return NotImplemented + return (self.line, self.character) > (o.line, o.character) + + def __repr__(self) -> str: + return f"{self.line}:{self.character}" + + +@attrs.define +class SemanticTokensEdit: + """@since 3.16.0""" + + # Since: 3.16.0 + + start: int = attrs.field(validator=validators.uinteger_validator) + """The start offset of the edit.""" + + delete_count: int = attrs.field(validator=validators.uinteger_validator) + """The count of elements to remove.""" + + data: Optional[List[int]] = attrs.field(default=None) + """The elements to insert.""" + + +@attrs.define +class FileCreate: + """Represents information on a file/folder create. + + @since 3.16.0""" + + # Since: 3.16.0 + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A file:// URI for the location of the file/folder being created.""" + + +@attrs.define +class TextDocumentEdit: + """Describes textual changes on a text document. A TextDocumentEdit describes all changes + on a document version Si and after they are applied move the document to version Si+1. + So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any + kind of ordering. However the edits must be non overlapping.""" + + text_document: "OptionalVersionedTextDocumentIdentifier" = attrs.field() + """The text document to change.""" + + edits: List[Union[TextEdit, "AnnotatedTextEdit"]] = attrs.field() + """The edits to be applied. + + @since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a + client capability.""" + # Since: 3.16.0 - support for AnnotatedTextEdit. This is guarded using aclient capability. + + +@attrs.define +class ResourceOperation: + """A generic resource operation.""" + + kind: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The resource operation kind.""" + + annotation_id: Optional[ChangeAnnotationIdentifier] = attrs.field(default=None) + """An optional annotation identifier describing the operation. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class CreateFile: + """Create file operation.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The resource to create.""" + + kind: str = attrs.field( + validator=attrs.validators.in_(["create"]), default="create" + ) + """A create""" + + options: Optional["CreateFileOptions"] = attrs.field(default=None) + """Additional options""" + + annotation_id: Optional[ChangeAnnotationIdentifier] = attrs.field(default=None) + """An optional annotation identifier describing the operation. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class RenameFile: + """Rename file operation""" + + old_uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The old (existing) location.""" + + new_uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The new location.""" + + kind: str = attrs.field( + validator=attrs.validators.in_(["rename"]), default="rename" + ) + """A rename""" + + options: Optional["RenameFileOptions"] = attrs.field(default=None) + """Rename options.""" + + annotation_id: Optional[ChangeAnnotationIdentifier] = attrs.field(default=None) + """An optional annotation identifier describing the operation. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class DeleteFile: + """Delete file operation""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The file to delete.""" + + kind: str = attrs.field( + validator=attrs.validators.in_(["delete"]), default="delete" + ) + """A delete""" + + options: Optional["DeleteFileOptions"] = attrs.field(default=None) + """Delete options.""" + + annotation_id: Optional[ChangeAnnotationIdentifier] = attrs.field(default=None) + """An optional annotation identifier describing the operation. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class ChangeAnnotation: + """Additional information that describes document changes. + + @since 3.16.0""" + + # Since: 3.16.0 + + label: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A human-readable string describing the actual change. The string + is rendered prominent in the user interface.""" + + needs_confirmation: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """A flag which indicates that user confirmation is needed + before applying the change.""" + + description: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A human-readable string which is rendered less prominent in + the user interface.""" + + +@attrs.define +class FileOperationFilter: + """A filter to describe in which file operation requests or notifications + the server is interested in receiving. + + @since 3.16.0""" + + # Since: 3.16.0 + + pattern: "FileOperationPattern" = attrs.field() + """The actual file operation pattern.""" + + scheme: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A Uri scheme like `file` or `untitled`.""" + + +@attrs.define +class FileRename: + """Represents information on a file/folder rename. + + @since 3.16.0""" + + # Since: 3.16.0 + + old_uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A file:// URI for the original location of the file/folder being renamed.""" + + new_uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A file:// URI for the new location of the file/folder being renamed.""" + + +@attrs.define +class FileDelete: + """Represents information on a file/folder delete. + + @since 3.16.0""" + + # Since: 3.16.0 + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A file:// URI for the location of the file/folder being deleted.""" + + +@attrs.define +class InlineValueContext: + """@since 3.17.0""" + + # Since: 3.17.0 + + frame_id: int = attrs.field(validator=validators.integer_validator) + """The stack frame (as a DAP Id) where the execution has stopped.""" + + stopped_location: Range = attrs.field() + """The document range where execution has stopped. + Typically the end position of the range denotes the line where the inline values are shown.""" + + +@attrs.define +class InlineValueText: + """Provide inline value as text. + + @since 3.17.0""" + + # Since: 3.17.0 + + range: Range = attrs.field() + """The document range for which the inline value applies.""" + + text: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The text of the inline value.""" + + +@attrs.define +class InlineValueVariableLookup: + """Provide inline value through a variable lookup. + If only a range is specified, the variable name will be extracted from the underlying document. + An optional variable name can be used to override the extracted name. + + @since 3.17.0""" + + # Since: 3.17.0 + + range: Range = attrs.field() + """The document range for which the inline value applies. + The range is used to extract the variable name from the underlying document.""" + + case_sensitive_lookup: bool = attrs.field( + validator=attrs.validators.instance_of(bool) + ) + """How to perform the lookup.""" + + variable_name: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """If specified the name of the variable to look up.""" + + +@attrs.define +class InlineValueEvaluatableExpression: + """Provide an inline value through an expression evaluation. + If only a range is specified, the expression will be extracted from the underlying document. + An optional expression can be used to override the extracted expression. + + @since 3.17.0""" + + # Since: 3.17.0 + + range: Range = attrs.field() + """The document range for which the inline value applies. + The range is used to extract the evaluatable expression from the underlying document.""" + + expression: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """If specified the expression overrides the extracted expression.""" + + +@attrs.define +class InlayHintLabelPart: + """An inlay hint label part allows for interactive and composite labels + of inlay hints. + + @since 3.17.0""" + + # Since: 3.17.0 + + value: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The value of this label part.""" + + tooltip: Optional[Union[str, "MarkupContent"]] = attrs.field(default=None) + """The tooltip text when you hover over this label part. Depending on + the client capability `inlayHint.resolveSupport` clients might resolve + this property late using the resolve request.""" + + location: Optional[Location] = attrs.field(default=None) + """An optional source code location that represents this + label part. + + The editor will use this location for the hover and for code navigation + features: This part will become a clickable link that resolves to the + definition of the symbol at the given location (not necessarily the + location itself), it shows the hover that shows at the given location, + and it shows a context menu with further code navigation commands. + + Depending on the client capability `inlayHint.resolveSupport` clients + might resolve this property late using the resolve request.""" + + command: Optional[Command] = attrs.field(default=None) + """An optional command for this label part. + + Depending on the client capability `inlayHint.resolveSupport` clients + might resolve this property late using the resolve request.""" + + +@attrs.define +class MarkupContent: + """A `MarkupContent` literal represents a string value which content is interpreted base on its + kind flag. Currently the protocol supports `plaintext` and `markdown` as markup kinds. + + If the kind is `markdown` then the value can contain fenced code blocks like in GitHub issues. + See https://help.github.com/articles/creating-and-highlighting-code-blocks/#syntax-highlighting + + Here is an example how such a string can be constructed using JavaScript / TypeScript: + ```ts + let markdown: MarkdownContent = { + kind: MarkupKind.Markdown, + value: [ + '# Header', + 'Some text', + '```typescript', + 'someCode();', + '```' + ].join('\n') + }; + ``` + + *Please Note* that clients might sanitize the return markdown. A client could decide to + remove HTML from the markdown to avoid script execution.""" + + kind: MarkupKind = attrs.field() + """The type of the Markup""" + + value: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The content itself""" + + +@attrs.define +class FullDocumentDiagnosticReport: + """A diagnostic report with a full set of problems. + + @since 3.17.0""" + + # Since: 3.17.0 + + items: List["Diagnostic"] = attrs.field() + """The actual items.""" + + kind: str = attrs.field(validator=attrs.validators.in_(["full"]), default="full") + """A full document diagnostic report.""" + + result_id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional result id. If provided it will + be sent on the next diagnostic request for the + same document.""" + + +@attrs.define +class RelatedFullDocumentDiagnosticReport: + """A full diagnostic report with a set of related documents. + + @since 3.17.0""" + + # Since: 3.17.0 + + items: List["Diagnostic"] = attrs.field() + """The actual items.""" + + related_documents: Optional[ + Dict[ + str, + Union[FullDocumentDiagnosticReport, "UnchangedDocumentDiagnosticReport"], + ] + ] = attrs.field(default=None) + """Diagnostics of related documents. This information is useful + in programming languages where code in a file A can generate + diagnostics in a file B which A depends on. An example of + such a language is C/C++ where marco definitions in a file + a.cpp and result in errors in a header file b.hpp. + + @since 3.17.0""" + # Since: 3.17.0 + + kind: str = attrs.field(validator=attrs.validators.in_(["full"]), default="full") + """A full document diagnostic report.""" + + result_id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional result id. If provided it will + be sent on the next diagnostic request for the + same document.""" + + +@attrs.define +class UnchangedDocumentDiagnosticReport: + """A diagnostic report indicating that the last returned + report is still accurate. + + @since 3.17.0""" + + # Since: 3.17.0 + + result_id: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A result id which will be sent on the next + diagnostic request for the same document.""" + + kind: str = attrs.field( + validator=attrs.validators.in_(["unchanged"]), default="unchanged" + ) + """A document diagnostic report indicating + no changes to the last result. A server can + only return `unchanged` if result ids are + provided.""" + + +@attrs.define +class RelatedUnchangedDocumentDiagnosticReport: + """An unchanged diagnostic report with a set of related documents. + + @since 3.17.0""" + + # Since: 3.17.0 + + result_id: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A result id which will be sent on the next + diagnostic request for the same document.""" + + related_documents: Optional[ + Dict[ + str, Union[FullDocumentDiagnosticReport, UnchangedDocumentDiagnosticReport] + ] + ] = attrs.field(default=None) + """Diagnostics of related documents. This information is useful + in programming languages where code in a file A can generate + diagnostics in a file B which A depends on. An example of + such a language is C/C++ where marco definitions in a file + a.cpp and result in errors in a header file b.hpp. + + @since 3.17.0""" + # Since: 3.17.0 + + kind: str = attrs.field( + validator=attrs.validators.in_(["unchanged"]), default="unchanged" + ) + """A document diagnostic report indicating + no changes to the last result. A server can + only return `unchanged` if result ids are + provided.""" + + +@attrs.define +class PreviousResultId: + """A previous result id in a workspace pull request. + + @since 3.17.0""" + + # Since: 3.17.0 + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The URI for which the client knowns a + result id.""" + + value: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The value of the previous result id.""" + + +@attrs.define +class NotebookDocument: + """A notebook document. + + @since 3.17.0""" + + # Since: 3.17.0 + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The notebook document's uri.""" + + notebook_type: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The type of the notebook.""" + + version: int = attrs.field(validator=validators.integer_validator) + """The version number of this document (it will increase after each + change, including undo/redo).""" + + cells: List["NotebookCell"] = attrs.field() + """The cells of a notebook.""" + + metadata: Optional[LSPObject] = attrs.field(default=None) + """Additional metadata stored with the notebook + document. + + Note: should always be an object literal (e.g. LSPObject)""" + + +@attrs.define +class TextDocumentItem: + """An item to transfer a text document from the client to the + server.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The text document's uri.""" + + language_id: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The text document's language identifier.""" + + version: int = attrs.field(validator=validators.integer_validator) + """The version number of this document (it will increase after each + change, including undo/redo).""" + + text: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The content of the opened text document.""" + + +@attrs.define +class VersionedNotebookDocumentIdentifier: + """A versioned notebook document identifier. + + @since 3.17.0""" + + # Since: 3.17.0 + + version: int = attrs.field(validator=validators.integer_validator) + """The version number of this notebook document.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The notebook document's uri.""" + + +@attrs.define +class NotebookDocumentChangeEventCellsTypeStructureType: + array: "NotebookCellArrayChange" = attrs.field() + """The change to the cell array.""" + + did_open: Optional[List[TextDocumentItem]] = attrs.field(default=None) + """Additional opened cell text documents.""" + + did_close: Optional[List[TextDocumentIdentifier]] = attrs.field(default=None) + """Additional closed cell text documents.""" + + +@attrs.define +class NotebookDocumentChangeEventCellsTypeTextContentType: + document: "VersionedTextDocumentIdentifier" = attrs.field() + + changes: List[TextDocumentContentChangeEvent] = attrs.field() + + +@attrs.define +class NotebookDocumentChangeEventCellsType: + structure: Optional[ + "NotebookDocumentChangeEventCellsTypeStructureType" + ] = attrs.field(default=None) + """Changes to the cell structure to add or + remove cells.""" + + data: Optional[List["NotebookCell"]] = attrs.field(default=None) + """Changes to notebook cells properties like its + kind, execution summary or metadata.""" + + text_content: Optional[ + List["NotebookDocumentChangeEventCellsTypeTextContentType"] + ] = attrs.field(default=None) + """Changes to the text content of notebook cells.""" + + +@attrs.define +class NotebookDocumentChangeEvent: + """A change event for a notebook document. + + @since 3.17.0""" + + # Since: 3.17.0 + + metadata: Optional[LSPObject] = attrs.field(default=None) + """The changed meta data if any. + + Note: should always be an object literal (e.g. LSPObject)""" + + cells: Optional["NotebookDocumentChangeEventCellsType"] = attrs.field(default=None) + """Changes to cells""" + + +@attrs.define +class NotebookDocumentIdentifier: + """A literal to identify a notebook document in the client. + + @since 3.17.0""" + + # Since: 3.17.0 + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The notebook document's uri.""" + + +@attrs.define +class InlineCompletionContext: + """Provides information about the context in which an inline completion was requested. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + trigger_kind: InlineCompletionTriggerKind = attrs.field() + """Describes how the inline completion was triggered.""" + + selected_completion_info: Optional["SelectedCompletionInfo"] = attrs.field( + default=None + ) + """Provides information about the currently selected item in the autocomplete widget if it is visible.""" + + +@attrs.define +class StringValue: + """A string value used as a snippet is a template which allows to insert text + and to control the editor cursor when insertion happens. + + A snippet can define tab stops and placeholders with `$1`, `$2` + and `${3:foo}`. `$0` defines the final tab stop, it defaults to + the end of the snippet. Variables are defined with `$name` and + `${name:default value}`. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + value: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The snippet string.""" + + kind: str = attrs.field( + validator=attrs.validators.in_(["snippet"]), default="snippet" + ) + """The kind of string value.""" + + +@attrs.define +class Registration: + """General parameters to register for a notification or to register a provider.""" + + id: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The id used to register the request. The id can be used to deregister + the request again.""" + + method: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The method / capability to register for.""" + + register_options: Optional[LSPAny] = attrs.field(default=None) + """Options necessary for the registration.""" + + +@attrs.define +class Unregistration: + """General parameters to unregister a request or notification.""" + + id: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The id used to unregister the request or notification. Usually an id + provided during the register request.""" + + method: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The method to unregister for.""" + + +@attrs.define +class ServerCapabilitiesWorkspaceType: + workspace_folders: Optional["WorkspaceFoldersServerCapabilities"] = attrs.field( + default=None + ) + """The server supports workspace folder. + + @since 3.6.0""" + # Since: 3.6.0 + + file_operations: Optional["FileOperationOptions"] = attrs.field(default=None) + """The server is interested in notifications/requests for operations on files. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class ServerCapabilities: + """Defines the capabilities provided by a language + server.""" + + position_encoding: Optional[Union[PositionEncodingKind, str]] = attrs.field( + default=None + ) + """The position encoding the server picked from the encodings offered + by the client via the client capability `general.positionEncodings`. + + If the client didn't provide any position encodings the only valid + value that a server can return is 'utf-16'. + + If omitted it defaults to 'utf-16'. + + @since 3.17.0""" + # Since: 3.17.0 + + text_document_sync: Optional[ + Union["TextDocumentSyncOptions", TextDocumentSyncKind] + ] = attrs.field(default=None) + """Defines how text documents are synced. Is either a detailed structure + defining each notification or for backwards compatibility the + TextDocumentSyncKind number.""" + + notebook_document_sync: Optional[ + Union["NotebookDocumentSyncOptions", "NotebookDocumentSyncRegistrationOptions"] + ] = attrs.field(default=None) + """Defines how notebook documents are synced. + + @since 3.17.0""" + # Since: 3.17.0 + + completion_provider: Optional[CompletionOptions] = attrs.field(default=None) + """The server provides completion support.""" + + hover_provider: Optional[Union[bool, HoverOptions]] = attrs.field(default=None) + """The server provides hover support.""" + + signature_help_provider: Optional[SignatureHelpOptions] = attrs.field(default=None) + """The server provides signature help support.""" + + declaration_provider: Optional[ + Union[bool, DeclarationOptions, DeclarationRegistrationOptions] + ] = attrs.field(default=None) + """The server provides Goto Declaration support.""" + + definition_provider: Optional[Union[bool, DefinitionOptions]] = attrs.field( + default=None + ) + """The server provides goto definition support.""" + + type_definition_provider: Optional[ + Union[bool, TypeDefinitionOptions, TypeDefinitionRegistrationOptions] + ] = attrs.field(default=None) + """The server provides Goto Type Definition support.""" + + implementation_provider: Optional[ + Union[bool, ImplementationOptions, ImplementationRegistrationOptions] + ] = attrs.field(default=None) + """The server provides Goto Implementation support.""" + + references_provider: Optional[Union[bool, ReferenceOptions]] = attrs.field( + default=None + ) + """The server provides find references support.""" + + document_highlight_provider: Optional[ + Union[bool, DocumentHighlightOptions] + ] = attrs.field(default=None) + """The server provides document highlight support.""" + + document_symbol_provider: Optional[ + Union[bool, DocumentSymbolOptions] + ] = attrs.field(default=None) + """The server provides document symbol support.""" + + code_action_provider: Optional[Union[bool, CodeActionOptions]] = attrs.field( + default=None + ) + """The server provides code actions. CodeActionOptions may only be + specified if the client states that it supports + `codeActionLiteralSupport` in its initial `initialize` request.""" + + code_lens_provider: Optional[CodeLensOptions] = attrs.field(default=None) + """The server provides code lens.""" + + document_link_provider: Optional[DocumentLinkOptions] = attrs.field(default=None) + """The server provides document link support.""" + + color_provider: Optional[ + Union[bool, DocumentColorOptions, DocumentColorRegistrationOptions] + ] = attrs.field(default=None) + """The server provides color provider support.""" + + workspace_symbol_provider: Optional[ + Union[bool, WorkspaceSymbolOptions] + ] = attrs.field(default=None) + """The server provides workspace symbol support.""" + + document_formatting_provider: Optional[ + Union[bool, DocumentFormattingOptions] + ] = attrs.field(default=None) + """The server provides document formatting.""" + + document_range_formatting_provider: Optional[ + Union[bool, DocumentRangeFormattingOptions] + ] = attrs.field(default=None) + """The server provides document range formatting.""" + + document_on_type_formatting_provider: Optional[ + DocumentOnTypeFormattingOptions + ] = attrs.field(default=None) + """The server provides document formatting on typing.""" + + rename_provider: Optional[Union[bool, RenameOptions]] = attrs.field(default=None) + """The server provides rename support. RenameOptions may only be + specified if the client states that it supports + `prepareSupport` in its initial `initialize` request.""" + + folding_range_provider: Optional[ + Union[bool, FoldingRangeOptions, FoldingRangeRegistrationOptions] + ] = attrs.field(default=None) + """The server provides folding provider support.""" + + selection_range_provider: Optional[ + Union[bool, SelectionRangeOptions, SelectionRangeRegistrationOptions] + ] = attrs.field(default=None) + """The server provides selection range support.""" + + execute_command_provider: Optional[ExecuteCommandOptions] = attrs.field( + default=None + ) + """The server provides execute command support.""" + + call_hierarchy_provider: Optional[ + Union[bool, CallHierarchyOptions, CallHierarchyRegistrationOptions] + ] = attrs.field(default=None) + """The server provides call hierarchy support. + + @since 3.16.0""" + # Since: 3.16.0 + + linked_editing_range_provider: Optional[ + Union[bool, LinkedEditingRangeOptions, LinkedEditingRangeRegistrationOptions] + ] = attrs.field(default=None) + """The server provides linked editing range support. + + @since 3.16.0""" + # Since: 3.16.0 + + semantic_tokens_provider: Optional[ + Union[SemanticTokensOptions, SemanticTokensRegistrationOptions] + ] = attrs.field(default=None) + """The server provides semantic tokens support. + + @since 3.16.0""" + # Since: 3.16.0 + + moniker_provider: Optional[ + Union[bool, MonikerOptions, MonikerRegistrationOptions] + ] = attrs.field(default=None) + """The server provides moniker support. + + @since 3.16.0""" + # Since: 3.16.0 + + type_hierarchy_provider: Optional[ + Union[bool, TypeHierarchyOptions, TypeHierarchyRegistrationOptions] + ] = attrs.field(default=None) + """The server provides type hierarchy support. + + @since 3.17.0""" + # Since: 3.17.0 + + inline_value_provider: Optional[ + Union[bool, InlineValueOptions, InlineValueRegistrationOptions] + ] = attrs.field(default=None) + """The server provides inline values. + + @since 3.17.0""" + # Since: 3.17.0 + + inlay_hint_provider: Optional[ + Union[bool, InlayHintOptions, InlayHintRegistrationOptions] + ] = attrs.field(default=None) + """The server provides inlay hints. + + @since 3.17.0""" + # Since: 3.17.0 + + diagnostic_provider: Optional[ + Union[DiagnosticOptions, DiagnosticRegistrationOptions] + ] = attrs.field(default=None) + """The server has support for pull model diagnostics. + + @since 3.17.0""" + # Since: 3.17.0 + + inline_completion_provider: Optional[ + Union[bool, InlineCompletionOptions] + ] = attrs.field(default=None) + """Inline completion options used during static registration. + + @since 3.18.0 + @proposed""" + # Since: 3.18.0 + # Proposed + + workspace: Optional["ServerCapabilitiesWorkspaceType"] = attrs.field(default=None) + """Workspace specific server capabilities.""" + + experimental: Optional[LSPAny] = attrs.field(default=None) + """Experimental server capabilities.""" + + +@attrs.define +class VersionedTextDocumentIdentifier: + """A text document identifier to denote a specific version of a text document.""" + + version: int = attrs.field(validator=validators.integer_validator) + """The version number of this document.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The text document's uri.""" + + +@attrs.define +class FileEvent: + """An event describing a file change.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The file's uri.""" + + type: FileChangeType = attrs.field() + """The change type.""" + + +@attrs.define +class FileSystemWatcher: + glob_pattern: GlobPattern = attrs.field() + """The glob pattern to watch. See {@link GlobPattern glob pattern} for more detail. + + @since 3.17.0 support for relative patterns.""" + # Since: 3.17.0 support for relative patterns. + + kind: Optional[Union[WatchKind, int]] = attrs.field(default=None) + """The kind of events of interest. If omitted it defaults + to WatchKind.Create | WatchKind.Change | WatchKind.Delete + which is 7.""" + + +@attrs.define +class Diagnostic: + """Represents a diagnostic, such as a compiler error or warning. Diagnostic objects + are only valid in the scope of a resource.""" + + range: Range = attrs.field() + """The range at which the message applies""" + + message: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The diagnostic's message. It usually appears in the user interface""" + + severity: Optional[DiagnosticSeverity] = attrs.field(default=None) + """The diagnostic's severity. Can be omitted. If omitted it is up to the + client to interpret diagnostics as error, warning, info or hint.""" + + code: Optional[Union[int, str]] = attrs.field(default=None) + """The diagnostic's code, which usually appear in the user interface.""" + + code_description: Optional["CodeDescription"] = attrs.field(default=None) + """An optional property to describe the error code. + Requires the code field (above) to be present/not null. + + @since 3.16.0""" + # Since: 3.16.0 + + source: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A human-readable string describing the source of this + diagnostic, e.g. 'typescript' or 'super lint'. It usually + appears in the user interface.""" + + tags: Optional[List[DiagnosticTag]] = attrs.field(default=None) + """Additional metadata about the diagnostic. + + @since 3.15.0""" + # Since: 3.15.0 + + related_information: Optional[List["DiagnosticRelatedInformation"]] = attrs.field( + default=None + ) + """An array of related diagnostic information, e.g. when symbol-names within + a scope collide all definitions can be marked via this property.""" + + data: Optional[LSPAny] = attrs.field(default=None) + """A data entry field that is preserved between a `textDocument/publishDiagnostics` + notification and `textDocument/codeAction` request. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class CompletionContext: + """Contains additional information about the context in which a completion request is triggered.""" + + trigger_kind: CompletionTriggerKind = attrs.field() + """How the completion was triggered.""" + + trigger_character: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The trigger character (a single character) that has trigger code complete. + Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`""" + + +@attrs.define +class CompletionItemLabelDetails: + """Additional details for a completion item label. + + @since 3.17.0""" + + # Since: 3.17.0 + + detail: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional string which is rendered less prominently directly after {@link CompletionItem.label label}, + without any spacing. Should be used for function signatures and type annotations.""" + + description: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional string which is rendered less prominently after {@link CompletionItem.detail}. Should be used + for fully qualified names and file paths.""" + + +@attrs.define +class InsertReplaceEdit: + """A special text edit to provide an insert and a replace operation. + + @since 3.16.0""" + + # Since: 3.16.0 + + new_text: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The string to be inserted.""" + + insert: Range = attrs.field() + """The range if the insert is requested""" + + replace: Range = attrs.field() + """The range if the replace is requested.""" + + +@attrs.define +class SignatureHelpContext: + """Additional information about the context in which a signature help request was triggered. + + @since 3.15.0""" + + # Since: 3.15.0 + + trigger_kind: SignatureHelpTriggerKind = attrs.field() + """Action that caused signature help to be triggered.""" + + is_retrigger: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + """`true` if signature help was already showing when it was triggered. + + Retriggers occurs when the signature help is already active and can be caused by actions such as + typing a trigger character, a cursor move, or document content changes.""" + + trigger_character: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """Character that caused signature help to be triggered. + + This is undefined when `triggerKind !== SignatureHelpTriggerKind.TriggerCharacter`""" + + active_signature_help: Optional[SignatureHelp] = attrs.field(default=None) + """The currently active `SignatureHelp`. + + The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field updated based on + the user navigating through available signatures.""" + + +@attrs.define +class SignatureInformation: + """Represents the signature of something callable. A signature + can have a label, like a function-name, a doc-comment, and + a set of parameters.""" + + label: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The label of this signature. Will be shown in + the UI.""" + + documentation: Optional[Union[str, MarkupContent]] = attrs.field(default=None) + """The human-readable doc-comment of this signature. Will be shown + in the UI but can be omitted.""" + + parameters: Optional[List["ParameterInformation"]] = attrs.field(default=None) + """The parameters of this signature.""" + + active_parameter: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """The index of the active parameter. + + If provided, this is used in place of `SignatureHelp.activeParameter`. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class ReferenceContext: + """Value-object that contains additional information when + requesting references.""" + + include_declaration: bool = attrs.field( + validator=attrs.validators.instance_of(bool) + ) + """Include the declaration of the current symbol.""" + + +@attrs.define +class CodeActionContext: + """Contains additional diagnostic information about the context in which + a {@link CodeActionProvider.provideCodeActions code action} is run.""" + + diagnostics: List[Diagnostic] = attrs.field() + """An array of diagnostics known on the client side overlapping the range provided to the + `textDocument/codeAction` request. They are provided so that the server knows which + errors are currently presented to the user for the given range. There is no guarantee + that these accurately reflect the error state of the resource. The primary parameter + to compute code actions is the provided range.""" + + only: Optional[List[Union[CodeActionKind, str]]] = attrs.field(default=None) + """Requested kind of actions to return. + + Actions not of this kind are filtered out by the client before being shown. So servers + can omit computing them.""" + + trigger_kind: Optional[CodeActionTriggerKind] = attrs.field(default=None) + """The reason why code actions were requested. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class FormattingOptions: + """Value-object describing what options formatting should use.""" + + tab_size: int = attrs.field(validator=validators.uinteger_validator) + """Size of a tab in spaces.""" + + insert_spaces: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + """Prefer spaces over tabs.""" + + trim_trailing_whitespace: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Trim trailing whitespace on a line. + + @since 3.15.0""" + # Since: 3.15.0 + + insert_final_newline: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Insert a newline character at the end of the file if one does not exist. + + @since 3.15.0""" + # Since: 3.15.0 + + trim_final_newlines: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Trim all newlines after the final newline at the end of the file. + + @since 3.15.0""" + # Since: 3.15.0 + + +@attrs.define +class SemanticTokensLegend: + """@since 3.16.0""" + + # Since: 3.16.0 + + token_types: List[str] = attrs.field() + """The token types a server uses.""" + + token_modifiers: List[str] = attrs.field() + """The token modifiers a server uses.""" + + +@attrs.define +class OptionalVersionedTextDocumentIdentifier: + """A text document identifier to optionally denote a specific version of a text document.""" + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The text document's uri.""" + + version: Optional[Union[int, None]] = attrs.field(default=None) + """The version number of this document. If a versioned text document identifier + is sent from the server to the client and the file is not open in the editor + (the server has not received an open notification before) the server can send + `null` to indicate that the version is unknown and the content on disk is the + truth (as specified with document content ownership).""" + + +@attrs.define +class AnnotatedTextEdit: + """A special text edit with an additional change annotation. + + @since 3.16.0.""" + + # Since: 3.16.0. + + annotation_id: ChangeAnnotationIdentifier = attrs.field() + """The actual identifier of the change annotation""" + + range: Range = attrs.field() + """The range of the text document to be manipulated. To insert + text into a document create a range where start === end.""" + + new_text: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The string to be inserted. For delete operations use an + empty string.""" + + +@attrs.define +class CreateFileOptions: + """Options to create a file.""" + + overwrite: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Overwrite existing file. Overwrite wins over `ignoreIfExists`""" + + ignore_if_exists: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Ignore if exists.""" + + +@attrs.define +class RenameFileOptions: + """Rename file options""" + + overwrite: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Overwrite target if existing. Overwrite wins over `ignoreIfExists`""" + + ignore_if_exists: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Ignores if target exists.""" + + +@attrs.define +class DeleteFileOptions: + """Delete file options""" + + recursive: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Delete the content recursively if a folder is denoted.""" + + ignore_if_not_exists: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Ignore the operation if the file doesn't exist.""" + + +@attrs.define +class FileOperationPattern: + """A pattern to describe in which file operation requests or notifications + the server is interested in receiving. + + @since 3.16.0""" + + # Since: 3.16.0 + + glob: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The glob pattern to match. Glob patterns can have the following syntax: + - `*` to match one or more characters in a path segment + - `?` to match on one character in a path segment + - `**` to match any number of path segments, including none + - `{}` to group sub patterns into an OR expression. (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files) + - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) + - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)""" + + matches: Optional[FileOperationPatternKind] = attrs.field(default=None) + """Whether to match files or folders with this pattern. + + Matches both if undefined.""" + + options: Optional["FileOperationPatternOptions"] = attrs.field(default=None) + """Additional options used during matching.""" + + +@attrs.define +class WorkspaceFullDocumentDiagnosticReport: + """A full document diagnostic report for a workspace diagnostic result. + + @since 3.17.0""" + + # Since: 3.17.0 + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The URI for which diagnostic information is reported.""" + + items: List[Diagnostic] = attrs.field() + """The actual items.""" + + version: Optional[Union[int, None]] = attrs.field(default=None) + """The version number for which the diagnostics are reported. + If the document is not marked as open `null` can be provided.""" + + kind: str = attrs.field(validator=attrs.validators.in_(["full"]), default="full") + """A full document diagnostic report.""" + + result_id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """An optional result id. If provided it will + be sent on the next diagnostic request for the + same document.""" + + +@attrs.define +class WorkspaceUnchangedDocumentDiagnosticReport: + """An unchanged document diagnostic report for a workspace diagnostic result. + + @since 3.17.0""" + + # Since: 3.17.0 + + uri: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The URI for which diagnostic information is reported.""" + + result_id: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A result id which will be sent on the next + diagnostic request for the same document.""" + + version: Optional[Union[int, None]] = attrs.field(default=None) + """The version number for which the diagnostics are reported. + If the document is not marked as open `null` can be provided.""" + + kind: str = attrs.field( + validator=attrs.validators.in_(["unchanged"]), default="unchanged" + ) + """A document diagnostic report indicating + no changes to the last result. A server can + only return `unchanged` if result ids are + provided.""" + + +@attrs.define +class NotebookCell: + """A notebook cell. + + A cell's document URI must be unique across ALL notebook + cells and can therefore be used to uniquely identify a + notebook cell or the cell's text document. + + @since 3.17.0""" + + # Since: 3.17.0 + + kind: NotebookCellKind = attrs.field() + """The cell's kind""" + + document: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The URI of the cell's text document + content.""" + + metadata: Optional[LSPObject] = attrs.field(default=None) + """Additional metadata stored with the cell. + + Note: should always be an object literal (e.g. LSPObject)""" + + execution_summary: Optional["ExecutionSummary"] = attrs.field(default=None) + """Additional execution summary information + if supported by the client.""" + + +@attrs.define +class NotebookCellArrayChange: + """A change describing how to move a `NotebookCell` + array from state S to S'. + + @since 3.17.0""" + + # Since: 3.17.0 + + start: int = attrs.field(validator=validators.uinteger_validator) + """The start oftest of the cell that changed.""" + + delete_count: int = attrs.field(validator=validators.uinteger_validator) + """The deleted cells""" + + cells: Optional[List[NotebookCell]] = attrs.field(default=None) + """The new cells, if any""" + + +@attrs.define +class SelectedCompletionInfo: + """Describes the currently selected completion item. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + range: Range = attrs.field() + """The range that will be replaced if this completion item is accepted.""" + + text: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The text the range will be replaced with if this completion is accepted.""" + + +@attrs.define +class ClientCapabilities: + """Defines the capabilities provided by the client.""" + + workspace: Optional["WorkspaceClientCapabilities"] = attrs.field(default=None) + """Workspace specific client capabilities.""" + + text_document: Optional["TextDocumentClientCapabilities"] = attrs.field( + default=None + ) + """Text document specific client capabilities.""" + + notebook_document: Optional["NotebookDocumentClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the notebook document support. + + @since 3.17.0""" + # Since: 3.17.0 + + window: Optional["WindowClientCapabilities"] = attrs.field(default=None) + """Window specific client capabilities.""" + + general: Optional["GeneralClientCapabilities"] = attrs.field(default=None) + """General client capabilities. + + @since 3.16.0""" + # Since: 3.16.0 + + experimental: Optional[LSPAny] = attrs.field(default=None) + """Experimental client capabilities.""" + + +@attrs.define +class TextDocumentSyncOptions: + open_close: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Open and close notifications are sent to the server. If omitted open close notification should not + be sent.""" + + change: Optional[TextDocumentSyncKind] = attrs.field(default=None) + """Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full + and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None.""" + + will_save: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """If present will save notifications are sent to the server. If omitted the notification should not be + sent.""" + + will_save_wait_until: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """If present will save wait until requests are sent to the server. If omitted the request should not be + sent.""" + + save: Optional[Union[bool, SaveOptions]] = attrs.field(default=None) + """If present save notifications are sent to the server. If omitted the notification should not be + sent.""" + + +@attrs.define +class NotebookDocumentSyncOptionsNotebookSelectorType1CellsType: + language: str = attrs.field(validator=attrs.validators.instance_of(str)) + + +@attrs.define +class NotebookDocumentSyncOptionsNotebookSelectorType1: + notebook: Union[str, NotebookDocumentFilter] = attrs.field() + """The notebook to be synced If a string + value is provided it matches against the + notebook type. '*' matches every notebook.""" + + cells: Optional[ + List["NotebookDocumentSyncOptionsNotebookSelectorType1CellsType"] + ] = attrs.field(default=None) + """The cells of the matching notebook to be synced.""" + + +@attrs.define +class NotebookDocumentSyncOptionsNotebookSelectorType2CellsType: + language: str = attrs.field(validator=attrs.validators.instance_of(str)) + + +@attrs.define +class NotebookDocumentSyncOptionsNotebookSelectorType2: + cells: List[ + "NotebookDocumentSyncOptionsNotebookSelectorType2CellsType" + ] = attrs.field() + """The cells of the matching notebook to be synced.""" + + notebook: Optional[Union[str, NotebookDocumentFilter]] = attrs.field(default=None) + """The notebook to be synced If a string + value is provided it matches against the + notebook type. '*' matches every notebook.""" + + +@attrs.define +class NotebookDocumentSyncOptions: + """Options specific to a notebook plus its cells + to be synced to the server. + + If a selector provides a notebook document + filter but no cell selector all cells of a + matching notebook document will be synced. + + If a selector provides no notebook document + filter but only a cell selector all notebook + document that contain at least one matching + cell will be synced. + + @since 3.17.0""" + + # Since: 3.17.0 + + notebook_selector: List[ + Union[ + "NotebookDocumentSyncOptionsNotebookSelectorType1", + "NotebookDocumentSyncOptionsNotebookSelectorType2", + ] + ] = attrs.field() + """The notebooks to be synced""" + + save: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether save notification should be forwarded to + the server. Will only be honored if mode === `notebook`.""" + + +@attrs.define +class NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1CellsType: + language: str = attrs.field(validator=attrs.validators.instance_of(str)) + + +@attrs.define +class NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1: + notebook: Union[str, NotebookDocumentFilter] = attrs.field() + """The notebook to be synced If a string + value is provided it matches against the + notebook type. '*' matches every notebook.""" + + cells: Optional[ + List["NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1CellsType"] + ] = attrs.field(default=None) + """The cells of the matching notebook to be synced.""" + + +@attrs.define +class NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2CellsType: + language: str = attrs.field(validator=attrs.validators.instance_of(str)) + + +@attrs.define +class NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2: + cells: List[ + "NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2CellsType" + ] = attrs.field() + """The cells of the matching notebook to be synced.""" + + notebook: Optional[Union[str, NotebookDocumentFilter]] = attrs.field(default=None) + """The notebook to be synced If a string + value is provided it matches against the + notebook type. '*' matches every notebook.""" + + +@attrs.define +class NotebookDocumentSyncRegistrationOptions: + """Registration options specific to a notebook. + + @since 3.17.0""" + + # Since: 3.17.0 + + notebook_selector: List[ + Union[ + "NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1", + "NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2", + ] + ] = attrs.field() + """The notebooks to be synced""" + + save: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether save notification should be forwarded to + the server. Will only be honored if mode === `notebook`.""" + + id: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The id used to register the request. The id can be used to deregister + the request again. See also Registration#id.""" + + +@attrs.define +class WorkspaceFoldersServerCapabilities: + supported: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The server has support for workspace folders""" + + change_notifications: Optional[Union[str, bool]] = attrs.field(default=None) + """Whether the server wants to receive workspace folder + change notifications. + + If a string is provided the string is treated as an ID + under which the notification is registered on the client + side. The ID can be used to unregister for these events + using the `client/unregisterCapability` request.""" + + +@attrs.define +class FileOperationOptions: + """Options for notifications/requests for user operations on files. + + @since 3.16.0""" + + # Since: 3.16.0 + + did_create: Optional[FileOperationRegistrationOptions] = attrs.field(default=None) + """The server is interested in receiving didCreateFiles notifications.""" + + will_create: Optional[FileOperationRegistrationOptions] = attrs.field(default=None) + """The server is interested in receiving willCreateFiles requests.""" + + did_rename: Optional[FileOperationRegistrationOptions] = attrs.field(default=None) + """The server is interested in receiving didRenameFiles notifications.""" + + will_rename: Optional[FileOperationRegistrationOptions] = attrs.field(default=None) + """The server is interested in receiving willRenameFiles requests.""" + + did_delete: Optional[FileOperationRegistrationOptions] = attrs.field(default=None) + """The server is interested in receiving didDeleteFiles file notifications.""" + + will_delete: Optional[FileOperationRegistrationOptions] = attrs.field(default=None) + """The server is interested in receiving willDeleteFiles file requests.""" + + +@attrs.define +class CodeDescription: + """Structure to capture a description for an error code. + + @since 3.16.0""" + + # Since: 3.16.0 + + href: str = attrs.field(validator=attrs.validators.instance_of(str)) + """An URI to open with more information about the diagnostic error.""" + + +@attrs.define +class DiagnosticRelatedInformation: + """Represents a related message and source code location for a diagnostic. This should be + used to point to code locations that cause or related to a diagnostics, e.g when duplicating + a symbol in a scope.""" + + location: Location = attrs.field() + """The location of this related diagnostic information.""" + + message: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The message of this related diagnostic information.""" + + +@attrs.define +class ParameterInformation: + """Represents a parameter of a callable-signature. A parameter can + have a label and a doc-comment.""" + + label: Union[str, Tuple[int, int]] = attrs.field() + """The label of this parameter information. + + Either a string or an inclusive start and exclusive end offsets within its containing + signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 + string representation as `Position` and `Range` does. + + *Note*: a label of type string should be a substring of its containing signature label. + Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.""" + + documentation: Optional[Union[str, MarkupContent]] = attrs.field(default=None) + """The human-readable doc-comment of this parameter. Will be shown + in the UI but can be omitted.""" + + +@attrs.define +class NotebookCellTextDocumentFilter: + """A notebook cell text document filter denotes a cell text + document by different properties. + + @since 3.17.0""" + + # Since: 3.17.0 + + notebook: Union[str, NotebookDocumentFilter] = attrs.field() + """A filter that matches against the notebook + containing the notebook cell. If a string + value is provided it matches against the + notebook type. '*' matches every notebook.""" + + language: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """A language id like `python`. + + Will be matched against the language id of the + notebook cell document. '*' matches every language.""" + + +@attrs.define +class FileOperationPatternOptions: + """Matching options for the file operation pattern. + + @since 3.16.0""" + + # Since: 3.16.0 + + ignore_case: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The pattern should be matched ignoring casing.""" + + +@attrs.define +class ExecutionSummary: + execution_order: int = attrs.field(validator=validators.uinteger_validator) + """A strict monotonically increasing value + indicating the execution order of a cell + inside a notebook.""" + + success: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the execution was successful or + not if known by the client.""" + + +@attrs.define +class WorkspaceClientCapabilities: + """Workspace specific client capabilities.""" + + apply_edit: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports applying batch edits + to the workspace by supporting the request + 'workspace/applyEdit'""" + + workspace_edit: Optional["WorkspaceEditClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to `WorkspaceEdit`s.""" + + did_change_configuration: Optional[ + "DidChangeConfigurationClientCapabilities" + ] = attrs.field(default=None) + """Capabilities specific to the `workspace/didChangeConfiguration` notification.""" + + did_change_watched_files: Optional[ + "DidChangeWatchedFilesClientCapabilities" + ] = attrs.field(default=None) + """Capabilities specific to the `workspace/didChangeWatchedFiles` notification.""" + + symbol: Optional["WorkspaceSymbolClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `workspace/symbol` request.""" + + execute_command: Optional["ExecuteCommandClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `workspace/executeCommand` request.""" + + workspace_folders: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client has support for workspace folders. + + @since 3.6.0""" + # Since: 3.6.0 + + configuration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports `workspace/configuration` requests. + + @since 3.6.0""" + # Since: 3.6.0 + + semantic_tokens: Optional[ + "SemanticTokensWorkspaceClientCapabilities" + ] = attrs.field(default=None) + """Capabilities specific to the semantic token requests scoped to the + workspace. + + @since 3.16.0.""" + # Since: 3.16.0. + + code_lens: Optional["CodeLensWorkspaceClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the code lens requests scoped to the + workspace. + + @since 3.16.0.""" + # Since: 3.16.0. + + file_operations: Optional["FileOperationClientCapabilities"] = attrs.field( + default=None + ) + """The client has support for file notifications/requests for user operations on files. + + Since 3.16.0""" + + inline_value: Optional["InlineValueWorkspaceClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the inline values requests scoped to the + workspace. + + @since 3.17.0.""" + # Since: 3.17.0. + + inlay_hint: Optional["InlayHintWorkspaceClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the inlay hint requests scoped to the + workspace. + + @since 3.17.0.""" + # Since: 3.17.0. + + diagnostics: Optional["DiagnosticWorkspaceClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the diagnostic requests scoped to the + workspace. + + @since 3.17.0.""" + # Since: 3.17.0. + + folding_range: Optional["FoldingRangeWorkspaceClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the folding range requests scoped to the workspace. + + @since 3.18.0 + @proposed""" + # Since: 3.18.0 + # Proposed + + +@attrs.define +class TextDocumentClientCapabilities: + """Text document specific client capabilities.""" + + synchronization: Optional["TextDocumentSyncClientCapabilities"] = attrs.field( + default=None + ) + """Defines which synchronization capabilities the client supports.""" + + completion: Optional["CompletionClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/completion` request.""" + + hover: Optional["HoverClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/hover` request.""" + + signature_help: Optional["SignatureHelpClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/signatureHelp` request.""" + + declaration: Optional["DeclarationClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/declaration` request. + + @since 3.14.0""" + # Since: 3.14.0 + + definition: Optional["DefinitionClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/definition` request.""" + + type_definition: Optional["TypeDefinitionClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/typeDefinition` request. + + @since 3.6.0""" + # Since: 3.6.0 + + implementation: Optional["ImplementationClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/implementation` request. + + @since 3.6.0""" + # Since: 3.6.0 + + references: Optional["ReferenceClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/references` request.""" + + document_highlight: Optional["DocumentHighlightClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/documentHighlight` request.""" + + document_symbol: Optional["DocumentSymbolClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/documentSymbol` request.""" + + code_action: Optional["CodeActionClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/codeAction` request.""" + + code_lens: Optional["CodeLensClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/codeLens` request.""" + + document_link: Optional["DocumentLinkClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/documentLink` request.""" + + color_provider: Optional["DocumentColorClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/documentColor` and the + `textDocument/colorPresentation` request. + + @since 3.6.0""" + # Since: 3.6.0 + + formatting: Optional["DocumentFormattingClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/formatting` request.""" + + range_formatting: Optional[ + "DocumentRangeFormattingClientCapabilities" + ] = attrs.field(default=None) + """Capabilities specific to the `textDocument/rangeFormatting` request.""" + + on_type_formatting: Optional[ + "DocumentOnTypeFormattingClientCapabilities" + ] = attrs.field(default=None) + """Capabilities specific to the `textDocument/onTypeFormatting` request.""" + + rename: Optional["RenameClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/rename` request.""" + + folding_range: Optional["FoldingRangeClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/foldingRange` request. + + @since 3.10.0""" + # Since: 3.10.0 + + selection_range: Optional["SelectionRangeClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/selectionRange` request. + + @since 3.15.0""" + # Since: 3.15.0 + + publish_diagnostics: Optional["PublishDiagnosticsClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the `textDocument/publishDiagnostics` notification.""" + + call_hierarchy: Optional["CallHierarchyClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the various call hierarchy requests. + + @since 3.16.0""" + # Since: 3.16.0 + + semantic_tokens: Optional["SemanticTokensClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the various semantic token request. + + @since 3.16.0""" + # Since: 3.16.0 + + linked_editing_range: Optional[ + "LinkedEditingRangeClientCapabilities" + ] = attrs.field(default=None) + """Capabilities specific to the `textDocument/linkedEditingRange` request. + + @since 3.16.0""" + # Since: 3.16.0 + + moniker: Optional["MonikerClientCapabilities"] = attrs.field(default=None) + """Client capabilities specific to the `textDocument/moniker` request. + + @since 3.16.0""" + # Since: 3.16.0 + + type_hierarchy: Optional["TypeHierarchyClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the various type hierarchy requests. + + @since 3.17.0""" + # Since: 3.17.0 + + inline_value: Optional["InlineValueClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/inlineValue` request. + + @since 3.17.0""" + # Since: 3.17.0 + + inlay_hint: Optional["InlayHintClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the `textDocument/inlayHint` request. + + @since 3.17.0""" + # Since: 3.17.0 + + diagnostic: Optional["DiagnosticClientCapabilities"] = attrs.field(default=None) + """Capabilities specific to the diagnostic pull model. + + @since 3.17.0""" + # Since: 3.17.0 + + inline_completion: Optional["InlineCompletionClientCapabilities"] = attrs.field( + default=None + ) + """Client capabilities specific to inline completions. + + @since 3.18.0 + @proposed""" + # Since: 3.18.0 + # Proposed + + +@attrs.define +class NotebookDocumentClientCapabilities: + """Capabilities specific to the notebook document support. + + @since 3.17.0""" + + # Since: 3.17.0 + + synchronization: "NotebookDocumentSyncClientCapabilities" = attrs.field() + """Capabilities specific to notebook document synchronization + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class WindowClientCapabilities: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """It indicates whether the client supports server initiated + progress using the `window/workDoneProgress/create` request. + + The capability also controls Whether client supports handling + of progress notifications. If set servers are allowed to report a + `workDoneProgress` property in the request specific server + capabilities. + + @since 3.15.0""" + # Since: 3.15.0 + + show_message: Optional["ShowMessageRequestClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the showMessage request. + + @since 3.16.0""" + # Since: 3.16.0 + + show_document: Optional["ShowDocumentClientCapabilities"] = attrs.field( + default=None + ) + """Capabilities specific to the showDocument request. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class GeneralClientCapabilitiesStaleRequestSupportType: + cancel: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + """The client will actively cancel the request.""" + + retry_on_content_modified: List[str] = attrs.field() + """The list of requests for which the client + will retry the request if it receives a + response with error code `ContentModified`""" + + +@attrs.define +class GeneralClientCapabilities: + """General client capabilities. + + @since 3.16.0""" + + # Since: 3.16.0 + + stale_request_support: Optional[ + "GeneralClientCapabilitiesStaleRequestSupportType" + ] = attrs.field(default=None) + """Client capability that signals how the client + handles stale requests (e.g. a request + for which the client will not process the response + anymore since the information is outdated). + + @since 3.17.0""" + # Since: 3.17.0 + + regular_expressions: Optional["RegularExpressionsClientCapabilities"] = attrs.field( + default=None + ) + """Client capabilities specific to regular expressions. + + @since 3.16.0""" + # Since: 3.16.0 + + markdown: Optional["MarkdownClientCapabilities"] = attrs.field(default=None) + """Client capabilities specific to the client's markdown parser. + + @since 3.16.0""" + # Since: 3.16.0 + + position_encodings: Optional[List[Union[PositionEncodingKind, str]]] = attrs.field( + default=None + ) + """The position encodings supported by the client. Client and server + have to agree on the same position encoding to ensure that offsets + (e.g. character position in a line) are interpreted the same on both + sides. + + To keep the protocol backwards compatible the following applies: if + the value 'utf-16' is missing from the array of position encodings + servers can assume that the client supports UTF-16. UTF-16 is + therefore a mandatory encoding. + + If omitted it defaults to ['utf-16']. + + Implementation considerations: since the conversion from one encoding + into another requires the content of the file / line the conversion + is best done where the file is read which is usually on the server + side. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class RelativePattern: + """A relative pattern is a helper to construct glob patterns that are matched + relatively to a base URI. The common value for a `baseUri` is a workspace + folder root, but it can be another absolute URI as well. + + @since 3.17.0""" + + # Since: 3.17.0 + + base_uri: Union[WorkspaceFolder, str] = attrs.field() + """A workspace folder or a base URI to which this pattern will be matched + against relatively.""" + + pattern: Pattern = attrs.field() + """The actual glob pattern;""" + + +@attrs.define +class WorkspaceEditClientCapabilitiesChangeAnnotationSupportType: + groups_on_label: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client groups edits with equal labels into tree nodes, + for instance all edits labelled with "Changes in Strings" would + be a tree node.""" + + +@attrs.define +class WorkspaceEditClientCapabilities: + document_changes: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports versioned document changes in `WorkspaceEdit`s""" + + resource_operations: Optional[List[ResourceOperationKind]] = attrs.field( + default=None + ) + """The resource operations the client supports. Clients should at least + support 'create', 'rename' and 'delete' files and folders. + + @since 3.13.0""" + # Since: 3.13.0 + + failure_handling: Optional[FailureHandlingKind] = attrs.field(default=None) + """The failure handling strategy of a client if applying the workspace edit + fails. + + @since 3.13.0""" + # Since: 3.13.0 + + normalizes_line_endings: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client normalizes line endings to the client specific + setting. + If set to `true` the client will normalize line ending characters + in a workspace edit to the client-specified new line + character. + + @since 3.16.0""" + # Since: 3.16.0 + + change_annotation_support: Optional[ + "WorkspaceEditClientCapabilitiesChangeAnnotationSupportType" + ] = attrs.field(default=None) + """Whether the client in general supports change annotations on text edits, + create file, rename file and delete file changes. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class DidChangeConfigurationClientCapabilities: + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Did change configuration notification supports dynamic registration.""" + + +@attrs.define +class DidChangeWatchedFilesClientCapabilities: + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Did change watched files notification supports dynamic registration. Please note + that the current protocol doesn't support static configuration for file changes + from the server side.""" + + relative_pattern_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client has support for {@link RelativePattern relative pattern} + or not. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class WorkspaceSymbolClientCapabilitiesSymbolKindType: + value_set: Optional[List[SymbolKind]] = attrs.field(default=None) + """The symbol kind values the client supports. When this + property exists the client also guarantees that it will + handle values outside its set gracefully and falls back + to a default value when unknown. + + If this property is not present the client only supports + the symbol kinds from `File` to `Array` as defined in + the initial version of the protocol.""" + + +@attrs.define +class WorkspaceSymbolClientCapabilitiesTagSupportType: + value_set: List[SymbolTag] = attrs.field() + """The tags supported by the client.""" + + +@attrs.define +class WorkspaceSymbolClientCapabilitiesResolveSupportType: + properties: List[str] = attrs.field() + """The properties that a client can resolve lazily. Usually + `location.range`""" + + +@attrs.define +class WorkspaceSymbolClientCapabilities: + """Client capabilities for a {@link WorkspaceSymbolRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Symbol request supports dynamic registration.""" + + symbol_kind: Optional[ + "WorkspaceSymbolClientCapabilitiesSymbolKindType" + ] = attrs.field(default=None) + """Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.""" + + tag_support: Optional[ + "WorkspaceSymbolClientCapabilitiesTagSupportType" + ] = attrs.field(default=None) + """The client supports tags on `SymbolInformation`. + Clients supporting tags have to handle unknown tags gracefully. + + @since 3.16.0""" + # Since: 3.16.0 + + resolve_support: Optional[ + "WorkspaceSymbolClientCapabilitiesResolveSupportType" + ] = attrs.field(default=None) + """The client support partial workspace symbols. The client will send the + request `workspaceSymbol/resolve` to the server to resolve additional + properties. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class ExecuteCommandClientCapabilities: + """The client capabilities of a {@link ExecuteCommandRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Execute command supports dynamic registration.""" + + +@attrs.define +class SemanticTokensWorkspaceClientCapabilities: + """@since 3.16.0""" + + # Since: 3.16.0 + + refresh_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client implementation supports a refresh request sent from + the server to the client. + + Note that this event is global and will force the client to refresh all + semantic tokens currently shown. It should be used with absolute care + and is useful for situation where a server for example detects a project + wide change that requires such a calculation.""" + + +@attrs.define +class CodeLensWorkspaceClientCapabilities: + """@since 3.16.0""" + + # Since: 3.16.0 + + refresh_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client implementation supports a refresh request sent from the + server to the client. + + Note that this event is global and will force the client to refresh all + code lenses currently shown. It should be used with absolute care and is + useful for situation where a server for example detect a project wide + change that requires such a calculation.""" + + +@attrs.define +class FileOperationClientCapabilities: + """Capabilities relating to events from file operations by the user in the client. + + These events do not come from the file system, they come from user operations + like renaming a file in the UI. + + @since 3.16.0""" + + # Since: 3.16.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client supports dynamic registration for file requests/notifications.""" + + did_create: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client has support for sending didCreateFiles notifications.""" + + will_create: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client has support for sending willCreateFiles requests.""" + + did_rename: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client has support for sending didRenameFiles notifications.""" + + will_rename: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client has support for sending willRenameFiles requests.""" + + did_delete: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client has support for sending didDeleteFiles notifications.""" + + will_delete: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client has support for sending willDeleteFiles requests.""" + + +@attrs.define +class InlineValueWorkspaceClientCapabilities: + """Client workspace capabilities specific to inline values. + + @since 3.17.0""" + + # Since: 3.17.0 + + refresh_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client implementation supports a refresh request sent from the + server to the client. + + Note that this event is global and will force the client to refresh all + inline values currently shown. It should be used with absolute care and is + useful for situation where a server for example detects a project wide + change that requires such a calculation.""" + + +@attrs.define +class InlayHintWorkspaceClientCapabilities: + """Client workspace capabilities specific to inlay hints. + + @since 3.17.0""" + + # Since: 3.17.0 + + refresh_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client implementation supports a refresh request sent from + the server to the client. + + Note that this event is global and will force the client to refresh all + inlay hints currently shown. It should be used with absolute care and + is useful for situation where a server for example detects a project wide + change that requires such a calculation.""" + + +@attrs.define +class DiagnosticWorkspaceClientCapabilities: + """Workspace client capabilities specific to diagnostic pull requests. + + @since 3.17.0""" + + # Since: 3.17.0 + + refresh_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client implementation supports a refresh request sent from + the server to the client. + + Note that this event is global and will force the client to refresh all + pulled diagnostics currently shown. It should be used with absolute care and + is useful for situation where a server for example detects a project wide + change that requires such a calculation.""" + + +@attrs.define +class FoldingRangeWorkspaceClientCapabilities: + """Client workspace capabilities specific to folding ranges + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + refresh_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client implementation supports a refresh request sent from the + server to the client. + + Note that this event is global and will force the client to refresh all + folding ranges currently shown. It should be used with absolute care and is + useful for situation where a server for example detects a project wide + change that requires such a calculation. + + @since 3.18.0 + @proposed""" + # Since: 3.18.0 + # Proposed + + +@attrs.define +class TextDocumentSyncClientCapabilities: + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether text document synchronization supports dynamic registration.""" + + will_save: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports sending will save notifications.""" + + will_save_wait_until: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports sending a will save request and + waits for a response providing text edits which will + be applied to the document before it is saved.""" + + did_save: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports did save notifications.""" + + +@attrs.define +class CompletionClientCapabilitiesCompletionItemTypeTagSupportType: + value_set: List[CompletionItemTag] = attrs.field() + """The tags supported by the client.""" + + +@attrs.define +class CompletionClientCapabilitiesCompletionItemTypeResolveSupportType: + properties: List[str] = attrs.field() + """The properties that a client can resolve lazily.""" + + +@attrs.define +class CompletionClientCapabilitiesCompletionItemTypeInsertTextModeSupportType: + value_set: List[InsertTextMode] = attrs.field() + + +@attrs.define +class CompletionClientCapabilitiesCompletionItemType: + snippet_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Client supports snippets as insert text. + + A snippet can define tab stops and placeholders with `$1`, `$2` + and `${3:foo}`. `$0` defines the final tab stop, it defaults to + the end of the snippet. Placeholders with equal identifiers are linked, + that is typing in one will update others too.""" + + commit_characters_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Client supports commit characters on a completion item.""" + + documentation_format: Optional[List[MarkupKind]] = attrs.field(default=None) + """Client supports the following content formats for the documentation + property. The order describes the preferred format of the client.""" + + deprecated_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Client supports the deprecated property on a completion item.""" + + preselect_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Client supports the preselect property on a completion item.""" + + tag_support: Optional[ + "CompletionClientCapabilitiesCompletionItemTypeTagSupportType" + ] = attrs.field(default=None) + """Client supports the tag property on a completion item. Clients supporting + tags have to handle unknown tags gracefully. Clients especially need to + preserve unknown tags when sending a completion item back to the server in + a resolve call. + + @since 3.15.0""" + # Since: 3.15.0 + + insert_replace_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Client support insert replace edit to control different behavior if a + completion item is inserted in the text or should replace text. + + @since 3.16.0""" + # Since: 3.16.0 + + resolve_support: Optional[ + "CompletionClientCapabilitiesCompletionItemTypeResolveSupportType" + ] = attrs.field(default=None) + """Indicates which properties a client can resolve lazily on a completion + item. Before version 3.16.0 only the predefined properties `documentation` + and `details` could be resolved lazily. + + @since 3.16.0""" + # Since: 3.16.0 + + insert_text_mode_support: Optional[ + "CompletionClientCapabilitiesCompletionItemTypeInsertTextModeSupportType" + ] = attrs.field(default=None) + """The client supports the `insertTextMode` property on + a completion item to override the whitespace handling mode + as defined by the client (see `insertTextMode`). + + @since 3.16.0""" + # Since: 3.16.0 + + label_details_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client has support for completion item label + details (see also `CompletionItemLabelDetails`). + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class CompletionClientCapabilitiesCompletionItemKindType: + value_set: Optional[List[CompletionItemKind]] = attrs.field(default=None) + """The completion item kind values the client supports. When this + property exists the client also guarantees that it will + handle values outside its set gracefully and falls back + to a default value when unknown. + + If this property is not present the client only supports + the completion items kinds from `Text` to `Reference` as defined in + the initial version of the protocol.""" + + +@attrs.define +class CompletionClientCapabilitiesCompletionListType: + item_defaults: Optional[List[str]] = attrs.field(default=None) + """The client supports the following itemDefaults on + a completion list. + + The value lists the supported property names of the + `CompletionList.itemDefaults` object. If omitted + no properties are supported. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class CompletionClientCapabilities: + """Completion client capabilities""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether completion supports dynamic registration.""" + + completion_item: Optional[ + "CompletionClientCapabilitiesCompletionItemType" + ] = attrs.field(default=None) + """The client supports the following `CompletionItem` specific + capabilities.""" + + completion_item_kind: Optional[ + "CompletionClientCapabilitiesCompletionItemKindType" + ] = attrs.field(default=None) + + insert_text_mode: Optional[InsertTextMode] = attrs.field(default=None) + """Defines how the client handles whitespace and indentation + when accepting a completion item that uses multi line + text in either `insertText` or `textEdit`. + + @since 3.17.0""" + # Since: 3.17.0 + + context_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports to send additional context information for a + `textDocument/completion` request.""" + + completion_list: Optional[ + "CompletionClientCapabilitiesCompletionListType" + ] = attrs.field(default=None) + """The client supports the following `CompletionList` specific + capabilities. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class HoverClientCapabilities: + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether hover supports dynamic registration.""" + + content_format: Optional[List[MarkupKind]] = attrs.field(default=None) + """Client supports the following content formats for the content + property. The order describes the preferred format of the client.""" + + +@attrs.define +class SignatureHelpClientCapabilitiesSignatureInformationTypeParameterInformationType: + label_offset_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports processing label offsets instead of a + simple label string. + + @since 3.14.0""" + # Since: 3.14.0 + + +@attrs.define +class SignatureHelpClientCapabilitiesSignatureInformationType: + documentation_format: Optional[List[MarkupKind]] = attrs.field(default=None) + """Client supports the following content formats for the documentation + property. The order describes the preferred format of the client.""" + + parameter_information: Optional[ + "SignatureHelpClientCapabilitiesSignatureInformationTypeParameterInformationType" + ] = attrs.field(default=None) + """Client capabilities specific to parameter information.""" + + active_parameter_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports the `activeParameter` property on `SignatureInformation` + literal. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class SignatureHelpClientCapabilities: + """Client Capabilities for a {@link SignatureHelpRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether signature help supports dynamic registration.""" + + signature_information: Optional[ + "SignatureHelpClientCapabilitiesSignatureInformationType" + ] = attrs.field(default=None) + """The client supports the following `SignatureInformation` + specific properties.""" + + context_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports to send additional context information for a + `textDocument/signatureHelp` request. A client that opts into + contextSupport will also support the `retriggerCharacters` on + `SignatureHelpOptions`. + + @since 3.15.0""" + # Since: 3.15.0 + + +@attrs.define +class DeclarationClientCapabilities: + """@since 3.14.0""" + + # Since: 3.14.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether declaration supports dynamic registration. If this is set to `true` + the client supports the new `DeclarationRegistrationOptions` return value + for the corresponding server capability as well.""" + + link_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports additional metadata in the form of declaration links.""" + + +@attrs.define +class DefinitionClientCapabilities: + """Client Capabilities for a {@link DefinitionRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether definition supports dynamic registration.""" + + link_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports additional metadata in the form of definition links. + + @since 3.14.0""" + # Since: 3.14.0 + + +@attrs.define +class TypeDefinitionClientCapabilities: + """Since 3.6.0""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is set to `true` + the client supports the new `TypeDefinitionRegistrationOptions` return value + for the corresponding server capability as well.""" + + link_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports additional metadata in the form of definition links. + + Since 3.14.0""" + + +@attrs.define +class ImplementationClientCapabilities: + """@since 3.6.0""" + + # Since: 3.6.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is set to `true` + the client supports the new `ImplementationRegistrationOptions` return value + for the corresponding server capability as well.""" + + link_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports additional metadata in the form of definition links. + + @since 3.14.0""" + # Since: 3.14.0 + + +@attrs.define +class ReferenceClientCapabilities: + """Client Capabilities for a {@link ReferencesRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether references supports dynamic registration.""" + + +@attrs.define +class DocumentHighlightClientCapabilities: + """Client Capabilities for a {@link DocumentHighlightRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether document highlight supports dynamic registration.""" + + +@attrs.define +class DocumentSymbolClientCapabilitiesSymbolKindType: + value_set: Optional[List[SymbolKind]] = attrs.field(default=None) + """The symbol kind values the client supports. When this + property exists the client also guarantees that it will + handle values outside its set gracefully and falls back + to a default value when unknown. + + If this property is not present the client only supports + the symbol kinds from `File` to `Array` as defined in + the initial version of the protocol.""" + + +@attrs.define +class DocumentSymbolClientCapabilitiesTagSupportType: + value_set: List[SymbolTag] = attrs.field() + """The tags supported by the client.""" + + +@attrs.define +class DocumentSymbolClientCapabilities: + """Client Capabilities for a {@link DocumentSymbolRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether document symbol supports dynamic registration.""" + + symbol_kind: Optional[ + "DocumentSymbolClientCapabilitiesSymbolKindType" + ] = attrs.field(default=None) + """Specific capabilities for the `SymbolKind` in the + `textDocument/documentSymbol` request.""" + + hierarchical_document_symbol_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports hierarchical document symbols.""" + + tag_support: Optional[ + "DocumentSymbolClientCapabilitiesTagSupportType" + ] = attrs.field(default=None) + """The client supports tags on `SymbolInformation`. Tags are supported on + `DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true. + Clients supporting tags have to handle unknown tags gracefully. + + @since 3.16.0""" + # Since: 3.16.0 + + label_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports an additional label presented in the UI when + registering a document symbol provider. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class CodeActionClientCapabilitiesCodeActionLiteralSupportTypeCodeActionKindType: + value_set: List[Union[CodeActionKind, str]] = attrs.field() + """The code action kind values the client supports. When this + property exists the client also guarantees that it will + handle values outside its set gracefully and falls back + to a default value when unknown.""" + + +@attrs.define +class CodeActionClientCapabilitiesCodeActionLiteralSupportType: + code_action_kind: "CodeActionClientCapabilitiesCodeActionLiteralSupportTypeCodeActionKindType" = ( + attrs.field() + ) + """The code action kind is support with the following value + set.""" + + +@attrs.define +class CodeActionClientCapabilitiesResolveSupportType: + properties: List[str] = attrs.field() + """The properties that a client can resolve lazily.""" + + +@attrs.define +class CodeActionClientCapabilities: + """The Client Capabilities of a {@link CodeActionRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether code action supports dynamic registration.""" + + code_action_literal_support: Optional[ + "CodeActionClientCapabilitiesCodeActionLiteralSupportType" + ] = attrs.field(default=None) + """The client support code action literals of type `CodeAction` as a valid + response of the `textDocument/codeAction` request. If the property is not + set the request can only return `Command` literals. + + @since 3.8.0""" + # Since: 3.8.0 + + is_preferred_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether code action supports the `isPreferred` property. + + @since 3.15.0""" + # Since: 3.15.0 + + disabled_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether code action supports the `disabled` property. + + @since 3.16.0""" + # Since: 3.16.0 + + data_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether code action supports the `data` property which is + preserved between a `textDocument/codeAction` and a + `codeAction/resolve` request. + + @since 3.16.0""" + # Since: 3.16.0 + + resolve_support: Optional[ + "CodeActionClientCapabilitiesResolveSupportType" + ] = attrs.field(default=None) + """Whether the client supports resolving additional code action + properties via a separate `codeAction/resolve` request. + + @since 3.16.0""" + # Since: 3.16.0 + + honors_change_annotations: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client honors the change annotations in + text edits and resource operations returned via the + `CodeAction#edit` property by for example presenting + the workspace edit in the user interface and asking + for confirmation. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class CodeLensClientCapabilities: + """The client capabilities of a {@link CodeLensRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether code lens supports dynamic registration.""" + + +@attrs.define +class DocumentLinkClientCapabilities: + """The client capabilities of a {@link DocumentLinkRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether document link supports dynamic registration.""" + + tooltip_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client supports the `tooltip` property on `DocumentLink`. + + @since 3.15.0""" + # Since: 3.15.0 + + +@attrs.define +class DocumentColorClientCapabilities: + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is set to `true` + the client supports the new `DocumentColorRegistrationOptions` return value + for the corresponding server capability as well.""" + + +@attrs.define +class DocumentFormattingClientCapabilities: + """Client capabilities of a {@link DocumentFormattingRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether formatting supports dynamic registration.""" + + +@attrs.define +class DocumentRangeFormattingClientCapabilities: + """Client capabilities of a {@link DocumentRangeFormattingRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether range formatting supports dynamic registration.""" + + ranges_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client supports formatting multiple ranges at once. + + @since 3.18.0 + @proposed""" + # Since: 3.18.0 + # Proposed + + +@attrs.define +class DocumentOnTypeFormattingClientCapabilities: + """Client capabilities of a {@link DocumentOnTypeFormattingRequest}.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether on type formatting supports dynamic registration.""" + + +@attrs.define +class RenameClientCapabilities: + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether rename supports dynamic registration.""" + + prepare_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Client supports testing for validity of rename operations + before execution. + + @since 3.12.0""" + # Since: 3.12.0 + + prepare_support_default_behavior: Optional[ + PrepareSupportDefaultBehavior + ] = attrs.field(default=None) + """Client supports the default behavior result. + + The value indicates the default behavior used by the + client. + + @since 3.16.0""" + # Since: 3.16.0 + + honors_change_annotations: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client honors the change annotations in + text edits and resource operations returned via the + rename request's workspace edit by for example presenting + the workspace edit in the user interface and asking + for confirmation. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class FoldingRangeClientCapabilitiesFoldingRangeKindType: + value_set: Optional[List[Union[FoldingRangeKind, str]]] = attrs.field(default=None) + """The folding range kind values the client supports. When this + property exists the client also guarantees that it will + handle values outside its set gracefully and falls back + to a default value when unknown.""" + + +@attrs.define +class FoldingRangeClientCapabilitiesFoldingRangeType: + collapsed_text: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """If set, the client signals that it supports setting collapsedText on + folding ranges to display custom labels instead of the default text. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class FoldingRangeClientCapabilities: + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration for folding range + providers. If this is set to `true` the client supports the new + `FoldingRangeRegistrationOptions` return value for the corresponding + server capability as well.""" + + range_limit: Optional[int] = attrs.field( + validator=attrs.validators.optional(validators.uinteger_validator), default=None + ) + """The maximum number of folding ranges that the client prefers to receive + per document. The value serves as a hint, servers are free to follow the + limit.""" + + line_folding_only: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """If set, the client signals that it only supports folding complete lines. + If set, client will ignore specified `startCharacter` and `endCharacter` + properties in a FoldingRange.""" + + folding_range_kind: Optional[ + "FoldingRangeClientCapabilitiesFoldingRangeKindType" + ] = attrs.field(default=None) + """Specific options for the folding range kind. + + @since 3.17.0""" + # Since: 3.17.0 + + folding_range: Optional[ + "FoldingRangeClientCapabilitiesFoldingRangeType" + ] = attrs.field(default=None) + """Specific options for the folding range. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class SelectionRangeClientCapabilities: + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration for selection range providers. If this is set to `true` + the client supports the new `SelectionRangeRegistrationOptions` return value for the corresponding server + capability as well.""" + + +@attrs.define +class PublishDiagnosticsClientCapabilitiesTagSupportType: + value_set: List[DiagnosticTag] = attrs.field() + """The tags supported by the client.""" + + +@attrs.define +class PublishDiagnosticsClientCapabilities: + """The publish diagnostic client capabilities.""" + + related_information: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the clients accepts diagnostics with related information.""" + + tag_support: Optional[ + "PublishDiagnosticsClientCapabilitiesTagSupportType" + ] = attrs.field(default=None) + """Client supports the tag property to provide meta data about a diagnostic. + Clients supporting tags have to handle unknown tags gracefully. + + @since 3.15.0""" + # Since: 3.15.0 + + version_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client interprets the version property of the + `textDocument/publishDiagnostics` notification's parameter. + + @since 3.15.0""" + # Since: 3.15.0 + + code_description_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Client supports a codeDescription property + + @since 3.16.0""" + # Since: 3.16.0 + + data_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether code action supports the `data` property which is + preserved between a `textDocument/publishDiagnostics` and + `textDocument/codeAction` request. + + @since 3.16.0""" + # Since: 3.16.0 + + +@attrs.define +class CallHierarchyClientCapabilities: + """@since 3.16.0""" + + # Since: 3.16.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is set to `true` + the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + return value for the corresponding server capability as well.""" + + +@attrs.define +class SemanticTokensClientCapabilitiesRequestsTypeFullType1: + delta: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client will send the `textDocument/semanticTokens/full/delta` request if + the server provides a corresponding handler.""" + + +@attrs.define +class SemanticTokensClientCapabilitiesRequestsType: + range: Optional[Union[bool, Any]] = attrs.field(default=None) + """The client will send the `textDocument/semanticTokens/range` request if + the server provides a corresponding handler.""" + + full: Optional[ + Union[bool, "SemanticTokensClientCapabilitiesRequestsTypeFullType1"] + ] = attrs.field(default=None) + """The client will send the `textDocument/semanticTokens/full` request if + the server provides a corresponding handler.""" + + +@attrs.define +class SemanticTokensClientCapabilities: + """@since 3.16.0""" + + # Since: 3.16.0 + + requests: "SemanticTokensClientCapabilitiesRequestsType" = attrs.field() + """Which requests the client supports and might send to the server + depending on the server's capability. Please note that clients might not + show semantic tokens or degrade some of the user experience if a range + or full request is advertised by the client but not provided by the + server. If for example the client capability `requests.full` and + `request.range` are both set to true but the server only provides a + range provider the client might not render a minimap correctly or might + even decide to not show any semantic tokens at all.""" + + token_types: List[str] = attrs.field() + """The token types that the client supports.""" + + token_modifiers: List[str] = attrs.field() + """The token modifiers that the client supports.""" + + formats: List[TokenFormat] = attrs.field() + """The token formats the clients supports.""" + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is set to `true` + the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + return value for the corresponding server capability as well.""" + + overlapping_token_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client supports tokens that can overlap each other.""" + + multiline_token_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client supports tokens that can span multiple lines.""" + + server_cancel_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client allows the server to actively cancel a + semantic token request, e.g. supports returning + LSPErrorCodes.ServerCancelled. If a server does the client + needs to retrigger the request. + + @since 3.17.0""" + # Since: 3.17.0 + + augments_syntax_tokens: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client uses semantic tokens to augment existing + syntax tokens. If set to `true` client side created syntax + tokens and semantic tokens are both used for colorization. If + set to `false` the client only uses the returned semantic tokens + for colorization. + + If the value is `undefined` then the client behavior is not + specified. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class LinkedEditingRangeClientCapabilities: + """Client capabilities for the linked editing range request. + + @since 3.16.0""" + + # Since: 3.16.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is set to `true` + the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + return value for the corresponding server capability as well.""" + + +@attrs.define +class MonikerClientCapabilities: + """Client capabilities specific to the moniker request. + + @since 3.16.0""" + + # Since: 3.16.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether moniker supports dynamic registration. If this is set to `true` + the client supports the new `MonikerRegistrationOptions` return value + for the corresponding server capability as well.""" + + +@attrs.define +class TypeHierarchyClientCapabilities: + """@since 3.17.0""" + + # Since: 3.17.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is set to `true` + the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + return value for the corresponding server capability as well.""" + + +@attrs.define +class InlineValueClientCapabilities: + """Client capabilities specific to inline values. + + @since 3.17.0""" + + # Since: 3.17.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration for inline value providers.""" + + +@attrs.define +class InlayHintClientCapabilitiesResolveSupportType: + properties: List[str] = attrs.field() + """The properties that a client can resolve lazily.""" + + +@attrs.define +class InlayHintClientCapabilities: + """Inlay hint client capabilities. + + @since 3.17.0""" + + # Since: 3.17.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether inlay hints support dynamic registration.""" + + resolve_support: Optional[ + "InlayHintClientCapabilitiesResolveSupportType" + ] = attrs.field(default=None) + """Indicates which properties a client can resolve lazily on an inlay + hint.""" + + +@attrs.define +class DiagnosticClientCapabilities: + """Client capabilities specific to diagnostic pull requests. + + @since 3.17.0""" + + # Since: 3.17.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is set to `true` + the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + return value for the corresponding server capability as well.""" + + related_document_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the clients supports related documents for document diagnostic pulls.""" + + +@attrs.define +class InlineCompletionClientCapabilities: + """Client capabilities specific to inline completions. + + @since 3.18.0 + @proposed""" + + # Since: 3.18.0 + # Proposed + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration for inline completion providers.""" + + +@attrs.define +class NotebookDocumentSyncClientCapabilities: + """Notebook specific client capabilities. + + @since 3.17.0""" + + # Since: 3.17.0 + + dynamic_registration: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether implementation supports dynamic registration. If this is + set to `true` the client supports the new + `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` + return value for the corresponding server capability as well.""" + + execution_summary_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """The client supports sending execution summary data per cell.""" + + +@attrs.define +class ShowMessageRequestClientCapabilitiesMessageActionItemType: + additional_properties_support: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + """Whether the client supports additional attributes which + are preserved and send back to the server in the + request's response.""" + + +@attrs.define +class ShowMessageRequestClientCapabilities: + """Show message request client capabilities""" + + message_action_item: Optional[ + "ShowMessageRequestClientCapabilitiesMessageActionItemType" + ] = attrs.field(default=None) + """Capabilities specific to the `MessageActionItem` type.""" + + +@attrs.define +class ShowDocumentClientCapabilities: + """Client capabilities for the showDocument request. + + @since 3.16.0""" + + # Since: 3.16.0 + + support: bool = attrs.field(validator=attrs.validators.instance_of(bool)) + """The client has support for the showDocument + request.""" + + +@attrs.define +class RegularExpressionsClientCapabilities: + """Client capabilities specific to regular expressions. + + @since 3.16.0""" + + # Since: 3.16.0 + + engine: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The engine's name.""" + + version: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The engine's version.""" + + +@attrs.define +class MarkdownClientCapabilities: + """Client capabilities specific to the used markdown parser. + + @since 3.16.0""" + + # Since: 3.16.0 + + parser: str = attrs.field(validator=attrs.validators.instance_of(str)) + """The name of the parser.""" + + version: Optional[str] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(str)), + default=None, + ) + """The version of the parser.""" + + allowed_tags: Optional[List[str]] = attrs.field(default=None) + """A list of HTML tags that the client allows / supports in + Markdown. + + @since 3.17.0""" + # Since: 3.17.0 + + +@attrs.define +class TextDocumentColorPresentationOptions: + work_done_progress: Optional[bool] = attrs.field( + validator=attrs.validators.optional(attrs.validators.instance_of(bool)), + default=None, + ) + + document_selector: Optional[Union[DocumentSelector, None]] = attrs.field( + default=None + ) + """A document selector to identify the scope of the registration. If set to null + the document selector provided on the client side will be used.""" + + +@attrs.define +class ResponseError: + code: int = attrs.field(validator=validators.integer_validator) + """A number indicating the error type that occurred.""" + message: str = attrs.field(validator=attrs.validators.instance_of(str)) + """A string providing a short description of the error.""" + data: Optional[LSPAny] = attrs.field(default=None) + """A primitive or structured value that contains additional information + about the error. Can be omitted.""" + + +@attrs.define +class ResponseErrorMessage: + id: Optional[Union[int, str]] = attrs.field(default=None) + """The request id where the error occurred.""" + error: Optional[ResponseError] = attrs.field(default=None) + """The error object in case a request fails.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentImplementationRequest: + """A request to resolve the implementation locations of a symbol at a given text + document position. The request's parameter is of type {@link TextDocumentPositionParams} + the response is of type {@link Definition} or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: ImplementationParams = attrs.field() + method: str = "textDocument/implementation" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentImplementationResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[Definition, List[DefinitionLink], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentTypeDefinitionRequest: + """A request to resolve the type definition locations of a symbol at a given text + document position. The request's parameter is of type {@link TextDocumentPositionParams} + the response is of type {@link Definition} or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: TypeDefinitionParams = attrs.field() + method: str = "textDocument/typeDefinition" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentTypeDefinitionResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[Definition, List[DefinitionLink], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceWorkspaceFoldersRequest: + """The `workspace/workspaceFolders` is sent from the server to the client to fetch the open workspace folders.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: Optional[None] = attrs.field(default=None) + method: str = "workspace/workspaceFolders" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceWorkspaceFoldersResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[WorkspaceFolder], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +WorkspaceConfigurationParams = ConfigurationParams + + +@attrs.define +class WorkspaceConfigurationRequest: + """The 'workspace/configuration' request is sent from the server to the client to fetch a certain + configuration setting. + + This pull model replaces the old push model were the client signaled configuration change via an + event. If the server still needs to react to configuration changes (since the server caches the + result of `workspace/configuration` requests) the server should register for an empty configuration + change event and empty the cache if such an event is received.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: WorkspaceConfigurationParams = attrs.field() + method: str = "workspace/configuration" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceConfigurationResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: List[LSPAny] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDocumentColorRequest: + """A request to list all color symbols found in a given text document. The request's + parameter is of type {@link DocumentColorParams} the + response is of type {@link ColorInformation ColorInformation[]} or a Thenable + that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentColorParams = attrs.field() + method: str = "textDocument/documentColor" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDocumentColorResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: List[ColorInformation] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentColorPresentationRequest: + """A request to list all presentation for a color. The request's + parameter is of type {@link ColorPresentationParams} the + response is of type {@link ColorInformation ColorInformation[]} or a Thenable + that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: ColorPresentationParams = attrs.field() + method: str = "textDocument/colorPresentation" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentColorPresentationResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: List[ColorPresentation] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentFoldingRangeRequest: + """A request to provide folding ranges in a document. The request's + parameter is of type {@link FoldingRangeParams}, the + response is of type {@link FoldingRangeList} or a Thenable + that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: FoldingRangeParams = attrs.field() + method: str = "textDocument/foldingRange" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentFoldingRangeResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[FoldingRange], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceFoldingRangeRefreshRequest: + """@since 3.18.0 + @proposed""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: Optional[None] = attrs.field(default=None) + method: str = "workspace/foldingRange/refresh" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceFoldingRangeRefreshResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDeclarationRequest: + """A request to resolve the type definition locations of a symbol at a given text + document position. The request's parameter is of type {@link TextDocumentPositionParams} + the response is of type {@link Declaration} or a typed array of {@link DeclarationLink} + or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DeclarationParams = attrs.field() + method: str = "textDocument/declaration" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDeclarationResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[Declaration, List[DeclarationLink], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSelectionRangeRequest: + """A request to provide selection ranges in a document. The request's + parameter is of type {@link SelectionRangeParams}, the + response is of type {@link SelectionRange SelectionRange[]} or a Thenable + that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: SelectionRangeParams = attrs.field() + method: str = "textDocument/selectionRange" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSelectionRangeResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[SelectionRange], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowWorkDoneProgressCreateRequest: + """The `window/workDoneProgress/create` request is sent from the server to the client to initiate progress + reporting from the server.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: WorkDoneProgressCreateParams = attrs.field() + method: str = "window/workDoneProgress/create" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowWorkDoneProgressCreateResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentPrepareCallHierarchyRequest: + """A request to result a `CallHierarchyItem` in a document at a given position. + Can be used as an input to an incoming or outgoing call hierarchy. + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CallHierarchyPrepareParams = attrs.field() + method: str = "textDocument/prepareCallHierarchy" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentPrepareCallHierarchyResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[CallHierarchyItem], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CallHierarchyIncomingCallsRequest: + """A request to resolve the incoming calls for a given `CallHierarchyItem`. + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CallHierarchyIncomingCallsParams = attrs.field() + method: str = "callHierarchy/incomingCalls" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CallHierarchyIncomingCallsResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[CallHierarchyIncomingCall], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CallHierarchyOutgoingCallsRequest: + """A request to resolve the outgoing calls for a given `CallHierarchyItem`. + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CallHierarchyOutgoingCallsParams = attrs.field() + method: str = "callHierarchy/outgoingCalls" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CallHierarchyOutgoingCallsResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[CallHierarchyOutgoingCall], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSemanticTokensFullRequest: + """@since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: SemanticTokensParams = attrs.field() + method: str = "textDocument/semanticTokens/full" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSemanticTokensFullResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[SemanticTokens, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSemanticTokensFullDeltaRequest: + """@since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: SemanticTokensDeltaParams = attrs.field() + method: str = "textDocument/semanticTokens/full/delta" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSemanticTokensFullDeltaResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[SemanticTokens, SemanticTokensDelta, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSemanticTokensRangeRequest: + """@since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: SemanticTokensRangeParams = attrs.field() + method: str = "textDocument/semanticTokens/range" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSemanticTokensRangeResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[SemanticTokens, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceSemanticTokensRefreshRequest: + """@since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: Optional[None] = attrs.field(default=None) + method: str = "workspace/semanticTokens/refresh" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceSemanticTokensRefreshResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowShowDocumentRequest: + """A request to show a document. This request might open an + external program depending on the value of the URI to open. + For example a request to open `https://code.visualstudio.com/` + will very likely open the URI in a WEB browser. + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: ShowDocumentParams = attrs.field() + method: str = "window/showDocument" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowShowDocumentResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: ShowDocumentResult = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentLinkedEditingRangeRequest: + """A request to provide ranges that can be edited together. + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: LinkedEditingRangeParams = attrs.field() + method: str = "textDocument/linkedEditingRange" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentLinkedEditingRangeResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[LinkedEditingRanges, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceWillCreateFilesRequest: + """The will create files request is sent from the client to the server before files are actually + created as long as the creation is triggered from within the client. + + The request can return a `WorkspaceEdit` which will be applied to workspace before the + files are created. Hence the `WorkspaceEdit` can not manipulate the content of the file + to be created. + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CreateFilesParams = attrs.field() + method: str = "workspace/willCreateFiles" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceWillCreateFilesResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[WorkspaceEdit, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceWillRenameFilesRequest: + """The will rename files request is sent from the client to the server before files are actually + renamed as long as the rename is triggered from within the client. + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: RenameFilesParams = attrs.field() + method: str = "workspace/willRenameFiles" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceWillRenameFilesResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[WorkspaceEdit, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceWillDeleteFilesRequest: + """The did delete files notification is sent from the client to the server when + files were deleted from within the client. + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DeleteFilesParams = attrs.field() + method: str = "workspace/willDeleteFiles" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceWillDeleteFilesResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[WorkspaceEdit, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentMonikerRequest: + """A request to get the moniker of a symbol at a given text document position. + The request parameter is of type {@link TextDocumentPositionParams}. + The response is of type {@link Moniker Moniker[]} or `null`.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: MonikerParams = attrs.field() + method: str = "textDocument/moniker" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentMonikerResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[Moniker], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentPrepareTypeHierarchyRequest: + """A request to result a `TypeHierarchyItem` in a document at a given position. + Can be used as an input to a subtypes or supertypes type hierarchy. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: TypeHierarchyPrepareParams = attrs.field() + method: str = "textDocument/prepareTypeHierarchy" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentPrepareTypeHierarchyResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[TypeHierarchyItem], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TypeHierarchySupertypesRequest: + """A request to resolve the supertypes for a given `TypeHierarchyItem`. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: TypeHierarchySupertypesParams = attrs.field() + method: str = "typeHierarchy/supertypes" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TypeHierarchySupertypesResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[TypeHierarchyItem], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TypeHierarchySubtypesRequest: + """A request to resolve the subtypes for a given `TypeHierarchyItem`. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: TypeHierarchySubtypesParams = attrs.field() + method: str = "typeHierarchy/subtypes" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TypeHierarchySubtypesResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[TypeHierarchyItem], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentInlineValueRequest: + """A request to provide inline values in a document. The request's parameter is of + type {@link InlineValueParams}, the response is of type + {@link InlineValue InlineValue[]} or a Thenable that resolves to such. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: InlineValueParams = attrs.field() + method: str = "textDocument/inlineValue" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentInlineValueResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[InlineValue], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceInlineValueRefreshRequest: + """@since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: Optional[None] = attrs.field(default=None) + method: str = "workspace/inlineValue/refresh" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceInlineValueRefreshResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentInlayHintRequest: + """A request to provide inlay hints in a document. The request's parameter is of + type {@link InlayHintsParams}, the response is of type + {@link InlayHint InlayHint[]} or a Thenable that resolves to such. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: InlayHintParams = attrs.field() + method: str = "textDocument/inlayHint" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentInlayHintResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[InlayHint], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class InlayHintResolveRequest: + """A request to resolve additional properties for an inlay hint. + The request's parameter is of type {@link InlayHint}, the response is + of type {@link InlayHint} or a Thenable that resolves to such. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: InlayHint = attrs.field() + method: str = "inlayHint/resolve" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class InlayHintResolveResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: InlayHint = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceInlayHintRefreshRequest: + """@since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: Optional[None] = attrs.field(default=None) + method: str = "workspace/inlayHint/refresh" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceInlayHintRefreshResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDiagnosticRequest: + """The document diagnostic request definition. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentDiagnosticParams = attrs.field() + method: str = "textDocument/diagnostic" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDiagnosticResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: DocumentDiagnosticReport = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDiagnosticRequest: + """The workspace diagnostic request definition. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: WorkspaceDiagnosticParams = attrs.field() + method: str = "workspace/diagnostic" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDiagnosticResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: WorkspaceDiagnosticReport = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDiagnosticRefreshRequest: + """The diagnostic refresh request definition. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: Optional[None] = attrs.field(default=None) + method: str = "workspace/diagnostic/refresh" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDiagnosticRefreshResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentInlineCompletionRequest: + """A request to provide inline completions in a document. The request's parameter is of + type {@link InlineCompletionParams}, the response is of type + {@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such. + + @since 3.18.0 + @proposed""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: InlineCompletionParams = attrs.field() + method: str = "textDocument/inlineCompletion" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentInlineCompletionResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[InlineCompletionList, List[InlineCompletionItem], None] = attrs.field( + default=None + ) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class ClientRegisterCapabilityRequest: + """The `client/registerCapability` request is sent from the server to the client to register a new capability + handler on the client side.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: RegistrationParams = attrs.field() + method: str = "client/registerCapability" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class ClientRegisterCapabilityResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class ClientUnregisterCapabilityRequest: + """The `client/unregisterCapability` request is sent from the server to the client to unregister a previously registered capability + handler on the client side.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: UnregistrationParams = attrs.field() + method: str = "client/unregisterCapability" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class ClientUnregisterCapabilityResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class InitializeRequest: + """The initialize request is sent from the client to the server. + It is sent once as the request after starting up the server. + The requests parameter is of type {@link InitializeParams} + the response if of type {@link InitializeResult} of a Thenable that + resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: InitializeParams = attrs.field() + method: str = "initialize" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class InitializeResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: InitializeResult = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class ShutdownRequest: + """A shutdown request is sent from the client to the server. + It is sent once when the client decides to shutdown the + server. The only notification that is sent after a shutdown request + is the exit event.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: Optional[None] = attrs.field(default=None) + method: str = "shutdown" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class ShutdownResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowShowMessageRequestRequest: + """The show message request is sent from the server to the client to show a message + and a set of options actions to the user.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: ShowMessageRequestParams = attrs.field() + method: str = "window/showMessageRequest" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowShowMessageRequestResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[MessageActionItem, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentWillSaveWaitUntilRequest: + """A document will save request is sent from the client to the server before + the document is actually saved. The request can return an array of TextEdits + which will be applied to the text document before it is saved. Please note that + clients might drop results if computing the text edits took too long or if a + server constantly fails on this request. This is done to keep the save fast and + reliable.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: WillSaveTextDocumentParams = attrs.field() + method: str = "textDocument/willSaveWaitUntil" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentWillSaveWaitUntilResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[TextEdit], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentCompletionRequest: + """Request to request completion at a given text document position. The request's + parameter is of type {@link TextDocumentPosition} the response + is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList} + or a Thenable that resolves to such. + + The request can delay the computation of the {@link CompletionItem.detail `detail`} + and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve` + request. However, properties that are needed for the initial sorting and filtering, like `sortText`, + `filterText`, `insertText`, and `textEdit`, must not be changed during resolve.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CompletionParams = attrs.field() + method: str = "textDocument/completion" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentCompletionResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[CompletionItem], CompletionList, None] = attrs.field( + default=None + ) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CompletionItemResolveRequest: + """Request to resolve additional information for a given completion item.The request's + parameter is of type {@link CompletionItem} the response + is of type {@link CompletionItem} or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CompletionItem = attrs.field() + method: str = "completionItem/resolve" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CompletionItemResolveResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: CompletionItem = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentHoverRequest: + """Request to request hover information at a given text document position. The request's + parameter is of type {@link TextDocumentPosition} the response is of + type {@link Hover} or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: HoverParams = attrs.field() + method: str = "textDocument/hover" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentHoverResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[Hover, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSignatureHelpRequest: + id: Union[int, str] = attrs.field() + """The request id.""" + params: SignatureHelpParams = attrs.field() + method: str = "textDocument/signatureHelp" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentSignatureHelpResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[SignatureHelp, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDefinitionRequest: + """A request to resolve the definition location of a symbol at a given text + document position. The request's parameter is of type {@link TextDocumentPosition} + the response is of either type {@link Definition} or a typed array of + {@link DefinitionLink} or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DefinitionParams = attrs.field() + method: str = "textDocument/definition" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDefinitionResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[Definition, List[DefinitionLink], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentReferencesRequest: + """A request to resolve project-wide references for the symbol denoted + by the given text document position. The request's parameter is of + type {@link ReferenceParams} the response is of type + {@link Location Location[]} or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: ReferenceParams = attrs.field() + method: str = "textDocument/references" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentReferencesResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[Location], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDocumentHighlightRequest: + """Request to resolve a {@link DocumentHighlight} for a given + text document position. The request's parameter is of type {@link TextDocumentPosition} + the request response is an array of type {@link DocumentHighlight} + or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentHighlightParams = attrs.field() + method: str = "textDocument/documentHighlight" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDocumentHighlightResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[DocumentHighlight], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDocumentSymbolRequest: + """A request to list all symbols found in a given text document. The request's + parameter is of type {@link TextDocumentIdentifier} the + response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable + that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentSymbolParams = attrs.field() + method: str = "textDocument/documentSymbol" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDocumentSymbolResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[SymbolInformation], List[DocumentSymbol], None] = attrs.field( + default=None + ) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentCodeActionRequest: + """A request to provide commands for the given text document and range.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CodeActionParams = attrs.field() + method: str = "textDocument/codeAction" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentCodeActionResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[Union[Command, CodeAction]], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CodeActionResolveRequest: + """Request to resolve additional information for a given code action.The request's + parameter is of type {@link CodeAction} the response + is of type {@link CodeAction} or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CodeAction = attrs.field() + method: str = "codeAction/resolve" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CodeActionResolveResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: CodeAction = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceSymbolRequest: + """A request to list project-wide symbols matching the query string given + by the {@link WorkspaceSymbolParams}. The response is + of type {@link SymbolInformation SymbolInformation[]} or a Thenable that + resolves to such. + + @since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients + need to advertise support for WorkspaceSymbols via the client capability + `workspace.symbol.resolveSupport`.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: WorkspaceSymbolParams = attrs.field() + method: str = "workspace/symbol" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceSymbolResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[SymbolInformation], List[WorkspaceSymbol], None] = attrs.field( + default=None + ) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceSymbolResolveRequest: + """A request to resolve the range inside the workspace + symbol's location. + + @since 3.17.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: WorkspaceSymbol = attrs.field() + method: str = "workspaceSymbol/resolve" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceSymbolResolveResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: WorkspaceSymbol = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentCodeLensRequest: + """A request to provide code lens for the given text document.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CodeLensParams = attrs.field() + method: str = "textDocument/codeLens" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentCodeLensResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[CodeLens], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CodeLensResolveRequest: + """A request to resolve a command for a given code lens.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: CodeLens = attrs.field() + method: str = "codeLens/resolve" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CodeLensResolveResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: CodeLens = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceCodeLensRefreshRequest: + """A request to refresh all code actions + + @since 3.16.0""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: Optional[None] = attrs.field(default=None) + method: str = "workspace/codeLens/refresh" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceCodeLensRefreshResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: None = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDocumentLinkRequest: + """A request to provide document links""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentLinkParams = attrs.field() + method: str = "textDocument/documentLink" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDocumentLinkResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[DocumentLink], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class DocumentLinkResolveRequest: + """Request to resolve additional information for a given document link. The request's + parameter is of type {@link DocumentLink} the response + is of type {@link DocumentLink} or a Thenable that resolves to such.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentLink = attrs.field() + method: str = "documentLink/resolve" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class DocumentLinkResolveResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: DocumentLink = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentFormattingRequest: + """A request to format a whole document.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentFormattingParams = attrs.field() + method: str = "textDocument/formatting" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentFormattingResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[TextEdit], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentRangeFormattingRequest: + """A request to format a range in a document.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentRangeFormattingParams = attrs.field() + method: str = "textDocument/rangeFormatting" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentRangeFormattingResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[TextEdit], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentRangesFormattingRequest: + """A request to format ranges in a document. + + @since 3.18.0 + @proposed""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentRangesFormattingParams = attrs.field() + method: str = "textDocument/rangesFormatting" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentRangesFormattingResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[TextEdit], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentOnTypeFormattingRequest: + """A request to format a document on type.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: DocumentOnTypeFormattingParams = attrs.field() + method: str = "textDocument/onTypeFormatting" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentOnTypeFormattingResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[List[TextEdit], None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentRenameRequest: + """A request to rename a symbol.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: RenameParams = attrs.field() + method: str = "textDocument/rename" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentRenameResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[WorkspaceEdit, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentPrepareRenameRequest: + """A request to test and perform the setup necessary for a rename. + + @since 3.16 - support for default behavior""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: PrepareRenameParams = attrs.field() + method: str = "textDocument/prepareRename" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentPrepareRenameResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[PrepareRenameResult, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceExecuteCommandRequest: + """A request send from the client to the server to execute a command. The request might return + a workspace edit which the client will apply to the workspace.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: ExecuteCommandParams = attrs.field() + method: str = "workspace/executeCommand" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceExecuteCommandResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: Union[LSPAny, None] = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceApplyEditRequest: + """A request sent from the server to the client to modified certain resources.""" + + id: Union[int, str] = attrs.field() + """The request id.""" + params: ApplyWorkspaceEditParams = attrs.field() + method: str = "workspace/applyEdit" + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceApplyEditResponse: + id: Optional[Union[int, str]] = attrs.field() + """The request id.""" + result: ApplyWorkspaceEditResult = attrs.field(default=None) + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDidChangeWorkspaceFoldersNotification: + """The `workspace/didChangeWorkspaceFolders` notification is sent from the client to the server when the workspace + folder configuration changes.""" + + params: DidChangeWorkspaceFoldersParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["workspace/didChangeWorkspaceFolders"]), + default="workspace/didChangeWorkspaceFolders", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowWorkDoneProgressCancelNotification: + """The `window/workDoneProgress/cancel` notification is sent from the client to the server to cancel a progress + initiated on the server side.""" + + params: WorkDoneProgressCancelParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["window/workDoneProgress/cancel"]), + default="window/workDoneProgress/cancel", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDidCreateFilesNotification: + """The did create files notification is sent from the client to the server when + files were created from within the client. + + @since 3.16.0""" + + params: CreateFilesParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["workspace/didCreateFiles"]), + default="workspace/didCreateFiles", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDidRenameFilesNotification: + """The did rename files notification is sent from the client to the server when + files were renamed from within the client. + + @since 3.16.0""" + + params: RenameFilesParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["workspace/didRenameFiles"]), + default="workspace/didRenameFiles", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDidDeleteFilesNotification: + """The will delete files request is sent from the client to the server before files are actually + deleted as long as the deletion is triggered from within the client. + + @since 3.16.0""" + + params: DeleteFilesParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["workspace/didDeleteFiles"]), + default="workspace/didDeleteFiles", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class NotebookDocumentDidOpenNotification: + """A notification sent when a notebook opens. + + @since 3.17.0""" + + params: DidOpenNotebookDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["notebookDocument/didOpen"]), + default="notebookDocument/didOpen", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class NotebookDocumentDidChangeNotification: + params: DidChangeNotebookDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["notebookDocument/didChange"]), + default="notebookDocument/didChange", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class NotebookDocumentDidSaveNotification: + """A notification sent when a notebook document is saved. + + @since 3.17.0""" + + params: DidSaveNotebookDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["notebookDocument/didSave"]), + default="notebookDocument/didSave", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class NotebookDocumentDidCloseNotification: + """A notification sent when a notebook closes. + + @since 3.17.0""" + + params: DidCloseNotebookDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["notebookDocument/didClose"]), + default="notebookDocument/didClose", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class InitializedNotification: + """The initialized notification is sent from the client to the + server after the client is fully initialized and the server + is allowed to send requests from the server to the client.""" + + params: InitializedParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["initialized"]), + default="initialized", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class ExitNotification: + """The exit event is sent from the client to the server to + ask the server to exit its process.""" + + params: Optional[None] = attrs.field(default=None) + method: str = attrs.field( + validator=attrs.validators.in_(["exit"]), + default="exit", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDidChangeConfigurationNotification: + """The configuration change notification is sent from the client to the server + when the client's configuration has changed. The notification contains + the changed configuration as defined by the language client.""" + + params: DidChangeConfigurationParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["workspace/didChangeConfiguration"]), + default="workspace/didChangeConfiguration", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowShowMessageNotification: + """The show message notification is sent from a server to a client to ask + the client to display a particular message in the user interface.""" + + params: ShowMessageParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["window/showMessage"]), + default="window/showMessage", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WindowLogMessageNotification: + """The log message notification is sent from the server to the client to ask + the client to log a particular message.""" + + params: LogMessageParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["window/logMessage"]), + default="window/logMessage", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TelemetryEventNotification: + """The telemetry event notification is sent from the server to the client to ask + the client to log telemetry data.""" + + params: LSPAny = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["telemetry/event"]), + default="telemetry/event", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDidOpenNotification: + """The document open notification is sent from the client to the server to signal + newly opened text documents. The document's truth is now managed by the client + and the server must not try to read the document's truth using the document's + uri. Open in this sense means it is managed by the client. It doesn't necessarily + mean that its content is presented in an editor. An open notification must not + be sent more than once without a corresponding close notification send before. + This means open and close notification must be balanced and the max open count + is one.""" + + params: DidOpenTextDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["textDocument/didOpen"]), + default="textDocument/didOpen", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDidChangeNotification: + """The document change notification is sent from the client to the server to signal + changes to a text document.""" + + params: DidChangeTextDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["textDocument/didChange"]), + default="textDocument/didChange", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDidCloseNotification: + """The document close notification is sent from the client to the server when + the document got closed in the client. The document's truth now exists where + the document's uri points to (e.g. if the document's uri is a file uri the + truth now exists on disk). As with the open notification the close notification + is about managing the document's content. Receiving a close notification + doesn't mean that the document was open in an editor before. A close + notification requires a previous open notification to be sent.""" + + params: DidCloseTextDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["textDocument/didClose"]), + default="textDocument/didClose", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentDidSaveNotification: + """The document save notification is sent from the client to the server when + the document got saved in the client.""" + + params: DidSaveTextDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["textDocument/didSave"]), + default="textDocument/didSave", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentWillSaveNotification: + """A document will save notification is sent from the client to the server before + the document is actually saved.""" + + params: WillSaveTextDocumentParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["textDocument/willSave"]), + default="textDocument/willSave", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class WorkspaceDidChangeWatchedFilesNotification: + """The watched files notification is sent from the client to the server when + the client detects changes to file watched by the language client.""" + + params: DidChangeWatchedFilesParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["workspace/didChangeWatchedFiles"]), + default="workspace/didChangeWatchedFiles", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class TextDocumentPublishDiagnosticsNotification: + """Diagnostics notification are sent from the server to the client to signal + results of validation runs.""" + + params: PublishDiagnosticsParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["textDocument/publishDiagnostics"]), + default="textDocument/publishDiagnostics", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class SetTraceNotification: + params: SetTraceParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["$/setTrace"]), + default="$/setTrace", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class LogTraceNotification: + params: LogTraceParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["$/logTrace"]), + default="$/logTrace", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class CancelRequestNotification: + params: CancelParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["$/cancelRequest"]), + default="$/cancelRequest", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@attrs.define +class ProgressNotification: + params: ProgressParams = attrs.field() + method: str = attrs.field( + validator=attrs.validators.in_(["$/progress"]), + default="$/progress", + ) + """The method to be invoked.""" + jsonrpc: str = attrs.field(default="2.0") + + +@enum.unique +class MessageDirection(enum.Enum): + Both = "both" + ClientToServer = "clientToServer" + ServerToClient = "serverToClient" + + +CALL_HIERARCHY_INCOMING_CALLS = "callHierarchy/incomingCalls" +CALL_HIERARCHY_OUTGOING_CALLS = "callHierarchy/outgoingCalls" +CANCEL_REQUEST = "$/cancelRequest" +CLIENT_REGISTER_CAPABILITY = "client/registerCapability" +CLIENT_UNREGISTER_CAPABILITY = "client/unregisterCapability" +CODE_ACTION_RESOLVE = "codeAction/resolve" +CODE_LENS_RESOLVE = "codeLens/resolve" +COMPLETION_ITEM_RESOLVE = "completionItem/resolve" +DOCUMENT_LINK_RESOLVE = "documentLink/resolve" +EXIT = "exit" +INITIALIZE = "initialize" +INITIALIZED = "initialized" +INLAY_HINT_RESOLVE = "inlayHint/resolve" +LOG_TRACE = "$/logTrace" +NOTEBOOK_DOCUMENT_DID_CHANGE = "notebookDocument/didChange" +NOTEBOOK_DOCUMENT_DID_CLOSE = "notebookDocument/didClose" +NOTEBOOK_DOCUMENT_DID_OPEN = "notebookDocument/didOpen" +NOTEBOOK_DOCUMENT_DID_SAVE = "notebookDocument/didSave" +PROGRESS = "$/progress" +SET_TRACE = "$/setTrace" +SHUTDOWN = "shutdown" +TELEMETRY_EVENT = "telemetry/event" +TEXT_DOCUMENT_CODE_ACTION = "textDocument/codeAction" +TEXT_DOCUMENT_CODE_LENS = "textDocument/codeLens" +TEXT_DOCUMENT_COLOR_PRESENTATION = "textDocument/colorPresentation" +TEXT_DOCUMENT_COMPLETION = "textDocument/completion" +TEXT_DOCUMENT_DECLARATION = "textDocument/declaration" +TEXT_DOCUMENT_DEFINITION = "textDocument/definition" +TEXT_DOCUMENT_DIAGNOSTIC = "textDocument/diagnostic" +TEXT_DOCUMENT_DID_CHANGE = "textDocument/didChange" +TEXT_DOCUMENT_DID_CLOSE = "textDocument/didClose" +TEXT_DOCUMENT_DID_OPEN = "textDocument/didOpen" +TEXT_DOCUMENT_DID_SAVE = "textDocument/didSave" +TEXT_DOCUMENT_DOCUMENT_COLOR = "textDocument/documentColor" +TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT = "textDocument/documentHighlight" +TEXT_DOCUMENT_DOCUMENT_LINK = "textDocument/documentLink" +TEXT_DOCUMENT_DOCUMENT_SYMBOL = "textDocument/documentSymbol" +TEXT_DOCUMENT_FOLDING_RANGE = "textDocument/foldingRange" +TEXT_DOCUMENT_FORMATTING = "textDocument/formatting" +TEXT_DOCUMENT_HOVER = "textDocument/hover" +TEXT_DOCUMENT_IMPLEMENTATION = "textDocument/implementation" +TEXT_DOCUMENT_INLAY_HINT = "textDocument/inlayHint" +TEXT_DOCUMENT_INLINE_COMPLETION = "textDocument/inlineCompletion" +TEXT_DOCUMENT_INLINE_VALUE = "textDocument/inlineValue" +TEXT_DOCUMENT_LINKED_EDITING_RANGE = "textDocument/linkedEditingRange" +TEXT_DOCUMENT_MONIKER = "textDocument/moniker" +TEXT_DOCUMENT_ON_TYPE_FORMATTING = "textDocument/onTypeFormatting" +TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY = "textDocument/prepareCallHierarchy" +TEXT_DOCUMENT_PREPARE_RENAME = "textDocument/prepareRename" +TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY = "textDocument/prepareTypeHierarchy" +TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS = "textDocument/publishDiagnostics" +TEXT_DOCUMENT_RANGES_FORMATTING = "textDocument/rangesFormatting" +TEXT_DOCUMENT_RANGE_FORMATTING = "textDocument/rangeFormatting" +TEXT_DOCUMENT_REFERENCES = "textDocument/references" +TEXT_DOCUMENT_RENAME = "textDocument/rename" +TEXT_DOCUMENT_SELECTION_RANGE = "textDocument/selectionRange" +TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL = "textDocument/semanticTokens/full" +TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA = "textDocument/semanticTokens/full/delta" +TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE = "textDocument/semanticTokens/range" +TEXT_DOCUMENT_SIGNATURE_HELP = "textDocument/signatureHelp" +TEXT_DOCUMENT_TYPE_DEFINITION = "textDocument/typeDefinition" +TEXT_DOCUMENT_WILL_SAVE = "textDocument/willSave" +TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL = "textDocument/willSaveWaitUntil" +TYPE_HIERARCHY_SUBTYPES = "typeHierarchy/subtypes" +TYPE_HIERARCHY_SUPERTYPES = "typeHierarchy/supertypes" +WINDOW_LOG_MESSAGE = "window/logMessage" +WINDOW_SHOW_DOCUMENT = "window/showDocument" +WINDOW_SHOW_MESSAGE = "window/showMessage" +WINDOW_SHOW_MESSAGE_REQUEST = "window/showMessageRequest" +WINDOW_WORK_DONE_PROGRESS_CANCEL = "window/workDoneProgress/cancel" +WINDOW_WORK_DONE_PROGRESS_CREATE = "window/workDoneProgress/create" +WORKSPACE_APPLY_EDIT = "workspace/applyEdit" +WORKSPACE_CODE_LENS_REFRESH = "workspace/codeLens/refresh" +WORKSPACE_CONFIGURATION = "workspace/configuration" +WORKSPACE_DIAGNOSTIC = "workspace/diagnostic" +WORKSPACE_DIAGNOSTIC_REFRESH = "workspace/diagnostic/refresh" +WORKSPACE_DID_CHANGE_CONFIGURATION = "workspace/didChangeConfiguration" +WORKSPACE_DID_CHANGE_WATCHED_FILES = "workspace/didChangeWatchedFiles" +WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS = "workspace/didChangeWorkspaceFolders" +WORKSPACE_DID_CREATE_FILES = "workspace/didCreateFiles" +WORKSPACE_DID_DELETE_FILES = "workspace/didDeleteFiles" +WORKSPACE_DID_RENAME_FILES = "workspace/didRenameFiles" +WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand" +WORKSPACE_FOLDING_RANGE_REFRESH = "workspace/foldingRange/refresh" +WORKSPACE_INLAY_HINT_REFRESH = "workspace/inlayHint/refresh" +WORKSPACE_INLINE_VALUE_REFRESH = "workspace/inlineValue/refresh" +WORKSPACE_SEMANTIC_TOKENS_REFRESH = "workspace/semanticTokens/refresh" +WORKSPACE_SYMBOL = "workspace/symbol" +WORKSPACE_SYMBOL_RESOLVE = "workspaceSymbol/resolve" +WORKSPACE_WILL_CREATE_FILES = "workspace/willCreateFiles" +WORKSPACE_WILL_DELETE_FILES = "workspace/willDeleteFiles" +WORKSPACE_WILL_RENAME_FILES = "workspace/willRenameFiles" +WORKSPACE_WORKSPACE_FOLDERS = "workspace/workspaceFolders" + +METHOD_TO_TYPES = { + # Requests + CALL_HIERARCHY_INCOMING_CALLS: ( + CallHierarchyIncomingCallsRequest, + CallHierarchyIncomingCallsResponse, + CallHierarchyIncomingCallsParams, + None, + ), + CALL_HIERARCHY_OUTGOING_CALLS: ( + CallHierarchyOutgoingCallsRequest, + CallHierarchyOutgoingCallsResponse, + CallHierarchyOutgoingCallsParams, + None, + ), + CLIENT_REGISTER_CAPABILITY: ( + ClientRegisterCapabilityRequest, + ClientRegisterCapabilityResponse, + RegistrationParams, + None, + ), + CLIENT_UNREGISTER_CAPABILITY: ( + ClientUnregisterCapabilityRequest, + ClientUnregisterCapabilityResponse, + UnregistrationParams, + None, + ), + CODE_ACTION_RESOLVE: ( + CodeActionResolveRequest, + CodeActionResolveResponse, + CodeAction, + None, + ), + CODE_LENS_RESOLVE: ( + CodeLensResolveRequest, + CodeLensResolveResponse, + CodeLens, + None, + ), + COMPLETION_ITEM_RESOLVE: ( + CompletionItemResolveRequest, + CompletionItemResolveResponse, + CompletionItem, + None, + ), + DOCUMENT_LINK_RESOLVE: ( + DocumentLinkResolveRequest, + DocumentLinkResolveResponse, + DocumentLink, + None, + ), + INITIALIZE: (InitializeRequest, InitializeResponse, InitializeParams, None), + INLAY_HINT_RESOLVE: ( + InlayHintResolveRequest, + InlayHintResolveResponse, + InlayHint, + None, + ), + SHUTDOWN: (ShutdownRequest, ShutdownResponse, None, None), + TEXT_DOCUMENT_CODE_ACTION: ( + TextDocumentCodeActionRequest, + TextDocumentCodeActionResponse, + CodeActionParams, + CodeActionRegistrationOptions, + ), + TEXT_DOCUMENT_CODE_LENS: ( + TextDocumentCodeLensRequest, + TextDocumentCodeLensResponse, + CodeLensParams, + CodeLensRegistrationOptions, + ), + TEXT_DOCUMENT_COLOR_PRESENTATION: ( + TextDocumentColorPresentationRequest, + TextDocumentColorPresentationResponse, + ColorPresentationParams, + TextDocumentColorPresentationOptions, + ), + TEXT_DOCUMENT_COMPLETION: ( + TextDocumentCompletionRequest, + TextDocumentCompletionResponse, + CompletionParams, + CompletionRegistrationOptions, + ), + TEXT_DOCUMENT_DECLARATION: ( + TextDocumentDeclarationRequest, + TextDocumentDeclarationResponse, + DeclarationParams, + DeclarationRegistrationOptions, + ), + TEXT_DOCUMENT_DEFINITION: ( + TextDocumentDefinitionRequest, + TextDocumentDefinitionResponse, + DefinitionParams, + DefinitionRegistrationOptions, + ), + TEXT_DOCUMENT_DIAGNOSTIC: ( + TextDocumentDiagnosticRequest, + TextDocumentDiagnosticResponse, + DocumentDiagnosticParams, + DiagnosticRegistrationOptions, + ), + TEXT_DOCUMENT_DOCUMENT_COLOR: ( + TextDocumentDocumentColorRequest, + TextDocumentDocumentColorResponse, + DocumentColorParams, + DocumentColorRegistrationOptions, + ), + TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT: ( + TextDocumentDocumentHighlightRequest, + TextDocumentDocumentHighlightResponse, + DocumentHighlightParams, + DocumentHighlightRegistrationOptions, + ), + TEXT_DOCUMENT_DOCUMENT_LINK: ( + TextDocumentDocumentLinkRequest, + TextDocumentDocumentLinkResponse, + DocumentLinkParams, + DocumentLinkRegistrationOptions, + ), + TEXT_DOCUMENT_DOCUMENT_SYMBOL: ( + TextDocumentDocumentSymbolRequest, + TextDocumentDocumentSymbolResponse, + DocumentSymbolParams, + DocumentSymbolRegistrationOptions, + ), + TEXT_DOCUMENT_FOLDING_RANGE: ( + TextDocumentFoldingRangeRequest, + TextDocumentFoldingRangeResponse, + FoldingRangeParams, + FoldingRangeRegistrationOptions, + ), + TEXT_DOCUMENT_FORMATTING: ( + TextDocumentFormattingRequest, + TextDocumentFormattingResponse, + DocumentFormattingParams, + DocumentFormattingRegistrationOptions, + ), + TEXT_DOCUMENT_HOVER: ( + TextDocumentHoverRequest, + TextDocumentHoverResponse, + HoverParams, + HoverRegistrationOptions, + ), + TEXT_DOCUMENT_IMPLEMENTATION: ( + TextDocumentImplementationRequest, + TextDocumentImplementationResponse, + ImplementationParams, + ImplementationRegistrationOptions, + ), + TEXT_DOCUMENT_INLAY_HINT: ( + TextDocumentInlayHintRequest, + TextDocumentInlayHintResponse, + InlayHintParams, + InlayHintRegistrationOptions, + ), + TEXT_DOCUMENT_INLINE_COMPLETION: ( + TextDocumentInlineCompletionRequest, + TextDocumentInlineCompletionResponse, + InlineCompletionParams, + InlineCompletionRegistrationOptions, + ), + TEXT_DOCUMENT_INLINE_VALUE: ( + TextDocumentInlineValueRequest, + TextDocumentInlineValueResponse, + InlineValueParams, + InlineValueRegistrationOptions, + ), + TEXT_DOCUMENT_LINKED_EDITING_RANGE: ( + TextDocumentLinkedEditingRangeRequest, + TextDocumentLinkedEditingRangeResponse, + LinkedEditingRangeParams, + LinkedEditingRangeRegistrationOptions, + ), + TEXT_DOCUMENT_MONIKER: ( + TextDocumentMonikerRequest, + TextDocumentMonikerResponse, + MonikerParams, + MonikerRegistrationOptions, + ), + TEXT_DOCUMENT_ON_TYPE_FORMATTING: ( + TextDocumentOnTypeFormattingRequest, + TextDocumentOnTypeFormattingResponse, + DocumentOnTypeFormattingParams, + DocumentOnTypeFormattingRegistrationOptions, + ), + TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY: ( + TextDocumentPrepareCallHierarchyRequest, + TextDocumentPrepareCallHierarchyResponse, + CallHierarchyPrepareParams, + CallHierarchyRegistrationOptions, + ), + TEXT_DOCUMENT_PREPARE_RENAME: ( + TextDocumentPrepareRenameRequest, + TextDocumentPrepareRenameResponse, + PrepareRenameParams, + None, + ), + TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY: ( + TextDocumentPrepareTypeHierarchyRequest, + TextDocumentPrepareTypeHierarchyResponse, + TypeHierarchyPrepareParams, + TypeHierarchyRegistrationOptions, + ), + TEXT_DOCUMENT_RANGES_FORMATTING: ( + TextDocumentRangesFormattingRequest, + TextDocumentRangesFormattingResponse, + DocumentRangesFormattingParams, + DocumentRangeFormattingRegistrationOptions, + ), + TEXT_DOCUMENT_RANGE_FORMATTING: ( + TextDocumentRangeFormattingRequest, + TextDocumentRangeFormattingResponse, + DocumentRangeFormattingParams, + DocumentRangeFormattingRegistrationOptions, + ), + TEXT_DOCUMENT_REFERENCES: ( + TextDocumentReferencesRequest, + TextDocumentReferencesResponse, + ReferenceParams, + ReferenceRegistrationOptions, + ), + TEXT_DOCUMENT_RENAME: ( + TextDocumentRenameRequest, + TextDocumentRenameResponse, + RenameParams, + RenameRegistrationOptions, + ), + TEXT_DOCUMENT_SELECTION_RANGE: ( + TextDocumentSelectionRangeRequest, + TextDocumentSelectionRangeResponse, + SelectionRangeParams, + SelectionRangeRegistrationOptions, + ), + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL: ( + TextDocumentSemanticTokensFullRequest, + TextDocumentSemanticTokensFullResponse, + SemanticTokensParams, + SemanticTokensRegistrationOptions, + ), + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA: ( + TextDocumentSemanticTokensFullDeltaRequest, + TextDocumentSemanticTokensFullDeltaResponse, + SemanticTokensDeltaParams, + SemanticTokensRegistrationOptions, + ), + TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE: ( + TextDocumentSemanticTokensRangeRequest, + TextDocumentSemanticTokensRangeResponse, + SemanticTokensRangeParams, + None, + ), + TEXT_DOCUMENT_SIGNATURE_HELP: ( + TextDocumentSignatureHelpRequest, + TextDocumentSignatureHelpResponse, + SignatureHelpParams, + SignatureHelpRegistrationOptions, + ), + TEXT_DOCUMENT_TYPE_DEFINITION: ( + TextDocumentTypeDefinitionRequest, + TextDocumentTypeDefinitionResponse, + TypeDefinitionParams, + TypeDefinitionRegistrationOptions, + ), + TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL: ( + TextDocumentWillSaveWaitUntilRequest, + TextDocumentWillSaveWaitUntilResponse, + WillSaveTextDocumentParams, + TextDocumentRegistrationOptions, + ), + TYPE_HIERARCHY_SUBTYPES: ( + TypeHierarchySubtypesRequest, + TypeHierarchySubtypesResponse, + TypeHierarchySubtypesParams, + None, + ), + TYPE_HIERARCHY_SUPERTYPES: ( + TypeHierarchySupertypesRequest, + TypeHierarchySupertypesResponse, + TypeHierarchySupertypesParams, + None, + ), + WINDOW_SHOW_DOCUMENT: ( + WindowShowDocumentRequest, + WindowShowDocumentResponse, + ShowDocumentParams, + None, + ), + WINDOW_SHOW_MESSAGE_REQUEST: ( + WindowShowMessageRequestRequest, + WindowShowMessageRequestResponse, + ShowMessageRequestParams, + None, + ), + WINDOW_WORK_DONE_PROGRESS_CREATE: ( + WindowWorkDoneProgressCreateRequest, + WindowWorkDoneProgressCreateResponse, + WorkDoneProgressCreateParams, + None, + ), + WORKSPACE_APPLY_EDIT: ( + WorkspaceApplyEditRequest, + WorkspaceApplyEditResponse, + ApplyWorkspaceEditParams, + None, + ), + WORKSPACE_CODE_LENS_REFRESH: ( + WorkspaceCodeLensRefreshRequest, + WorkspaceCodeLensRefreshResponse, + None, + None, + ), + WORKSPACE_CONFIGURATION: ( + WorkspaceConfigurationRequest, + WorkspaceConfigurationResponse, + ConfigurationParams, + None, + ), + WORKSPACE_DIAGNOSTIC: ( + WorkspaceDiagnosticRequest, + WorkspaceDiagnosticResponse, + WorkspaceDiagnosticParams, + None, + ), + WORKSPACE_DIAGNOSTIC_REFRESH: ( + WorkspaceDiagnosticRefreshRequest, + WorkspaceDiagnosticRefreshResponse, + None, + None, + ), + WORKSPACE_EXECUTE_COMMAND: ( + WorkspaceExecuteCommandRequest, + WorkspaceExecuteCommandResponse, + ExecuteCommandParams, + ExecuteCommandRegistrationOptions, + ), + WORKSPACE_FOLDING_RANGE_REFRESH: ( + WorkspaceFoldingRangeRefreshRequest, + WorkspaceFoldingRangeRefreshResponse, + None, + None, + ), + WORKSPACE_INLAY_HINT_REFRESH: ( + WorkspaceInlayHintRefreshRequest, + WorkspaceInlayHintRefreshResponse, + None, + None, + ), + WORKSPACE_INLINE_VALUE_REFRESH: ( + WorkspaceInlineValueRefreshRequest, + WorkspaceInlineValueRefreshResponse, + None, + None, + ), + WORKSPACE_SEMANTIC_TOKENS_REFRESH: ( + WorkspaceSemanticTokensRefreshRequest, + WorkspaceSemanticTokensRefreshResponse, + None, + None, + ), + WORKSPACE_SYMBOL: ( + WorkspaceSymbolRequest, + WorkspaceSymbolResponse, + WorkspaceSymbolParams, + WorkspaceSymbolRegistrationOptions, + ), + WORKSPACE_SYMBOL_RESOLVE: ( + WorkspaceSymbolResolveRequest, + WorkspaceSymbolResolveResponse, + WorkspaceSymbol, + None, + ), + WORKSPACE_WILL_CREATE_FILES: ( + WorkspaceWillCreateFilesRequest, + WorkspaceWillCreateFilesResponse, + CreateFilesParams, + FileOperationRegistrationOptions, + ), + WORKSPACE_WILL_DELETE_FILES: ( + WorkspaceWillDeleteFilesRequest, + WorkspaceWillDeleteFilesResponse, + DeleteFilesParams, + FileOperationRegistrationOptions, + ), + WORKSPACE_WILL_RENAME_FILES: ( + WorkspaceWillRenameFilesRequest, + WorkspaceWillRenameFilesResponse, + RenameFilesParams, + FileOperationRegistrationOptions, + ), + WORKSPACE_WORKSPACE_FOLDERS: ( + WorkspaceWorkspaceFoldersRequest, + WorkspaceWorkspaceFoldersResponse, + None, + None, + ), + # Notifications + CANCEL_REQUEST: (CancelRequestNotification, None, CancelParams, None), + EXIT: (ExitNotification, None, None, None), + INITIALIZED: (InitializedNotification, None, InitializedParams, None), + LOG_TRACE: (LogTraceNotification, None, LogTraceParams, None), + NOTEBOOK_DOCUMENT_DID_CHANGE: ( + NotebookDocumentDidChangeNotification, + None, + DidChangeNotebookDocumentParams, + None, + ), + NOTEBOOK_DOCUMENT_DID_CLOSE: ( + NotebookDocumentDidCloseNotification, + None, + DidCloseNotebookDocumentParams, + None, + ), + NOTEBOOK_DOCUMENT_DID_OPEN: ( + NotebookDocumentDidOpenNotification, + None, + DidOpenNotebookDocumentParams, + None, + ), + NOTEBOOK_DOCUMENT_DID_SAVE: ( + NotebookDocumentDidSaveNotification, + None, + DidSaveNotebookDocumentParams, + None, + ), + PROGRESS: (ProgressNotification, None, ProgressParams, None), + SET_TRACE: (SetTraceNotification, None, SetTraceParams, None), + TELEMETRY_EVENT: (TelemetryEventNotification, None, LSPAny, None), + TEXT_DOCUMENT_DID_CHANGE: ( + TextDocumentDidChangeNotification, + None, + DidChangeTextDocumentParams, + TextDocumentChangeRegistrationOptions, + ), + TEXT_DOCUMENT_DID_CLOSE: ( + TextDocumentDidCloseNotification, + None, + DidCloseTextDocumentParams, + TextDocumentRegistrationOptions, + ), + TEXT_DOCUMENT_DID_OPEN: ( + TextDocumentDidOpenNotification, + None, + DidOpenTextDocumentParams, + TextDocumentRegistrationOptions, + ), + TEXT_DOCUMENT_DID_SAVE: ( + TextDocumentDidSaveNotification, + None, + DidSaveTextDocumentParams, + TextDocumentSaveRegistrationOptions, + ), + TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS: ( + TextDocumentPublishDiagnosticsNotification, + None, + PublishDiagnosticsParams, + None, + ), + TEXT_DOCUMENT_WILL_SAVE: ( + TextDocumentWillSaveNotification, + None, + WillSaveTextDocumentParams, + TextDocumentRegistrationOptions, + ), + WINDOW_LOG_MESSAGE: (WindowLogMessageNotification, None, LogMessageParams, None), + WINDOW_SHOW_MESSAGE: (WindowShowMessageNotification, None, ShowMessageParams, None), + WINDOW_WORK_DONE_PROGRESS_CANCEL: ( + WindowWorkDoneProgressCancelNotification, + None, + WorkDoneProgressCancelParams, + None, + ), + WORKSPACE_DID_CHANGE_CONFIGURATION: ( + WorkspaceDidChangeConfigurationNotification, + None, + DidChangeConfigurationParams, + DidChangeConfigurationRegistrationOptions, + ), + WORKSPACE_DID_CHANGE_WATCHED_FILES: ( + WorkspaceDidChangeWatchedFilesNotification, + None, + DidChangeWatchedFilesParams, + DidChangeWatchedFilesRegistrationOptions, + ), + WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS: ( + WorkspaceDidChangeWorkspaceFoldersNotification, + None, + DidChangeWorkspaceFoldersParams, + None, + ), + WORKSPACE_DID_CREATE_FILES: ( + WorkspaceDidCreateFilesNotification, + None, + CreateFilesParams, + FileOperationRegistrationOptions, + ), + WORKSPACE_DID_DELETE_FILES: ( + WorkspaceDidDeleteFilesNotification, + None, + DeleteFilesParams, + FileOperationRegistrationOptions, + ), + WORKSPACE_DID_RENAME_FILES: ( + WorkspaceDidRenameFilesNotification, + None, + RenameFilesParams, + FileOperationRegistrationOptions, + ), +} +REQUESTS = Union[ + CallHierarchyIncomingCallsRequest, + CallHierarchyOutgoingCallsRequest, + ClientRegisterCapabilityRequest, + ClientUnregisterCapabilityRequest, + CodeActionResolveRequest, + CodeLensResolveRequest, + CompletionItemResolveRequest, + DocumentLinkResolveRequest, + InitializeRequest, + InlayHintResolveRequest, + ShutdownRequest, + TextDocumentCodeActionRequest, + TextDocumentCodeLensRequest, + TextDocumentColorPresentationRequest, + TextDocumentCompletionRequest, + TextDocumentDeclarationRequest, + TextDocumentDefinitionRequest, + TextDocumentDiagnosticRequest, + TextDocumentDocumentColorRequest, + TextDocumentDocumentHighlightRequest, + TextDocumentDocumentLinkRequest, + TextDocumentDocumentSymbolRequest, + TextDocumentFoldingRangeRequest, + TextDocumentFormattingRequest, + TextDocumentHoverRequest, + TextDocumentImplementationRequest, + TextDocumentInlayHintRequest, + TextDocumentInlineCompletionRequest, + TextDocumentInlineValueRequest, + TextDocumentLinkedEditingRangeRequest, + TextDocumentMonikerRequest, + TextDocumentOnTypeFormattingRequest, + TextDocumentPrepareCallHierarchyRequest, + TextDocumentPrepareRenameRequest, + TextDocumentPrepareTypeHierarchyRequest, + TextDocumentRangeFormattingRequest, + TextDocumentRangesFormattingRequest, + TextDocumentReferencesRequest, + TextDocumentRenameRequest, + TextDocumentSelectionRangeRequest, + TextDocumentSemanticTokensFullDeltaRequest, + TextDocumentSemanticTokensFullRequest, + TextDocumentSemanticTokensRangeRequest, + TextDocumentSignatureHelpRequest, + TextDocumentTypeDefinitionRequest, + TextDocumentWillSaveWaitUntilRequest, + TypeHierarchySubtypesRequest, + TypeHierarchySupertypesRequest, + WindowShowDocumentRequest, + WindowShowMessageRequestRequest, + WindowWorkDoneProgressCreateRequest, + WorkspaceApplyEditRequest, + WorkspaceCodeLensRefreshRequest, + WorkspaceConfigurationRequest, + WorkspaceDiagnosticRefreshRequest, + WorkspaceDiagnosticRequest, + WorkspaceExecuteCommandRequest, + WorkspaceFoldingRangeRefreshRequest, + WorkspaceInlayHintRefreshRequest, + WorkspaceInlineValueRefreshRequest, + WorkspaceSemanticTokensRefreshRequest, + WorkspaceSymbolRequest, + WorkspaceSymbolResolveRequest, + WorkspaceWillCreateFilesRequest, + WorkspaceWillDeleteFilesRequest, + WorkspaceWillRenameFilesRequest, + WorkspaceWorkspaceFoldersRequest, +] +RESPONSES = Union[ + CallHierarchyIncomingCallsResponse, + CallHierarchyOutgoingCallsResponse, + ClientRegisterCapabilityResponse, + ClientUnregisterCapabilityResponse, + CodeActionResolveResponse, + CodeLensResolveResponse, + CompletionItemResolveResponse, + DocumentLinkResolveResponse, + InitializeResponse, + InlayHintResolveResponse, + ShutdownResponse, + TextDocumentCodeActionResponse, + TextDocumentCodeLensResponse, + TextDocumentColorPresentationResponse, + TextDocumentCompletionResponse, + TextDocumentDeclarationResponse, + TextDocumentDefinitionResponse, + TextDocumentDiagnosticResponse, + TextDocumentDocumentColorResponse, + TextDocumentDocumentHighlightResponse, + TextDocumentDocumentLinkResponse, + TextDocumentDocumentSymbolResponse, + TextDocumentFoldingRangeResponse, + TextDocumentFormattingResponse, + TextDocumentHoverResponse, + TextDocumentImplementationResponse, + TextDocumentInlayHintResponse, + TextDocumentInlineCompletionResponse, + TextDocumentInlineValueResponse, + TextDocumentLinkedEditingRangeResponse, + TextDocumentMonikerResponse, + TextDocumentOnTypeFormattingResponse, + TextDocumentPrepareCallHierarchyResponse, + TextDocumentPrepareRenameResponse, + TextDocumentPrepareTypeHierarchyResponse, + TextDocumentRangeFormattingResponse, + TextDocumentRangesFormattingResponse, + TextDocumentReferencesResponse, + TextDocumentRenameResponse, + TextDocumentSelectionRangeResponse, + TextDocumentSemanticTokensFullDeltaResponse, + TextDocumentSemanticTokensFullResponse, + TextDocumentSemanticTokensRangeResponse, + TextDocumentSignatureHelpResponse, + TextDocumentTypeDefinitionResponse, + TextDocumentWillSaveWaitUntilResponse, + TypeHierarchySubtypesResponse, + TypeHierarchySupertypesResponse, + WindowShowDocumentResponse, + WindowShowMessageRequestResponse, + WindowWorkDoneProgressCreateResponse, + WorkspaceApplyEditResponse, + WorkspaceCodeLensRefreshResponse, + WorkspaceConfigurationResponse, + WorkspaceDiagnosticRefreshResponse, + WorkspaceDiagnosticResponse, + WorkspaceExecuteCommandResponse, + WorkspaceFoldingRangeRefreshResponse, + WorkspaceInlayHintRefreshResponse, + WorkspaceInlineValueRefreshResponse, + WorkspaceSemanticTokensRefreshResponse, + WorkspaceSymbolResolveResponse, + WorkspaceSymbolResponse, + WorkspaceWillCreateFilesResponse, + WorkspaceWillDeleteFilesResponse, + WorkspaceWillRenameFilesResponse, + WorkspaceWorkspaceFoldersResponse, +] +NOTIFICATIONS = Union[ + CancelRequestNotification, + ExitNotification, + InitializedNotification, + LogTraceNotification, + NotebookDocumentDidChangeNotification, + NotebookDocumentDidCloseNotification, + NotebookDocumentDidOpenNotification, + NotebookDocumentDidSaveNotification, + ProgressNotification, + SetTraceNotification, + TelemetryEventNotification, + TextDocumentDidChangeNotification, + TextDocumentDidCloseNotification, + TextDocumentDidOpenNotification, + TextDocumentDidSaveNotification, + TextDocumentPublishDiagnosticsNotification, + TextDocumentWillSaveNotification, + WindowLogMessageNotification, + WindowShowMessageNotification, + WindowWorkDoneProgressCancelNotification, + WorkspaceDidChangeConfigurationNotification, + WorkspaceDidChangeWatchedFilesNotification, + WorkspaceDidChangeWorkspaceFoldersNotification, + WorkspaceDidCreateFilesNotification, + WorkspaceDidDeleteFilesNotification, + WorkspaceDidRenameFilesNotification, +] +MESSAGE_TYPES = Union[REQUESTS, RESPONSES, NOTIFICATIONS, ResponseErrorMessage] + +_KEYWORD_CLASSES = [CallHierarchyIncomingCall] + + +def is_keyword_class(cls: type) -> bool: + """Returns true if the class has a property that may be python keyword.""" + return any(cls is c for c in _KEYWORD_CLASSES) + + +_SPECIAL_CLASSES = [ + CallHierarchyIncomingCallsRequest, + CallHierarchyIncomingCallsResponse, + CallHierarchyOutgoingCallsRequest, + CallHierarchyOutgoingCallsResponse, + CallHierarchyRegistrationOptions, + CancelRequestNotification, + ClientRegisterCapabilityRequest, + ClientRegisterCapabilityResponse, + ClientUnregisterCapabilityRequest, + ClientUnregisterCapabilityResponse, + CodeActionRegistrationOptions, + CodeActionResolveRequest, + CodeActionResolveResponse, + CodeLensRegistrationOptions, + CodeLensResolveRequest, + CodeLensResolveResponse, + CompletionItemResolveRequest, + CompletionItemResolveResponse, + CompletionRegistrationOptions, + CreateFile, + DeclarationRegistrationOptions, + DefinitionRegistrationOptions, + DeleteFile, + DiagnosticRegistrationOptions, + DocumentColorRegistrationOptions, + DocumentFormattingRegistrationOptions, + DocumentHighlightRegistrationOptions, + DocumentLinkRegistrationOptions, + DocumentLinkResolveRequest, + DocumentLinkResolveResponse, + DocumentOnTypeFormattingRegistrationOptions, + DocumentRangeFormattingRegistrationOptions, + DocumentSymbolRegistrationOptions, + ExitNotification, + FoldingRangeRegistrationOptions, + FullDocumentDiagnosticReport, + HoverRegistrationOptions, + ImplementationRegistrationOptions, + InitializeParams, + InitializeRequest, + InitializeResponse, + InitializedNotification, + InlayHintRegistrationOptions, + InlayHintResolveRequest, + InlayHintResolveResponse, + InlineCompletionRegistrationOptions, + InlineValueRegistrationOptions, + LinkedEditingRangeRegistrationOptions, + LogTraceNotification, + MonikerRegistrationOptions, + NotebookDocumentDidChangeNotification, + NotebookDocumentDidCloseNotification, + NotebookDocumentDidOpenNotification, + NotebookDocumentDidSaveNotification, + OptionalVersionedTextDocumentIdentifier, + ProgressNotification, + ReferenceRegistrationOptions, + RelatedFullDocumentDiagnosticReport, + RelatedUnchangedDocumentDiagnosticReport, + RenameFile, + RenameRegistrationOptions, + ResponseErrorMessage, + SelectionRangeRegistrationOptions, + SemanticTokensRegistrationOptions, + SetTraceNotification, + ShutdownRequest, + ShutdownResponse, + SignatureHelpRegistrationOptions, + StringValue, + TelemetryEventNotification, + TextDocumentChangeRegistrationOptions, + TextDocumentCodeActionRequest, + TextDocumentCodeActionResponse, + TextDocumentCodeLensRequest, + TextDocumentCodeLensResponse, + TextDocumentColorPresentationOptions, + TextDocumentColorPresentationRequest, + TextDocumentColorPresentationResponse, + TextDocumentCompletionRequest, + TextDocumentCompletionResponse, + TextDocumentDeclarationRequest, + TextDocumentDeclarationResponse, + TextDocumentDefinitionRequest, + TextDocumentDefinitionResponse, + TextDocumentDiagnosticRequest, + TextDocumentDiagnosticResponse, + TextDocumentDidChangeNotification, + TextDocumentDidCloseNotification, + TextDocumentDidOpenNotification, + TextDocumentDidSaveNotification, + TextDocumentDocumentColorRequest, + TextDocumentDocumentColorResponse, + TextDocumentDocumentHighlightRequest, + TextDocumentDocumentHighlightResponse, + TextDocumentDocumentLinkRequest, + TextDocumentDocumentLinkResponse, + TextDocumentDocumentSymbolRequest, + TextDocumentDocumentSymbolResponse, + TextDocumentFoldingRangeRequest, + TextDocumentFoldingRangeResponse, + TextDocumentFormattingRequest, + TextDocumentFormattingResponse, + TextDocumentHoverRequest, + TextDocumentHoverResponse, + TextDocumentImplementationRequest, + TextDocumentImplementationResponse, + TextDocumentInlayHintRequest, + TextDocumentInlayHintResponse, + TextDocumentInlineCompletionRequest, + TextDocumentInlineCompletionResponse, + TextDocumentInlineValueRequest, + TextDocumentInlineValueResponse, + TextDocumentLinkedEditingRangeRequest, + TextDocumentLinkedEditingRangeResponse, + TextDocumentMonikerRequest, + TextDocumentMonikerResponse, + TextDocumentOnTypeFormattingRequest, + TextDocumentOnTypeFormattingResponse, + TextDocumentPrepareCallHierarchyRequest, + TextDocumentPrepareCallHierarchyResponse, + TextDocumentPrepareRenameRequest, + TextDocumentPrepareRenameResponse, + TextDocumentPrepareTypeHierarchyRequest, + TextDocumentPrepareTypeHierarchyResponse, + TextDocumentPublishDiagnosticsNotification, + TextDocumentRangeFormattingRequest, + TextDocumentRangeFormattingResponse, + TextDocumentRangesFormattingRequest, + TextDocumentRangesFormattingResponse, + TextDocumentReferencesRequest, + TextDocumentReferencesResponse, + TextDocumentRegistrationOptions, + TextDocumentRenameRequest, + TextDocumentRenameResponse, + TextDocumentSaveRegistrationOptions, + TextDocumentSelectionRangeRequest, + TextDocumentSelectionRangeResponse, + TextDocumentSemanticTokensFullDeltaRequest, + TextDocumentSemanticTokensFullDeltaResponse, + TextDocumentSemanticTokensFullRequest, + TextDocumentSemanticTokensFullResponse, + TextDocumentSemanticTokensRangeRequest, + TextDocumentSemanticTokensRangeResponse, + TextDocumentSignatureHelpRequest, + TextDocumentSignatureHelpResponse, + TextDocumentTypeDefinitionRequest, + TextDocumentTypeDefinitionResponse, + TextDocumentWillSaveNotification, + TextDocumentWillSaveWaitUntilRequest, + TextDocumentWillSaveWaitUntilResponse, + TypeDefinitionRegistrationOptions, + TypeHierarchyRegistrationOptions, + TypeHierarchySubtypesRequest, + TypeHierarchySubtypesResponse, + TypeHierarchySupertypesRequest, + TypeHierarchySupertypesResponse, + UnchangedDocumentDiagnosticReport, + WindowLogMessageNotification, + WindowShowDocumentRequest, + WindowShowDocumentResponse, + WindowShowMessageNotification, + WindowShowMessageRequestRequest, + WindowShowMessageRequestResponse, + WindowWorkDoneProgressCancelNotification, + WindowWorkDoneProgressCreateRequest, + WindowWorkDoneProgressCreateResponse, + WorkDoneProgressBegin, + WorkDoneProgressEnd, + WorkDoneProgressReport, + WorkspaceApplyEditRequest, + WorkspaceApplyEditResponse, + WorkspaceCodeLensRefreshRequest, + WorkspaceCodeLensRefreshResponse, + WorkspaceConfigurationRequest, + WorkspaceConfigurationResponse, + WorkspaceDiagnosticRefreshRequest, + WorkspaceDiagnosticRefreshResponse, + WorkspaceDiagnosticRequest, + WorkspaceDiagnosticResponse, + WorkspaceDidChangeConfigurationNotification, + WorkspaceDidChangeWatchedFilesNotification, + WorkspaceDidChangeWorkspaceFoldersNotification, + WorkspaceDidCreateFilesNotification, + WorkspaceDidDeleteFilesNotification, + WorkspaceDidRenameFilesNotification, + WorkspaceExecuteCommandRequest, + WorkspaceExecuteCommandResponse, + WorkspaceFoldersInitializeParams, + WorkspaceFoldingRangeRefreshRequest, + WorkspaceFoldingRangeRefreshResponse, + WorkspaceFullDocumentDiagnosticReport, + WorkspaceInlayHintRefreshRequest, + WorkspaceInlayHintRefreshResponse, + WorkspaceInlineValueRefreshRequest, + WorkspaceInlineValueRefreshResponse, + WorkspaceSemanticTokensRefreshRequest, + WorkspaceSemanticTokensRefreshResponse, + WorkspaceSymbolRequest, + WorkspaceSymbolResolveRequest, + WorkspaceSymbolResolveResponse, + WorkspaceSymbolResponse, + WorkspaceUnchangedDocumentDiagnosticReport, + WorkspaceWillCreateFilesRequest, + WorkspaceWillCreateFilesResponse, + WorkspaceWillDeleteFilesRequest, + WorkspaceWillDeleteFilesResponse, + WorkspaceWillRenameFilesRequest, + WorkspaceWillRenameFilesResponse, + WorkspaceWorkspaceFoldersRequest, + WorkspaceWorkspaceFoldersResponse, + _InitializeParams, +] + + +def is_special_class(cls: type) -> bool: + """Returns true if the class or its properties require special handling.""" + return any(cls is c for c in _SPECIAL_CLASSES) + + +_SPECIAL_PROPERTIES = [ + "CallHierarchyIncomingCallsRequest.jsonrpc", + "CallHierarchyIncomingCallsRequest.method", + "CallHierarchyIncomingCallsResponse.jsonrpc", + "CallHierarchyIncomingCallsResponse.result", + "CallHierarchyOutgoingCallsRequest.jsonrpc", + "CallHierarchyOutgoingCallsRequest.method", + "CallHierarchyOutgoingCallsResponse.jsonrpc", + "CallHierarchyOutgoingCallsResponse.result", + "CallHierarchyRegistrationOptions.document_selector", + "CancelRequestNotification.jsonrpc", + "CancelRequestNotification.method", + "ClientRegisterCapabilityRequest.jsonrpc", + "ClientRegisterCapabilityRequest.method", + "ClientRegisterCapabilityResponse.jsonrpc", + "ClientRegisterCapabilityResponse.result", + "ClientUnregisterCapabilityRequest.jsonrpc", + "ClientUnregisterCapabilityRequest.method", + "ClientUnregisterCapabilityResponse.jsonrpc", + "ClientUnregisterCapabilityResponse.result", + "CodeActionRegistrationOptions.document_selector", + "CodeActionResolveRequest.jsonrpc", + "CodeActionResolveRequest.method", + "CodeActionResolveResponse.jsonrpc", + "CodeActionResolveResponse.result", + "CodeLensRegistrationOptions.document_selector", + "CodeLensResolveRequest.jsonrpc", + "CodeLensResolveRequest.method", + "CodeLensResolveResponse.jsonrpc", + "CodeLensResolveResponse.result", + "CompletionItemResolveRequest.jsonrpc", + "CompletionItemResolveRequest.method", + "CompletionItemResolveResponse.jsonrpc", + "CompletionItemResolveResponse.result", + "CompletionRegistrationOptions.document_selector", + "CreateFile.kind", + "DeclarationRegistrationOptions.document_selector", + "DefinitionRegistrationOptions.document_selector", + "DeleteFile.kind", + "DiagnosticRegistrationOptions.document_selector", + "DocumentColorRegistrationOptions.document_selector", + "DocumentFormattingRegistrationOptions.document_selector", + "DocumentHighlightRegistrationOptions.document_selector", + "DocumentLinkRegistrationOptions.document_selector", + "DocumentLinkResolveRequest.jsonrpc", + "DocumentLinkResolveRequest.method", + "DocumentLinkResolveResponse.jsonrpc", + "DocumentLinkResolveResponse.result", + "DocumentOnTypeFormattingRegistrationOptions.document_selector", + "DocumentRangeFormattingRegistrationOptions.document_selector", + "DocumentSymbolRegistrationOptions.document_selector", + "ExitNotification.jsonrpc", + "ExitNotification.method", + "FoldingRangeRegistrationOptions.document_selector", + "FullDocumentDiagnosticReport.kind", + "HoverRegistrationOptions.document_selector", + "ImplementationRegistrationOptions.document_selector", + "InitializeParams.process_id", + "InitializeParams.root_path", + "InitializeParams.root_uri", + "InitializeParams.workspace_folders", + "InitializeRequest.jsonrpc", + "InitializeRequest.method", + "InitializeResponse.jsonrpc", + "InitializeResponse.result", + "InitializedNotification.jsonrpc", + "InitializedNotification.method", + "InlayHintRegistrationOptions.document_selector", + "InlayHintResolveRequest.jsonrpc", + "InlayHintResolveRequest.method", + "InlayHintResolveResponse.jsonrpc", + "InlayHintResolveResponse.result", + "InlineCompletionRegistrationOptions.document_selector", + "InlineValueRegistrationOptions.document_selector", + "LinkedEditingRangeRegistrationOptions.document_selector", + "LogTraceNotification.jsonrpc", + "LogTraceNotification.method", + "MonikerRegistrationOptions.document_selector", + "NotebookDocumentDidChangeNotification.jsonrpc", + "NotebookDocumentDidChangeNotification.method", + "NotebookDocumentDidCloseNotification.jsonrpc", + "NotebookDocumentDidCloseNotification.method", + "NotebookDocumentDidOpenNotification.jsonrpc", + "NotebookDocumentDidOpenNotification.method", + "NotebookDocumentDidSaveNotification.jsonrpc", + "NotebookDocumentDidSaveNotification.method", + "OptionalVersionedTextDocumentIdentifier.version", + "ProgressNotification.jsonrpc", + "ProgressNotification.method", + "ReferenceRegistrationOptions.document_selector", + "RelatedFullDocumentDiagnosticReport.kind", + "RelatedUnchangedDocumentDiagnosticReport.kind", + "RenameFile.kind", + "RenameRegistrationOptions.document_selector", + "ResponseErrorMessage.error", + "ResponseErrorMessage.jsonrpc", + "SelectionRangeRegistrationOptions.document_selector", + "SemanticTokensRegistrationOptions.document_selector", + "SetTraceNotification.jsonrpc", + "SetTraceNotification.method", + "ShutdownRequest.jsonrpc", + "ShutdownRequest.method", + "ShutdownResponse.jsonrpc", + "ShutdownResponse.result", + "SignatureHelpRegistrationOptions.document_selector", + "StringValue.kind", + "TelemetryEventNotification.jsonrpc", + "TelemetryEventNotification.method", + "TextDocumentChangeRegistrationOptions.document_selector", + "TextDocumentCodeActionRequest.jsonrpc", + "TextDocumentCodeActionRequest.method", + "TextDocumentCodeActionResponse.jsonrpc", + "TextDocumentCodeActionResponse.result", + "TextDocumentCodeLensRequest.jsonrpc", + "TextDocumentCodeLensRequest.method", + "TextDocumentCodeLensResponse.jsonrpc", + "TextDocumentCodeLensResponse.result", + "TextDocumentColorPresentationOptions.document_selector", + "TextDocumentColorPresentationRequest.jsonrpc", + "TextDocumentColorPresentationRequest.method", + "TextDocumentColorPresentationResponse.jsonrpc", + "TextDocumentColorPresentationResponse.result", + "TextDocumentCompletionRequest.jsonrpc", + "TextDocumentCompletionRequest.method", + "TextDocumentCompletionResponse.jsonrpc", + "TextDocumentCompletionResponse.result", + "TextDocumentDeclarationRequest.jsonrpc", + "TextDocumentDeclarationRequest.method", + "TextDocumentDeclarationResponse.jsonrpc", + "TextDocumentDeclarationResponse.result", + "TextDocumentDefinitionRequest.jsonrpc", + "TextDocumentDefinitionRequest.method", + "TextDocumentDefinitionResponse.jsonrpc", + "TextDocumentDefinitionResponse.result", + "TextDocumentDiagnosticRequest.jsonrpc", + "TextDocumentDiagnosticRequest.method", + "TextDocumentDiagnosticResponse.jsonrpc", + "TextDocumentDiagnosticResponse.result", + "TextDocumentDidChangeNotification.jsonrpc", + "TextDocumentDidChangeNotification.method", + "TextDocumentDidCloseNotification.jsonrpc", + "TextDocumentDidCloseNotification.method", + "TextDocumentDidOpenNotification.jsonrpc", + "TextDocumentDidOpenNotification.method", + "TextDocumentDidSaveNotification.jsonrpc", + "TextDocumentDidSaveNotification.method", + "TextDocumentDocumentColorRequest.jsonrpc", + "TextDocumentDocumentColorRequest.method", + "TextDocumentDocumentColorResponse.jsonrpc", + "TextDocumentDocumentColorResponse.result", + "TextDocumentDocumentHighlightRequest.jsonrpc", + "TextDocumentDocumentHighlightRequest.method", + "TextDocumentDocumentHighlightResponse.jsonrpc", + "TextDocumentDocumentHighlightResponse.result", + "TextDocumentDocumentLinkRequest.jsonrpc", + "TextDocumentDocumentLinkRequest.method", + "TextDocumentDocumentLinkResponse.jsonrpc", + "TextDocumentDocumentLinkResponse.result", + "TextDocumentDocumentSymbolRequest.jsonrpc", + "TextDocumentDocumentSymbolRequest.method", + "TextDocumentDocumentSymbolResponse.jsonrpc", + "TextDocumentDocumentSymbolResponse.result", + "TextDocumentFoldingRangeRequest.jsonrpc", + "TextDocumentFoldingRangeRequest.method", + "TextDocumentFoldingRangeResponse.jsonrpc", + "TextDocumentFoldingRangeResponse.result", + "TextDocumentFormattingRequest.jsonrpc", + "TextDocumentFormattingRequest.method", + "TextDocumentFormattingResponse.jsonrpc", + "TextDocumentFormattingResponse.result", + "TextDocumentHoverRequest.jsonrpc", + "TextDocumentHoverRequest.method", + "TextDocumentHoverResponse.jsonrpc", + "TextDocumentHoverResponse.result", + "TextDocumentImplementationRequest.jsonrpc", + "TextDocumentImplementationRequest.method", + "TextDocumentImplementationResponse.jsonrpc", + "TextDocumentImplementationResponse.result", + "TextDocumentInlayHintRequest.jsonrpc", + "TextDocumentInlayHintRequest.method", + "TextDocumentInlayHintResponse.jsonrpc", + "TextDocumentInlayHintResponse.result", + "TextDocumentInlineCompletionRequest.jsonrpc", + "TextDocumentInlineCompletionRequest.method", + "TextDocumentInlineCompletionResponse.jsonrpc", + "TextDocumentInlineCompletionResponse.result", + "TextDocumentInlineValueRequest.jsonrpc", + "TextDocumentInlineValueRequest.method", + "TextDocumentInlineValueResponse.jsonrpc", + "TextDocumentInlineValueResponse.result", + "TextDocumentLinkedEditingRangeRequest.jsonrpc", + "TextDocumentLinkedEditingRangeRequest.method", + "TextDocumentLinkedEditingRangeResponse.jsonrpc", + "TextDocumentLinkedEditingRangeResponse.result", + "TextDocumentMonikerRequest.jsonrpc", + "TextDocumentMonikerRequest.method", + "TextDocumentMonikerResponse.jsonrpc", + "TextDocumentMonikerResponse.result", + "TextDocumentOnTypeFormattingRequest.jsonrpc", + "TextDocumentOnTypeFormattingRequest.method", + "TextDocumentOnTypeFormattingResponse.jsonrpc", + "TextDocumentOnTypeFormattingResponse.result", + "TextDocumentPrepareCallHierarchyRequest.jsonrpc", + "TextDocumentPrepareCallHierarchyRequest.method", + "TextDocumentPrepareCallHierarchyResponse.jsonrpc", + "TextDocumentPrepareCallHierarchyResponse.result", + "TextDocumentPrepareRenameRequest.jsonrpc", + "TextDocumentPrepareRenameRequest.method", + "TextDocumentPrepareRenameResponse.jsonrpc", + "TextDocumentPrepareRenameResponse.result", + "TextDocumentPrepareTypeHierarchyRequest.jsonrpc", + "TextDocumentPrepareTypeHierarchyRequest.method", + "TextDocumentPrepareTypeHierarchyResponse.jsonrpc", + "TextDocumentPrepareTypeHierarchyResponse.result", + "TextDocumentPublishDiagnosticsNotification.jsonrpc", + "TextDocumentPublishDiagnosticsNotification.method", + "TextDocumentRangeFormattingRequest.jsonrpc", + "TextDocumentRangeFormattingRequest.method", + "TextDocumentRangeFormattingResponse.jsonrpc", + "TextDocumentRangeFormattingResponse.result", + "TextDocumentRangesFormattingRequest.jsonrpc", + "TextDocumentRangesFormattingRequest.method", + "TextDocumentRangesFormattingResponse.jsonrpc", + "TextDocumentRangesFormattingResponse.result", + "TextDocumentReferencesRequest.jsonrpc", + "TextDocumentReferencesRequest.method", + "TextDocumentReferencesResponse.jsonrpc", + "TextDocumentReferencesResponse.result", + "TextDocumentRegistrationOptions.document_selector", + "TextDocumentRenameRequest.jsonrpc", + "TextDocumentRenameRequest.method", + "TextDocumentRenameResponse.jsonrpc", + "TextDocumentRenameResponse.result", + "TextDocumentSaveRegistrationOptions.document_selector", + "TextDocumentSelectionRangeRequest.jsonrpc", + "TextDocumentSelectionRangeRequest.method", + "TextDocumentSelectionRangeResponse.jsonrpc", + "TextDocumentSelectionRangeResponse.result", + "TextDocumentSemanticTokensFullDeltaRequest.jsonrpc", + "TextDocumentSemanticTokensFullDeltaRequest.method", + "TextDocumentSemanticTokensFullDeltaResponse.jsonrpc", + "TextDocumentSemanticTokensFullDeltaResponse.result", + "TextDocumentSemanticTokensFullRequest.jsonrpc", + "TextDocumentSemanticTokensFullRequest.method", + "TextDocumentSemanticTokensFullResponse.jsonrpc", + "TextDocumentSemanticTokensFullResponse.result", + "TextDocumentSemanticTokensRangeRequest.jsonrpc", + "TextDocumentSemanticTokensRangeRequest.method", + "TextDocumentSemanticTokensRangeResponse.jsonrpc", + "TextDocumentSemanticTokensRangeResponse.result", + "TextDocumentSignatureHelpRequest.jsonrpc", + "TextDocumentSignatureHelpRequest.method", + "TextDocumentSignatureHelpResponse.jsonrpc", + "TextDocumentSignatureHelpResponse.result", + "TextDocumentTypeDefinitionRequest.jsonrpc", + "TextDocumentTypeDefinitionRequest.method", + "TextDocumentTypeDefinitionResponse.jsonrpc", + "TextDocumentTypeDefinitionResponse.result", + "TextDocumentWillSaveNotification.jsonrpc", + "TextDocumentWillSaveNotification.method", + "TextDocumentWillSaveWaitUntilRequest.jsonrpc", + "TextDocumentWillSaveWaitUntilRequest.method", + "TextDocumentWillSaveWaitUntilResponse.jsonrpc", + "TextDocumentWillSaveWaitUntilResponse.result", + "TypeDefinitionRegistrationOptions.document_selector", + "TypeHierarchyRegistrationOptions.document_selector", + "TypeHierarchySubtypesRequest.jsonrpc", + "TypeHierarchySubtypesRequest.method", + "TypeHierarchySubtypesResponse.jsonrpc", + "TypeHierarchySubtypesResponse.result", + "TypeHierarchySupertypesRequest.jsonrpc", + "TypeHierarchySupertypesRequest.method", + "TypeHierarchySupertypesResponse.jsonrpc", + "TypeHierarchySupertypesResponse.result", + "UnchangedDocumentDiagnosticReport.kind", + "WindowLogMessageNotification.jsonrpc", + "WindowLogMessageNotification.method", + "WindowShowDocumentRequest.jsonrpc", + "WindowShowDocumentRequest.method", + "WindowShowDocumentResponse.jsonrpc", + "WindowShowDocumentResponse.result", + "WindowShowMessageNotification.jsonrpc", + "WindowShowMessageNotification.method", + "WindowShowMessageRequestRequest.jsonrpc", + "WindowShowMessageRequestRequest.method", + "WindowShowMessageRequestResponse.jsonrpc", + "WindowShowMessageRequestResponse.result", + "WindowWorkDoneProgressCancelNotification.jsonrpc", + "WindowWorkDoneProgressCancelNotification.method", + "WindowWorkDoneProgressCreateRequest.jsonrpc", + "WindowWorkDoneProgressCreateRequest.method", + "WindowWorkDoneProgressCreateResponse.jsonrpc", + "WindowWorkDoneProgressCreateResponse.result", + "WorkDoneProgressBegin.kind", + "WorkDoneProgressEnd.kind", + "WorkDoneProgressReport.kind", + "WorkspaceApplyEditRequest.jsonrpc", + "WorkspaceApplyEditRequest.method", + "WorkspaceApplyEditResponse.jsonrpc", + "WorkspaceApplyEditResponse.result", + "WorkspaceCodeLensRefreshRequest.jsonrpc", + "WorkspaceCodeLensRefreshRequest.method", + "WorkspaceCodeLensRefreshResponse.jsonrpc", + "WorkspaceCodeLensRefreshResponse.result", + "WorkspaceConfigurationRequest.jsonrpc", + "WorkspaceConfigurationRequest.method", + "WorkspaceConfigurationResponse.jsonrpc", + "WorkspaceConfigurationResponse.result", + "WorkspaceDiagnosticRefreshRequest.jsonrpc", + "WorkspaceDiagnosticRefreshRequest.method", + "WorkspaceDiagnosticRefreshResponse.jsonrpc", + "WorkspaceDiagnosticRefreshResponse.result", + "WorkspaceDiagnosticRequest.jsonrpc", + "WorkspaceDiagnosticRequest.method", + "WorkspaceDiagnosticResponse.jsonrpc", + "WorkspaceDiagnosticResponse.result", + "WorkspaceDidChangeConfigurationNotification.jsonrpc", + "WorkspaceDidChangeConfigurationNotification.method", + "WorkspaceDidChangeWatchedFilesNotification.jsonrpc", + "WorkspaceDidChangeWatchedFilesNotification.method", + "WorkspaceDidChangeWorkspaceFoldersNotification.jsonrpc", + "WorkspaceDidChangeWorkspaceFoldersNotification.method", + "WorkspaceDidCreateFilesNotification.jsonrpc", + "WorkspaceDidCreateFilesNotification.method", + "WorkspaceDidDeleteFilesNotification.jsonrpc", + "WorkspaceDidDeleteFilesNotification.method", + "WorkspaceDidRenameFilesNotification.jsonrpc", + "WorkspaceDidRenameFilesNotification.method", + "WorkspaceExecuteCommandRequest.jsonrpc", + "WorkspaceExecuteCommandRequest.method", + "WorkspaceExecuteCommandResponse.jsonrpc", + "WorkspaceExecuteCommandResponse.result", + "WorkspaceFoldersInitializeParams.workspace_folders", + "WorkspaceFoldingRangeRefreshRequest.jsonrpc", + "WorkspaceFoldingRangeRefreshRequest.method", + "WorkspaceFoldingRangeRefreshResponse.jsonrpc", + "WorkspaceFoldingRangeRefreshResponse.result", + "WorkspaceFullDocumentDiagnosticReport.kind", + "WorkspaceFullDocumentDiagnosticReport.version", + "WorkspaceInlayHintRefreshRequest.jsonrpc", + "WorkspaceInlayHintRefreshRequest.method", + "WorkspaceInlayHintRefreshResponse.jsonrpc", + "WorkspaceInlayHintRefreshResponse.result", + "WorkspaceInlineValueRefreshRequest.jsonrpc", + "WorkspaceInlineValueRefreshRequest.method", + "WorkspaceInlineValueRefreshResponse.jsonrpc", + "WorkspaceInlineValueRefreshResponse.result", + "WorkspaceSemanticTokensRefreshRequest.jsonrpc", + "WorkspaceSemanticTokensRefreshRequest.method", + "WorkspaceSemanticTokensRefreshResponse.jsonrpc", + "WorkspaceSemanticTokensRefreshResponse.result", + "WorkspaceSymbolRequest.jsonrpc", + "WorkspaceSymbolRequest.method", + "WorkspaceSymbolResolveRequest.jsonrpc", + "WorkspaceSymbolResolveRequest.method", + "WorkspaceSymbolResolveResponse.jsonrpc", + "WorkspaceSymbolResolveResponse.result", + "WorkspaceSymbolResponse.jsonrpc", + "WorkspaceSymbolResponse.result", + "WorkspaceUnchangedDocumentDiagnosticReport.kind", + "WorkspaceUnchangedDocumentDiagnosticReport.version", + "WorkspaceWillCreateFilesRequest.jsonrpc", + "WorkspaceWillCreateFilesRequest.method", + "WorkspaceWillCreateFilesResponse.jsonrpc", + "WorkspaceWillCreateFilesResponse.result", + "WorkspaceWillDeleteFilesRequest.jsonrpc", + "WorkspaceWillDeleteFilesRequest.method", + "WorkspaceWillDeleteFilesResponse.jsonrpc", + "WorkspaceWillDeleteFilesResponse.result", + "WorkspaceWillRenameFilesRequest.jsonrpc", + "WorkspaceWillRenameFilesRequest.method", + "WorkspaceWillRenameFilesResponse.jsonrpc", + "WorkspaceWillRenameFilesResponse.result", + "WorkspaceWorkspaceFoldersRequest.jsonrpc", + "WorkspaceWorkspaceFoldersRequest.method", + "WorkspaceWorkspaceFoldersResponse.jsonrpc", + "WorkspaceWorkspaceFoldersResponse.result", + "_InitializeParams.process_id", + "_InitializeParams.root_path", + "_InitializeParams.root_uri", +] + + +def is_special_property(cls: type, property_name: str) -> bool: + """Returns true if the class or its properties require special handling. + Example: + Consider RenameRegistrationOptions + * document_selector property: + When you set `document_selector` to None in python it has to be preserved when + serializing it. Since the serialized JSON value `{"document_selector": null}` + means use the Clients document selector. Omitting it might throw error. + * prepare_provider property + This property does NOT need special handling, since omitting it or using + `{"prepare_provider": null}` in JSON has the same meaning. + """ + qualified_name = f"{cls.__name__}.{property_name}" + return qualified_name in _SPECIAL_PROPERTIES + + +ALL_TYPES_MAP: Dict[str, Union[type, object]] = { + "AnnotatedTextEdit": AnnotatedTextEdit, + "ApplyWorkspaceEditParams": ApplyWorkspaceEditParams, + "ApplyWorkspaceEditResult": ApplyWorkspaceEditResult, + "BaseSymbolInformation": BaseSymbolInformation, + "CallHierarchyClientCapabilities": CallHierarchyClientCapabilities, + "CallHierarchyIncomingCall": CallHierarchyIncomingCall, + "CallHierarchyIncomingCallsParams": CallHierarchyIncomingCallsParams, + "CallHierarchyIncomingCallsRequest": CallHierarchyIncomingCallsRequest, + "CallHierarchyIncomingCallsResponse": CallHierarchyIncomingCallsResponse, + "CallHierarchyItem": CallHierarchyItem, + "CallHierarchyOptions": CallHierarchyOptions, + "CallHierarchyOutgoingCall": CallHierarchyOutgoingCall, + "CallHierarchyOutgoingCallsParams": CallHierarchyOutgoingCallsParams, + "CallHierarchyOutgoingCallsRequest": CallHierarchyOutgoingCallsRequest, + "CallHierarchyOutgoingCallsResponse": CallHierarchyOutgoingCallsResponse, + "CallHierarchyPrepareParams": CallHierarchyPrepareParams, + "CallHierarchyRegistrationOptions": CallHierarchyRegistrationOptions, + "CancelParams": CancelParams, + "CancelRequestNotification": CancelRequestNotification, + "ChangeAnnotation": ChangeAnnotation, + "ChangeAnnotationIdentifier": ChangeAnnotationIdentifier, + "ClientCapabilities": ClientCapabilities, + "ClientRegisterCapabilityRequest": ClientRegisterCapabilityRequest, + "ClientRegisterCapabilityResponse": ClientRegisterCapabilityResponse, + "ClientUnregisterCapabilityRequest": ClientUnregisterCapabilityRequest, + "ClientUnregisterCapabilityResponse": ClientUnregisterCapabilityResponse, + "CodeAction": CodeAction, + "CodeActionClientCapabilities": CodeActionClientCapabilities, + "CodeActionClientCapabilitiesCodeActionLiteralSupportType": CodeActionClientCapabilitiesCodeActionLiteralSupportType, + "CodeActionClientCapabilitiesCodeActionLiteralSupportTypeCodeActionKindType": CodeActionClientCapabilitiesCodeActionLiteralSupportTypeCodeActionKindType, + "CodeActionClientCapabilitiesResolveSupportType": CodeActionClientCapabilitiesResolveSupportType, + "CodeActionContext": CodeActionContext, + "CodeActionDisabledType": CodeActionDisabledType, + "CodeActionKind": CodeActionKind, + "CodeActionOptions": CodeActionOptions, + "CodeActionParams": CodeActionParams, + "CodeActionRegistrationOptions": CodeActionRegistrationOptions, + "CodeActionResolveRequest": CodeActionResolveRequest, + "CodeActionResolveResponse": CodeActionResolveResponse, + "CodeActionTriggerKind": CodeActionTriggerKind, + "CodeDescription": CodeDescription, + "CodeLens": CodeLens, + "CodeLensClientCapabilities": CodeLensClientCapabilities, + "CodeLensOptions": CodeLensOptions, + "CodeLensParams": CodeLensParams, + "CodeLensRegistrationOptions": CodeLensRegistrationOptions, + "CodeLensResolveRequest": CodeLensResolveRequest, + "CodeLensResolveResponse": CodeLensResolveResponse, + "CodeLensWorkspaceClientCapabilities": CodeLensWorkspaceClientCapabilities, + "Color": Color, + "ColorInformation": ColorInformation, + "ColorPresentation": ColorPresentation, + "ColorPresentationParams": ColorPresentationParams, + "Command": Command, + "CompletionClientCapabilities": CompletionClientCapabilities, + "CompletionClientCapabilitiesCompletionItemKindType": CompletionClientCapabilitiesCompletionItemKindType, + "CompletionClientCapabilitiesCompletionItemType": CompletionClientCapabilitiesCompletionItemType, + "CompletionClientCapabilitiesCompletionItemTypeInsertTextModeSupportType": CompletionClientCapabilitiesCompletionItemTypeInsertTextModeSupportType, + "CompletionClientCapabilitiesCompletionItemTypeResolveSupportType": CompletionClientCapabilitiesCompletionItemTypeResolveSupportType, + "CompletionClientCapabilitiesCompletionItemTypeTagSupportType": CompletionClientCapabilitiesCompletionItemTypeTagSupportType, + "CompletionClientCapabilitiesCompletionListType": CompletionClientCapabilitiesCompletionListType, + "CompletionContext": CompletionContext, + "CompletionItem": CompletionItem, + "CompletionItemKind": CompletionItemKind, + "CompletionItemLabelDetails": CompletionItemLabelDetails, + "CompletionItemResolveRequest": CompletionItemResolveRequest, + "CompletionItemResolveResponse": CompletionItemResolveResponse, + "CompletionItemTag": CompletionItemTag, + "CompletionList": CompletionList, + "CompletionListItemDefaultsType": CompletionListItemDefaultsType, + "CompletionListItemDefaultsTypeEditRangeType1": CompletionListItemDefaultsTypeEditRangeType1, + "CompletionOptions": CompletionOptions, + "CompletionOptionsCompletionItemType": CompletionOptionsCompletionItemType, + "CompletionParams": CompletionParams, + "CompletionRegistrationOptions": CompletionRegistrationOptions, + "CompletionRegistrationOptionsCompletionItemType": CompletionRegistrationOptionsCompletionItemType, + "CompletionTriggerKind": CompletionTriggerKind, + "ConfigurationItem": ConfigurationItem, + "ConfigurationParams": ConfigurationParams, + "CreateFile": CreateFile, + "CreateFileOptions": CreateFileOptions, + "CreateFilesParams": CreateFilesParams, + "Declaration": Declaration, + "DeclarationClientCapabilities": DeclarationClientCapabilities, + "DeclarationLink": DeclarationLink, + "DeclarationOptions": DeclarationOptions, + "DeclarationParams": DeclarationParams, + "DeclarationRegistrationOptions": DeclarationRegistrationOptions, + "Definition": Definition, + "DefinitionClientCapabilities": DefinitionClientCapabilities, + "DefinitionLink": DefinitionLink, + "DefinitionOptions": DefinitionOptions, + "DefinitionParams": DefinitionParams, + "DefinitionRegistrationOptions": DefinitionRegistrationOptions, + "DeleteFile": DeleteFile, + "DeleteFileOptions": DeleteFileOptions, + "DeleteFilesParams": DeleteFilesParams, + "Diagnostic": Diagnostic, + "DiagnosticClientCapabilities": DiagnosticClientCapabilities, + "DiagnosticOptions": DiagnosticOptions, + "DiagnosticRegistrationOptions": DiagnosticRegistrationOptions, + "DiagnosticRelatedInformation": DiagnosticRelatedInformation, + "DiagnosticServerCancellationData": DiagnosticServerCancellationData, + "DiagnosticSeverity": DiagnosticSeverity, + "DiagnosticTag": DiagnosticTag, + "DiagnosticWorkspaceClientCapabilities": DiagnosticWorkspaceClientCapabilities, + "DidChangeConfigurationClientCapabilities": DidChangeConfigurationClientCapabilities, + "DidChangeConfigurationParams": DidChangeConfigurationParams, + "DidChangeConfigurationRegistrationOptions": DidChangeConfigurationRegistrationOptions, + "DidChangeNotebookDocumentParams": DidChangeNotebookDocumentParams, + "DidChangeTextDocumentParams": DidChangeTextDocumentParams, + "DidChangeWatchedFilesClientCapabilities": DidChangeWatchedFilesClientCapabilities, + "DidChangeWatchedFilesParams": DidChangeWatchedFilesParams, + "DidChangeWatchedFilesRegistrationOptions": DidChangeWatchedFilesRegistrationOptions, + "DidChangeWorkspaceFoldersParams": DidChangeWorkspaceFoldersParams, + "DidCloseNotebookDocumentParams": DidCloseNotebookDocumentParams, + "DidCloseTextDocumentParams": DidCloseTextDocumentParams, + "DidOpenNotebookDocumentParams": DidOpenNotebookDocumentParams, + "DidOpenTextDocumentParams": DidOpenTextDocumentParams, + "DidSaveNotebookDocumentParams": DidSaveNotebookDocumentParams, + "DidSaveTextDocumentParams": DidSaveTextDocumentParams, + "DocumentColorClientCapabilities": DocumentColorClientCapabilities, + "DocumentColorOptions": DocumentColorOptions, + "DocumentColorParams": DocumentColorParams, + "DocumentColorRegistrationOptions": DocumentColorRegistrationOptions, + "DocumentDiagnosticParams": DocumentDiagnosticParams, + "DocumentDiagnosticReport": DocumentDiagnosticReport, + "DocumentDiagnosticReportKind": DocumentDiagnosticReportKind, + "DocumentDiagnosticReportPartialResult": DocumentDiagnosticReportPartialResult, + "DocumentFilter": DocumentFilter, + "DocumentFormattingClientCapabilities": DocumentFormattingClientCapabilities, + "DocumentFormattingOptions": DocumentFormattingOptions, + "DocumentFormattingParams": DocumentFormattingParams, + "DocumentFormattingRegistrationOptions": DocumentFormattingRegistrationOptions, + "DocumentHighlight": DocumentHighlight, + "DocumentHighlightClientCapabilities": DocumentHighlightClientCapabilities, + "DocumentHighlightKind": DocumentHighlightKind, + "DocumentHighlightOptions": DocumentHighlightOptions, + "DocumentHighlightParams": DocumentHighlightParams, + "DocumentHighlightRegistrationOptions": DocumentHighlightRegistrationOptions, + "DocumentLink": DocumentLink, + "DocumentLinkClientCapabilities": DocumentLinkClientCapabilities, + "DocumentLinkOptions": DocumentLinkOptions, + "DocumentLinkParams": DocumentLinkParams, + "DocumentLinkRegistrationOptions": DocumentLinkRegistrationOptions, + "DocumentLinkResolveRequest": DocumentLinkResolveRequest, + "DocumentLinkResolveResponse": DocumentLinkResolveResponse, + "DocumentOnTypeFormattingClientCapabilities": DocumentOnTypeFormattingClientCapabilities, + "DocumentOnTypeFormattingOptions": DocumentOnTypeFormattingOptions, + "DocumentOnTypeFormattingParams": DocumentOnTypeFormattingParams, + "DocumentOnTypeFormattingRegistrationOptions": DocumentOnTypeFormattingRegistrationOptions, + "DocumentRangeFormattingClientCapabilities": DocumentRangeFormattingClientCapabilities, + "DocumentRangeFormattingOptions": DocumentRangeFormattingOptions, + "DocumentRangeFormattingParams": DocumentRangeFormattingParams, + "DocumentRangeFormattingRegistrationOptions": DocumentRangeFormattingRegistrationOptions, + "DocumentRangesFormattingParams": DocumentRangesFormattingParams, + "DocumentSelector": DocumentSelector, + "DocumentSymbol": DocumentSymbol, + "DocumentSymbolClientCapabilities": DocumentSymbolClientCapabilities, + "DocumentSymbolClientCapabilitiesSymbolKindType": DocumentSymbolClientCapabilitiesSymbolKindType, + "DocumentSymbolClientCapabilitiesTagSupportType": DocumentSymbolClientCapabilitiesTagSupportType, + "DocumentSymbolOptions": DocumentSymbolOptions, + "DocumentSymbolParams": DocumentSymbolParams, + "DocumentSymbolRegistrationOptions": DocumentSymbolRegistrationOptions, + "ErrorCodes": ErrorCodes, + "ExecuteCommandClientCapabilities": ExecuteCommandClientCapabilities, + "ExecuteCommandOptions": ExecuteCommandOptions, + "ExecuteCommandParams": ExecuteCommandParams, + "ExecuteCommandRegistrationOptions": ExecuteCommandRegistrationOptions, + "ExecutionSummary": ExecutionSummary, + "ExitNotification": ExitNotification, + "FailureHandlingKind": FailureHandlingKind, + "FileChangeType": FileChangeType, + "FileCreate": FileCreate, + "FileDelete": FileDelete, + "FileEvent": FileEvent, + "FileOperationClientCapabilities": FileOperationClientCapabilities, + "FileOperationFilter": FileOperationFilter, + "FileOperationOptions": FileOperationOptions, + "FileOperationPattern": FileOperationPattern, + "FileOperationPatternKind": FileOperationPatternKind, + "FileOperationPatternOptions": FileOperationPatternOptions, + "FileOperationRegistrationOptions": FileOperationRegistrationOptions, + "FileRename": FileRename, + "FileSystemWatcher": FileSystemWatcher, + "FoldingRange": FoldingRange, + "FoldingRangeClientCapabilities": FoldingRangeClientCapabilities, + "FoldingRangeClientCapabilitiesFoldingRangeKindType": FoldingRangeClientCapabilitiesFoldingRangeKindType, + "FoldingRangeClientCapabilitiesFoldingRangeType": FoldingRangeClientCapabilitiesFoldingRangeType, + "FoldingRangeKind": FoldingRangeKind, + "FoldingRangeOptions": FoldingRangeOptions, + "FoldingRangeParams": FoldingRangeParams, + "FoldingRangeRegistrationOptions": FoldingRangeRegistrationOptions, + "FoldingRangeWorkspaceClientCapabilities": FoldingRangeWorkspaceClientCapabilities, + "FormattingOptions": FormattingOptions, + "FullDocumentDiagnosticReport": FullDocumentDiagnosticReport, + "GeneralClientCapabilities": GeneralClientCapabilities, + "GeneralClientCapabilitiesStaleRequestSupportType": GeneralClientCapabilitiesStaleRequestSupportType, + "GlobPattern": GlobPattern, + "Hover": Hover, + "HoverClientCapabilities": HoverClientCapabilities, + "HoverOptions": HoverOptions, + "HoverParams": HoverParams, + "HoverRegistrationOptions": HoverRegistrationOptions, + "ImplementationClientCapabilities": ImplementationClientCapabilities, + "ImplementationOptions": ImplementationOptions, + "ImplementationParams": ImplementationParams, + "ImplementationRegistrationOptions": ImplementationRegistrationOptions, + "InitializeError": InitializeError, + "InitializeParams": InitializeParams, + "InitializeParamsClientInfoType": InitializeParamsClientInfoType, + "InitializeRequest": InitializeRequest, + "InitializeResponse": InitializeResponse, + "InitializeResult": InitializeResult, + "InitializeResultServerInfoType": InitializeResultServerInfoType, + "InitializedNotification": InitializedNotification, + "InitializedParams": InitializedParams, + "InlayHint": InlayHint, + "InlayHintClientCapabilities": InlayHintClientCapabilities, + "InlayHintClientCapabilitiesResolveSupportType": InlayHintClientCapabilitiesResolveSupportType, + "InlayHintKind": InlayHintKind, + "InlayHintLabelPart": InlayHintLabelPart, + "InlayHintOptions": InlayHintOptions, + "InlayHintParams": InlayHintParams, + "InlayHintRegistrationOptions": InlayHintRegistrationOptions, + "InlayHintResolveRequest": InlayHintResolveRequest, + "InlayHintResolveResponse": InlayHintResolveResponse, + "InlayHintWorkspaceClientCapabilities": InlayHintWorkspaceClientCapabilities, + "InlineCompletionClientCapabilities": InlineCompletionClientCapabilities, + "InlineCompletionContext": InlineCompletionContext, + "InlineCompletionItem": InlineCompletionItem, + "InlineCompletionList": InlineCompletionList, + "InlineCompletionOptions": InlineCompletionOptions, + "InlineCompletionParams": InlineCompletionParams, + "InlineCompletionRegistrationOptions": InlineCompletionRegistrationOptions, + "InlineCompletionTriggerKind": InlineCompletionTriggerKind, + "InlineValue": InlineValue, + "InlineValueClientCapabilities": InlineValueClientCapabilities, + "InlineValueContext": InlineValueContext, + "InlineValueEvaluatableExpression": InlineValueEvaluatableExpression, + "InlineValueOptions": InlineValueOptions, + "InlineValueParams": InlineValueParams, + "InlineValueRegistrationOptions": InlineValueRegistrationOptions, + "InlineValueText": InlineValueText, + "InlineValueVariableLookup": InlineValueVariableLookup, + "InlineValueWorkspaceClientCapabilities": InlineValueWorkspaceClientCapabilities, + "InsertReplaceEdit": InsertReplaceEdit, + "InsertTextFormat": InsertTextFormat, + "InsertTextMode": InsertTextMode, + "LSPAny": LSPAny, + "LSPArray": LSPArray, + "LSPErrorCodes": LSPErrorCodes, + "LSPObject": LSPObject, + "LinkedEditingRangeClientCapabilities": LinkedEditingRangeClientCapabilities, + "LinkedEditingRangeOptions": LinkedEditingRangeOptions, + "LinkedEditingRangeParams": LinkedEditingRangeParams, + "LinkedEditingRangeRegistrationOptions": LinkedEditingRangeRegistrationOptions, + "LinkedEditingRanges": LinkedEditingRanges, + "Location": Location, + "LocationLink": LocationLink, + "LogMessageParams": LogMessageParams, + "LogTraceNotification": LogTraceNotification, + "LogTraceParams": LogTraceParams, + "MarkdownClientCapabilities": MarkdownClientCapabilities, + "MarkedString": MarkedString, + "MarkedString_Type1": MarkedString_Type1, + "MarkupContent": MarkupContent, + "MarkupKind": MarkupKind, + "MessageActionItem": MessageActionItem, + "MessageDirection": MessageDirection, + "MessageType": MessageType, + "Moniker": Moniker, + "MonikerClientCapabilities": MonikerClientCapabilities, + "MonikerKind": MonikerKind, + "MonikerOptions": MonikerOptions, + "MonikerParams": MonikerParams, + "MonikerRegistrationOptions": MonikerRegistrationOptions, + "NotebookCell": NotebookCell, + "NotebookCellArrayChange": NotebookCellArrayChange, + "NotebookCellKind": NotebookCellKind, + "NotebookCellTextDocumentFilter": NotebookCellTextDocumentFilter, + "NotebookDocument": NotebookDocument, + "NotebookDocumentChangeEvent": NotebookDocumentChangeEvent, + "NotebookDocumentChangeEventCellsType": NotebookDocumentChangeEventCellsType, + "NotebookDocumentChangeEventCellsTypeStructureType": NotebookDocumentChangeEventCellsTypeStructureType, + "NotebookDocumentChangeEventCellsTypeTextContentType": NotebookDocumentChangeEventCellsTypeTextContentType, + "NotebookDocumentClientCapabilities": NotebookDocumentClientCapabilities, + "NotebookDocumentDidChangeNotification": NotebookDocumentDidChangeNotification, + "NotebookDocumentDidCloseNotification": NotebookDocumentDidCloseNotification, + "NotebookDocumentDidOpenNotification": NotebookDocumentDidOpenNotification, + "NotebookDocumentDidSaveNotification": NotebookDocumentDidSaveNotification, + "NotebookDocumentFilter": NotebookDocumentFilter, + "NotebookDocumentFilter_Type1": NotebookDocumentFilter_Type1, + "NotebookDocumentFilter_Type2": NotebookDocumentFilter_Type2, + "NotebookDocumentFilter_Type3": NotebookDocumentFilter_Type3, + "NotebookDocumentIdentifier": NotebookDocumentIdentifier, + "NotebookDocumentSyncClientCapabilities": NotebookDocumentSyncClientCapabilities, + "NotebookDocumentSyncOptions": NotebookDocumentSyncOptions, + "NotebookDocumentSyncOptionsNotebookSelectorType1": NotebookDocumentSyncOptionsNotebookSelectorType1, + "NotebookDocumentSyncOptionsNotebookSelectorType1CellsType": NotebookDocumentSyncOptionsNotebookSelectorType1CellsType, + "NotebookDocumentSyncOptionsNotebookSelectorType2": NotebookDocumentSyncOptionsNotebookSelectorType2, + "NotebookDocumentSyncOptionsNotebookSelectorType2CellsType": NotebookDocumentSyncOptionsNotebookSelectorType2CellsType, + "NotebookDocumentSyncRegistrationOptions": NotebookDocumentSyncRegistrationOptions, + "NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1": NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1, + "NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1CellsType": NotebookDocumentSyncRegistrationOptionsNotebookSelectorType1CellsType, + "NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2": NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2, + "NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2CellsType": NotebookDocumentSyncRegistrationOptionsNotebookSelectorType2CellsType, + "OptionalVersionedTextDocumentIdentifier": OptionalVersionedTextDocumentIdentifier, + "ParameterInformation": ParameterInformation, + "PartialResultParams": PartialResultParams, + "Pattern": Pattern, + "Position": Position, + "PositionEncodingKind": PositionEncodingKind, + "PrepareRenameParams": PrepareRenameParams, + "PrepareRenameResult": PrepareRenameResult, + "PrepareRenameResult_Type1": PrepareRenameResult_Type1, + "PrepareRenameResult_Type2": PrepareRenameResult_Type2, + "PrepareSupportDefaultBehavior": PrepareSupportDefaultBehavior, + "PreviousResultId": PreviousResultId, + "ProgressNotification": ProgressNotification, + "ProgressParams": ProgressParams, + "ProgressToken": ProgressToken, + "PublishDiagnosticsClientCapabilities": PublishDiagnosticsClientCapabilities, + "PublishDiagnosticsClientCapabilitiesTagSupportType": PublishDiagnosticsClientCapabilitiesTagSupportType, + "PublishDiagnosticsParams": PublishDiagnosticsParams, + "Range": Range, + "ReferenceClientCapabilities": ReferenceClientCapabilities, + "ReferenceContext": ReferenceContext, + "ReferenceOptions": ReferenceOptions, + "ReferenceParams": ReferenceParams, + "ReferenceRegistrationOptions": ReferenceRegistrationOptions, + "Registration": Registration, + "RegistrationParams": RegistrationParams, + "RegularExpressionsClientCapabilities": RegularExpressionsClientCapabilities, + "RelatedFullDocumentDiagnosticReport": RelatedFullDocumentDiagnosticReport, + "RelatedUnchangedDocumentDiagnosticReport": RelatedUnchangedDocumentDiagnosticReport, + "RelativePattern": RelativePattern, + "RenameClientCapabilities": RenameClientCapabilities, + "RenameFile": RenameFile, + "RenameFileOptions": RenameFileOptions, + "RenameFilesParams": RenameFilesParams, + "RenameOptions": RenameOptions, + "RenameParams": RenameParams, + "RenameRegistrationOptions": RenameRegistrationOptions, + "ResourceOperation": ResourceOperation, + "ResourceOperationKind": ResourceOperationKind, + "ResponseError": ResponseError, + "ResponseErrorMessage": ResponseErrorMessage, + "SaveOptions": SaveOptions, + "SelectedCompletionInfo": SelectedCompletionInfo, + "SelectionRange": SelectionRange, + "SelectionRangeClientCapabilities": SelectionRangeClientCapabilities, + "SelectionRangeOptions": SelectionRangeOptions, + "SelectionRangeParams": SelectionRangeParams, + "SelectionRangeRegistrationOptions": SelectionRangeRegistrationOptions, + "SemanticTokenModifiers": SemanticTokenModifiers, + "SemanticTokenTypes": SemanticTokenTypes, + "SemanticTokens": SemanticTokens, + "SemanticTokensClientCapabilities": SemanticTokensClientCapabilities, + "SemanticTokensClientCapabilitiesRequestsType": SemanticTokensClientCapabilitiesRequestsType, + "SemanticTokensClientCapabilitiesRequestsTypeFullType1": SemanticTokensClientCapabilitiesRequestsTypeFullType1, + "SemanticTokensDelta": SemanticTokensDelta, + "SemanticTokensDeltaParams": SemanticTokensDeltaParams, + "SemanticTokensDeltaPartialResult": SemanticTokensDeltaPartialResult, + "SemanticTokensEdit": SemanticTokensEdit, + "SemanticTokensLegend": SemanticTokensLegend, + "SemanticTokensOptions": SemanticTokensOptions, + "SemanticTokensOptionsFullType1": SemanticTokensOptionsFullType1, + "SemanticTokensParams": SemanticTokensParams, + "SemanticTokensPartialResult": SemanticTokensPartialResult, + "SemanticTokensRangeParams": SemanticTokensRangeParams, + "SemanticTokensRegistrationOptions": SemanticTokensRegistrationOptions, + "SemanticTokensRegistrationOptionsFullType1": SemanticTokensRegistrationOptionsFullType1, + "SemanticTokensWorkspaceClientCapabilities": SemanticTokensWorkspaceClientCapabilities, + "ServerCapabilities": ServerCapabilities, + "ServerCapabilitiesWorkspaceType": ServerCapabilitiesWorkspaceType, + "SetTraceNotification": SetTraceNotification, + "SetTraceParams": SetTraceParams, + "ShowDocumentClientCapabilities": ShowDocumentClientCapabilities, + "ShowDocumentParams": ShowDocumentParams, + "ShowDocumentResult": ShowDocumentResult, + "ShowMessageParams": ShowMessageParams, + "ShowMessageRequestClientCapabilities": ShowMessageRequestClientCapabilities, + "ShowMessageRequestClientCapabilitiesMessageActionItemType": ShowMessageRequestClientCapabilitiesMessageActionItemType, + "ShowMessageRequestParams": ShowMessageRequestParams, + "ShutdownRequest": ShutdownRequest, + "ShutdownResponse": ShutdownResponse, + "SignatureHelp": SignatureHelp, + "SignatureHelpClientCapabilities": SignatureHelpClientCapabilities, + "SignatureHelpClientCapabilitiesSignatureInformationType": SignatureHelpClientCapabilitiesSignatureInformationType, + "SignatureHelpClientCapabilitiesSignatureInformationTypeParameterInformationType": SignatureHelpClientCapabilitiesSignatureInformationTypeParameterInformationType, + "SignatureHelpContext": SignatureHelpContext, + "SignatureHelpOptions": SignatureHelpOptions, + "SignatureHelpParams": SignatureHelpParams, + "SignatureHelpRegistrationOptions": SignatureHelpRegistrationOptions, + "SignatureHelpTriggerKind": SignatureHelpTriggerKind, + "SignatureInformation": SignatureInformation, + "StaticRegistrationOptions": StaticRegistrationOptions, + "StringValue": StringValue, + "SymbolInformation": SymbolInformation, + "SymbolKind": SymbolKind, + "SymbolTag": SymbolTag, + "TelemetryEventNotification": TelemetryEventNotification, + "TextDocumentChangeRegistrationOptions": TextDocumentChangeRegistrationOptions, + "TextDocumentClientCapabilities": TextDocumentClientCapabilities, + "TextDocumentCodeActionRequest": TextDocumentCodeActionRequest, + "TextDocumentCodeActionResponse": TextDocumentCodeActionResponse, + "TextDocumentCodeLensRequest": TextDocumentCodeLensRequest, + "TextDocumentCodeLensResponse": TextDocumentCodeLensResponse, + "TextDocumentColorPresentationOptions": TextDocumentColorPresentationOptions, + "TextDocumentColorPresentationRequest": TextDocumentColorPresentationRequest, + "TextDocumentColorPresentationResponse": TextDocumentColorPresentationResponse, + "TextDocumentCompletionRequest": TextDocumentCompletionRequest, + "TextDocumentCompletionResponse": TextDocumentCompletionResponse, + "TextDocumentContentChangeEvent": TextDocumentContentChangeEvent, + "TextDocumentContentChangeEvent_Type1": TextDocumentContentChangeEvent_Type1, + "TextDocumentContentChangeEvent_Type2": TextDocumentContentChangeEvent_Type2, + "TextDocumentDeclarationRequest": TextDocumentDeclarationRequest, + "TextDocumentDeclarationResponse": TextDocumentDeclarationResponse, + "TextDocumentDefinitionRequest": TextDocumentDefinitionRequest, + "TextDocumentDefinitionResponse": TextDocumentDefinitionResponse, + "TextDocumentDiagnosticRequest": TextDocumentDiagnosticRequest, + "TextDocumentDiagnosticResponse": TextDocumentDiagnosticResponse, + "TextDocumentDidChangeNotification": TextDocumentDidChangeNotification, + "TextDocumentDidCloseNotification": TextDocumentDidCloseNotification, + "TextDocumentDidOpenNotification": TextDocumentDidOpenNotification, + "TextDocumentDidSaveNotification": TextDocumentDidSaveNotification, + "TextDocumentDocumentColorRequest": TextDocumentDocumentColorRequest, + "TextDocumentDocumentColorResponse": TextDocumentDocumentColorResponse, + "TextDocumentDocumentHighlightRequest": TextDocumentDocumentHighlightRequest, + "TextDocumentDocumentHighlightResponse": TextDocumentDocumentHighlightResponse, + "TextDocumentDocumentLinkRequest": TextDocumentDocumentLinkRequest, + "TextDocumentDocumentLinkResponse": TextDocumentDocumentLinkResponse, + "TextDocumentDocumentSymbolRequest": TextDocumentDocumentSymbolRequest, + "TextDocumentDocumentSymbolResponse": TextDocumentDocumentSymbolResponse, + "TextDocumentEdit": TextDocumentEdit, + "TextDocumentFilter": TextDocumentFilter, + "TextDocumentFilter_Type1": TextDocumentFilter_Type1, + "TextDocumentFilter_Type2": TextDocumentFilter_Type2, + "TextDocumentFilter_Type3": TextDocumentFilter_Type3, + "TextDocumentFoldingRangeRequest": TextDocumentFoldingRangeRequest, + "TextDocumentFoldingRangeResponse": TextDocumentFoldingRangeResponse, + "TextDocumentFormattingRequest": TextDocumentFormattingRequest, + "TextDocumentFormattingResponse": TextDocumentFormattingResponse, + "TextDocumentHoverRequest": TextDocumentHoverRequest, + "TextDocumentHoverResponse": TextDocumentHoverResponse, + "TextDocumentIdentifier": TextDocumentIdentifier, + "TextDocumentImplementationRequest": TextDocumentImplementationRequest, + "TextDocumentImplementationResponse": TextDocumentImplementationResponse, + "TextDocumentInlayHintRequest": TextDocumentInlayHintRequest, + "TextDocumentInlayHintResponse": TextDocumentInlayHintResponse, + "TextDocumentInlineCompletionRequest": TextDocumentInlineCompletionRequest, + "TextDocumentInlineCompletionResponse": TextDocumentInlineCompletionResponse, + "TextDocumentInlineValueRequest": TextDocumentInlineValueRequest, + "TextDocumentInlineValueResponse": TextDocumentInlineValueResponse, + "TextDocumentItem": TextDocumentItem, + "TextDocumentLinkedEditingRangeRequest": TextDocumentLinkedEditingRangeRequest, + "TextDocumentLinkedEditingRangeResponse": TextDocumentLinkedEditingRangeResponse, + "TextDocumentMonikerRequest": TextDocumentMonikerRequest, + "TextDocumentMonikerResponse": TextDocumentMonikerResponse, + "TextDocumentOnTypeFormattingRequest": TextDocumentOnTypeFormattingRequest, + "TextDocumentOnTypeFormattingResponse": TextDocumentOnTypeFormattingResponse, + "TextDocumentPositionParams": TextDocumentPositionParams, + "TextDocumentPrepareCallHierarchyRequest": TextDocumentPrepareCallHierarchyRequest, + "TextDocumentPrepareCallHierarchyResponse": TextDocumentPrepareCallHierarchyResponse, + "TextDocumentPrepareRenameRequest": TextDocumentPrepareRenameRequest, + "TextDocumentPrepareRenameResponse": TextDocumentPrepareRenameResponse, + "TextDocumentPrepareTypeHierarchyRequest": TextDocumentPrepareTypeHierarchyRequest, + "TextDocumentPrepareTypeHierarchyResponse": TextDocumentPrepareTypeHierarchyResponse, + "TextDocumentPublishDiagnosticsNotification": TextDocumentPublishDiagnosticsNotification, + "TextDocumentRangeFormattingRequest": TextDocumentRangeFormattingRequest, + "TextDocumentRangeFormattingResponse": TextDocumentRangeFormattingResponse, + "TextDocumentRangesFormattingRequest": TextDocumentRangesFormattingRequest, + "TextDocumentRangesFormattingResponse": TextDocumentRangesFormattingResponse, + "TextDocumentReferencesRequest": TextDocumentReferencesRequest, + "TextDocumentReferencesResponse": TextDocumentReferencesResponse, + "TextDocumentRegistrationOptions": TextDocumentRegistrationOptions, + "TextDocumentRenameRequest": TextDocumentRenameRequest, + "TextDocumentRenameResponse": TextDocumentRenameResponse, + "TextDocumentSaveReason": TextDocumentSaveReason, + "TextDocumentSaveRegistrationOptions": TextDocumentSaveRegistrationOptions, + "TextDocumentSelectionRangeRequest": TextDocumentSelectionRangeRequest, + "TextDocumentSelectionRangeResponse": TextDocumentSelectionRangeResponse, + "TextDocumentSemanticTokensFullDeltaRequest": TextDocumentSemanticTokensFullDeltaRequest, + "TextDocumentSemanticTokensFullDeltaResponse": TextDocumentSemanticTokensFullDeltaResponse, + "TextDocumentSemanticTokensFullRequest": TextDocumentSemanticTokensFullRequest, + "TextDocumentSemanticTokensFullResponse": TextDocumentSemanticTokensFullResponse, + "TextDocumentSemanticTokensRangeRequest": TextDocumentSemanticTokensRangeRequest, + "TextDocumentSemanticTokensRangeResponse": TextDocumentSemanticTokensRangeResponse, + "TextDocumentSignatureHelpRequest": TextDocumentSignatureHelpRequest, + "TextDocumentSignatureHelpResponse": TextDocumentSignatureHelpResponse, + "TextDocumentSyncClientCapabilities": TextDocumentSyncClientCapabilities, + "TextDocumentSyncKind": TextDocumentSyncKind, + "TextDocumentSyncOptions": TextDocumentSyncOptions, + "TextDocumentTypeDefinitionRequest": TextDocumentTypeDefinitionRequest, + "TextDocumentTypeDefinitionResponse": TextDocumentTypeDefinitionResponse, + "TextDocumentWillSaveNotification": TextDocumentWillSaveNotification, + "TextDocumentWillSaveWaitUntilRequest": TextDocumentWillSaveWaitUntilRequest, + "TextDocumentWillSaveWaitUntilResponse": TextDocumentWillSaveWaitUntilResponse, + "TextEdit": TextEdit, + "TokenFormat": TokenFormat, + "TraceValues": TraceValues, + "TypeDefinitionClientCapabilities": TypeDefinitionClientCapabilities, + "TypeDefinitionOptions": TypeDefinitionOptions, + "TypeDefinitionParams": TypeDefinitionParams, + "TypeDefinitionRegistrationOptions": TypeDefinitionRegistrationOptions, + "TypeHierarchyClientCapabilities": TypeHierarchyClientCapabilities, + "TypeHierarchyItem": TypeHierarchyItem, + "TypeHierarchyOptions": TypeHierarchyOptions, + "TypeHierarchyPrepareParams": TypeHierarchyPrepareParams, + "TypeHierarchyRegistrationOptions": TypeHierarchyRegistrationOptions, + "TypeHierarchySubtypesParams": TypeHierarchySubtypesParams, + "TypeHierarchySubtypesRequest": TypeHierarchySubtypesRequest, + "TypeHierarchySubtypesResponse": TypeHierarchySubtypesResponse, + "TypeHierarchySupertypesParams": TypeHierarchySupertypesParams, + "TypeHierarchySupertypesRequest": TypeHierarchySupertypesRequest, + "TypeHierarchySupertypesResponse": TypeHierarchySupertypesResponse, + "UnchangedDocumentDiagnosticReport": UnchangedDocumentDiagnosticReport, + "UniquenessLevel": UniquenessLevel, + "Unregistration": Unregistration, + "UnregistrationParams": UnregistrationParams, + "VersionedNotebookDocumentIdentifier": VersionedNotebookDocumentIdentifier, + "VersionedTextDocumentIdentifier": VersionedTextDocumentIdentifier, + "WatchKind": WatchKind, + "WillSaveTextDocumentParams": WillSaveTextDocumentParams, + "WindowClientCapabilities": WindowClientCapabilities, + "WindowLogMessageNotification": WindowLogMessageNotification, + "WindowShowDocumentRequest": WindowShowDocumentRequest, + "WindowShowDocumentResponse": WindowShowDocumentResponse, + "WindowShowMessageNotification": WindowShowMessageNotification, + "WindowShowMessageRequestRequest": WindowShowMessageRequestRequest, + "WindowShowMessageRequestResponse": WindowShowMessageRequestResponse, + "WindowWorkDoneProgressCancelNotification": WindowWorkDoneProgressCancelNotification, + "WindowWorkDoneProgressCreateRequest": WindowWorkDoneProgressCreateRequest, + "WindowWorkDoneProgressCreateResponse": WindowWorkDoneProgressCreateResponse, + "WorkDoneProgressBegin": WorkDoneProgressBegin, + "WorkDoneProgressCancelParams": WorkDoneProgressCancelParams, + "WorkDoneProgressCreateParams": WorkDoneProgressCreateParams, + "WorkDoneProgressEnd": WorkDoneProgressEnd, + "WorkDoneProgressOptions": WorkDoneProgressOptions, + "WorkDoneProgressParams": WorkDoneProgressParams, + "WorkDoneProgressReport": WorkDoneProgressReport, + "WorkspaceApplyEditRequest": WorkspaceApplyEditRequest, + "WorkspaceApplyEditResponse": WorkspaceApplyEditResponse, + "WorkspaceClientCapabilities": WorkspaceClientCapabilities, + "WorkspaceCodeLensRefreshRequest": WorkspaceCodeLensRefreshRequest, + "WorkspaceCodeLensRefreshResponse": WorkspaceCodeLensRefreshResponse, + "WorkspaceConfigurationParams": WorkspaceConfigurationParams, + "WorkspaceConfigurationRequest": WorkspaceConfigurationRequest, + "WorkspaceConfigurationResponse": WorkspaceConfigurationResponse, + "WorkspaceDiagnosticParams": WorkspaceDiagnosticParams, + "WorkspaceDiagnosticRefreshRequest": WorkspaceDiagnosticRefreshRequest, + "WorkspaceDiagnosticRefreshResponse": WorkspaceDiagnosticRefreshResponse, + "WorkspaceDiagnosticReport": WorkspaceDiagnosticReport, + "WorkspaceDiagnosticReportPartialResult": WorkspaceDiagnosticReportPartialResult, + "WorkspaceDiagnosticRequest": WorkspaceDiagnosticRequest, + "WorkspaceDiagnosticResponse": WorkspaceDiagnosticResponse, + "WorkspaceDidChangeConfigurationNotification": WorkspaceDidChangeConfigurationNotification, + "WorkspaceDidChangeWatchedFilesNotification": WorkspaceDidChangeWatchedFilesNotification, + "WorkspaceDidChangeWorkspaceFoldersNotification": WorkspaceDidChangeWorkspaceFoldersNotification, + "WorkspaceDidCreateFilesNotification": WorkspaceDidCreateFilesNotification, + "WorkspaceDidDeleteFilesNotification": WorkspaceDidDeleteFilesNotification, + "WorkspaceDidRenameFilesNotification": WorkspaceDidRenameFilesNotification, + "WorkspaceDocumentDiagnosticReport": WorkspaceDocumentDiagnosticReport, + "WorkspaceEdit": WorkspaceEdit, + "WorkspaceEditClientCapabilities": WorkspaceEditClientCapabilities, + "WorkspaceEditClientCapabilitiesChangeAnnotationSupportType": WorkspaceEditClientCapabilitiesChangeAnnotationSupportType, + "WorkspaceExecuteCommandRequest": WorkspaceExecuteCommandRequest, + "WorkspaceExecuteCommandResponse": WorkspaceExecuteCommandResponse, + "WorkspaceFolder": WorkspaceFolder, + "WorkspaceFoldersChangeEvent": WorkspaceFoldersChangeEvent, + "WorkspaceFoldersInitializeParams": WorkspaceFoldersInitializeParams, + "WorkspaceFoldersServerCapabilities": WorkspaceFoldersServerCapabilities, + "WorkspaceFoldingRangeRefreshRequest": WorkspaceFoldingRangeRefreshRequest, + "WorkspaceFoldingRangeRefreshResponse": WorkspaceFoldingRangeRefreshResponse, + "WorkspaceFullDocumentDiagnosticReport": WorkspaceFullDocumentDiagnosticReport, + "WorkspaceInlayHintRefreshRequest": WorkspaceInlayHintRefreshRequest, + "WorkspaceInlayHintRefreshResponse": WorkspaceInlayHintRefreshResponse, + "WorkspaceInlineValueRefreshRequest": WorkspaceInlineValueRefreshRequest, + "WorkspaceInlineValueRefreshResponse": WorkspaceInlineValueRefreshResponse, + "WorkspaceSemanticTokensRefreshRequest": WorkspaceSemanticTokensRefreshRequest, + "WorkspaceSemanticTokensRefreshResponse": WorkspaceSemanticTokensRefreshResponse, + "WorkspaceSymbol": WorkspaceSymbol, + "WorkspaceSymbolClientCapabilities": WorkspaceSymbolClientCapabilities, + "WorkspaceSymbolClientCapabilitiesResolveSupportType": WorkspaceSymbolClientCapabilitiesResolveSupportType, + "WorkspaceSymbolClientCapabilitiesSymbolKindType": WorkspaceSymbolClientCapabilitiesSymbolKindType, + "WorkspaceSymbolClientCapabilitiesTagSupportType": WorkspaceSymbolClientCapabilitiesTagSupportType, + "WorkspaceSymbolLocationType1": WorkspaceSymbolLocationType1, + "WorkspaceSymbolOptions": WorkspaceSymbolOptions, + "WorkspaceSymbolParams": WorkspaceSymbolParams, + "WorkspaceSymbolRegistrationOptions": WorkspaceSymbolRegistrationOptions, + "WorkspaceSymbolRequest": WorkspaceSymbolRequest, + "WorkspaceSymbolResolveRequest": WorkspaceSymbolResolveRequest, + "WorkspaceSymbolResolveResponse": WorkspaceSymbolResolveResponse, + "WorkspaceSymbolResponse": WorkspaceSymbolResponse, + "WorkspaceUnchangedDocumentDiagnosticReport": WorkspaceUnchangedDocumentDiagnosticReport, + "WorkspaceWillCreateFilesRequest": WorkspaceWillCreateFilesRequest, + "WorkspaceWillCreateFilesResponse": WorkspaceWillCreateFilesResponse, + "WorkspaceWillDeleteFilesRequest": WorkspaceWillDeleteFilesRequest, + "WorkspaceWillDeleteFilesResponse": WorkspaceWillDeleteFilesResponse, + "WorkspaceWillRenameFilesRequest": WorkspaceWillRenameFilesRequest, + "WorkspaceWillRenameFilesResponse": WorkspaceWillRenameFilesResponse, + "WorkspaceWorkspaceFoldersRequest": WorkspaceWorkspaceFoldersRequest, + "WorkspaceWorkspaceFoldersResponse": WorkspaceWorkspaceFoldersResponse, + "_InitializeParams": _InitializeParams, +} + +_MESSAGE_DIRECTION: Dict[str, str] = { + # Request methods + CALL_HIERARCHY_INCOMING_CALLS: "clientToServer", + CALL_HIERARCHY_OUTGOING_CALLS: "clientToServer", + CLIENT_REGISTER_CAPABILITY: "serverToClient", + CLIENT_UNREGISTER_CAPABILITY: "serverToClient", + CODE_ACTION_RESOLVE: "clientToServer", + CODE_LENS_RESOLVE: "clientToServer", + COMPLETION_ITEM_RESOLVE: "clientToServer", + DOCUMENT_LINK_RESOLVE: "clientToServer", + INITIALIZE: "clientToServer", + INLAY_HINT_RESOLVE: "clientToServer", + SHUTDOWN: "clientToServer", + TEXT_DOCUMENT_CODE_ACTION: "clientToServer", + TEXT_DOCUMENT_CODE_LENS: "clientToServer", + TEXT_DOCUMENT_COLOR_PRESENTATION: "clientToServer", + TEXT_DOCUMENT_COMPLETION: "clientToServer", + TEXT_DOCUMENT_DECLARATION: "clientToServer", + TEXT_DOCUMENT_DEFINITION: "clientToServer", + TEXT_DOCUMENT_DIAGNOSTIC: "clientToServer", + TEXT_DOCUMENT_DOCUMENT_COLOR: "clientToServer", + TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT: "clientToServer", + TEXT_DOCUMENT_DOCUMENT_LINK: "clientToServer", + TEXT_DOCUMENT_DOCUMENT_SYMBOL: "clientToServer", + TEXT_DOCUMENT_FOLDING_RANGE: "clientToServer", + TEXT_DOCUMENT_FORMATTING: "clientToServer", + TEXT_DOCUMENT_HOVER: "clientToServer", + TEXT_DOCUMENT_IMPLEMENTATION: "clientToServer", + TEXT_DOCUMENT_INLAY_HINT: "clientToServer", + TEXT_DOCUMENT_INLINE_COMPLETION: "clientToServer", + TEXT_DOCUMENT_INLINE_VALUE: "clientToServer", + TEXT_DOCUMENT_LINKED_EDITING_RANGE: "clientToServer", + TEXT_DOCUMENT_MONIKER: "clientToServer", + TEXT_DOCUMENT_ON_TYPE_FORMATTING: "clientToServer", + TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY: "clientToServer", + TEXT_DOCUMENT_PREPARE_RENAME: "clientToServer", + TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY: "clientToServer", + TEXT_DOCUMENT_RANGES_FORMATTING: "clientToServer", + TEXT_DOCUMENT_RANGE_FORMATTING: "clientToServer", + TEXT_DOCUMENT_REFERENCES: "clientToServer", + TEXT_DOCUMENT_RENAME: "clientToServer", + TEXT_DOCUMENT_SELECTION_RANGE: "clientToServer", + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL: "clientToServer", + TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA: "clientToServer", + TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE: "clientToServer", + TEXT_DOCUMENT_SIGNATURE_HELP: "clientToServer", + TEXT_DOCUMENT_TYPE_DEFINITION: "clientToServer", + TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL: "clientToServer", + TYPE_HIERARCHY_SUBTYPES: "clientToServer", + TYPE_HIERARCHY_SUPERTYPES: "clientToServer", + WINDOW_SHOW_DOCUMENT: "serverToClient", + WINDOW_SHOW_MESSAGE_REQUEST: "serverToClient", + WINDOW_WORK_DONE_PROGRESS_CREATE: "serverToClient", + WORKSPACE_APPLY_EDIT: "serverToClient", + WORKSPACE_CODE_LENS_REFRESH: "serverToClient", + WORKSPACE_CONFIGURATION: "serverToClient", + WORKSPACE_DIAGNOSTIC: "clientToServer", + WORKSPACE_DIAGNOSTIC_REFRESH: "serverToClient", + WORKSPACE_EXECUTE_COMMAND: "clientToServer", + WORKSPACE_FOLDING_RANGE_REFRESH: "serverToClient", + WORKSPACE_INLAY_HINT_REFRESH: "serverToClient", + WORKSPACE_INLINE_VALUE_REFRESH: "serverToClient", + WORKSPACE_SEMANTIC_TOKENS_REFRESH: "serverToClient", + WORKSPACE_SYMBOL: "clientToServer", + WORKSPACE_SYMBOL_RESOLVE: "clientToServer", + WORKSPACE_WILL_CREATE_FILES: "clientToServer", + WORKSPACE_WILL_DELETE_FILES: "clientToServer", + WORKSPACE_WILL_RENAME_FILES: "clientToServer", + WORKSPACE_WORKSPACE_FOLDERS: "serverToClient", + # Notification methods + CANCEL_REQUEST: "both", + EXIT: "clientToServer", + INITIALIZED: "clientToServer", + LOG_TRACE: "serverToClient", + NOTEBOOK_DOCUMENT_DID_CHANGE: "clientToServer", + NOTEBOOK_DOCUMENT_DID_CLOSE: "clientToServer", + NOTEBOOK_DOCUMENT_DID_OPEN: "clientToServer", + NOTEBOOK_DOCUMENT_DID_SAVE: "clientToServer", + PROGRESS: "both", + SET_TRACE: "clientToServer", + TELEMETRY_EVENT: "serverToClient", + TEXT_DOCUMENT_DID_CHANGE: "clientToServer", + TEXT_DOCUMENT_DID_CLOSE: "clientToServer", + TEXT_DOCUMENT_DID_OPEN: "clientToServer", + TEXT_DOCUMENT_DID_SAVE: "clientToServer", + TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS: "serverToClient", + TEXT_DOCUMENT_WILL_SAVE: "clientToServer", + WINDOW_LOG_MESSAGE: "serverToClient", + WINDOW_SHOW_MESSAGE: "serverToClient", + WINDOW_WORK_DONE_PROGRESS_CANCEL: "clientToServer", + WORKSPACE_DID_CHANGE_CONFIGURATION: "clientToServer", + WORKSPACE_DID_CHANGE_WATCHED_FILES: "clientToServer", + WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS: "clientToServer", + WORKSPACE_DID_CREATE_FILES: "clientToServer", + WORKSPACE_DID_DELETE_FILES: "clientToServer", + WORKSPACE_DID_RENAME_FILES: "clientToServer", +} + + +def message_direction(method: str) -> str: + """Returns message direction clientToServer, serverToClient or both.""" + return _MESSAGE_DIRECTION[method] diff --git a/server/libs/lsprotocol/validators.py b/server/libs/lsprotocol/validators.py new file mode 100644 index 0000000..a0f00c7 --- /dev/null +++ b/server/libs/lsprotocol/validators.py @@ -0,0 +1,47 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + import attrs + +INTEGER_MIN_VALUE = -(2**31) +INTEGER_MAX_VALUE = 2**31 - 1 + + +def integer_validator( + instance: Any, + attribute: "attrs.Attribute[int]", + value: Any, +) -> bool: + """Validates that integer value belongs in the range expected by LSP.""" + if not isinstance(value, int) or not ( + INTEGER_MIN_VALUE <= value <= INTEGER_MAX_VALUE + ): + name = attribute.name if hasattr(attribute, "name") else str(attribute) + raise ValueError( + f"{instance.__class__.__qualname__}.{name} should be in range [{INTEGER_MIN_VALUE}:{INTEGER_MAX_VALUE}], but was {value}." + ) + return True + + +UINTEGER_MIN_VALUE = 0 +UINTEGER_MAX_VALUE = 2**31 - 1 + + +def uinteger_validator( + instance: Any, + attribute: "attrs.Attribute[int]", + value: Any, +) -> bool: + """Validates that unsigned integer value belongs in the range expected by LSP.""" + if not isinstance(value, int) or not ( + UINTEGER_MIN_VALUE <= value <= UINTEGER_MAX_VALUE + ): + name = attribute.name if hasattr(attribute, "name") else str(attribute) + raise ValueError( + f"{instance.__class__.__qualname__}.{name} should be in range [{UINTEGER_MIN_VALUE}:{UINTEGER_MAX_VALUE}], but was {value}." + ) + return True diff --git a/server/libs/packaging-25.0.dist-info/INSTALLER b/server/libs/packaging-25.0.dist-info/INSTALLER new file mode 100644 index 0000000..a1b589e --- /dev/null +++ b/server/libs/packaging-25.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/server/libs/packaging-25.0.dist-info/METADATA b/server/libs/packaging-25.0.dist-info/METADATA new file mode 100644 index 0000000..10b290a --- /dev/null +++ b/server/libs/packaging-25.0.dist-info/METADATA @@ -0,0 +1,105 @@ +Metadata-Version: 2.4 +Name: packaging +Version: 25.0 +Summary: Core utilities for Python packages +Author-email: Donald Stufft +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Typing :: Typed +License-File: LICENSE +License-File: LICENSE.APACHE +License-File: LICENSE.BSD +Project-URL: Documentation, https://packaging.pypa.io/ +Project-URL: Source, https://github.com/pypa/packaging + +packaging +========= + +.. start-intro + +Reusable core utilities for various Python Packaging +`interoperability specifications `_. + +This library provides utilities that implement the interoperability +specifications which have clearly one correct behaviour (eg: :pep:`440`) +or benefit greatly from having a single shared implementation (eg: :pep:`425`). + +.. end-intro + +The ``packaging`` project includes the following: version handling, specifiers, +markers, requirements, tags, utilities. + +Documentation +------------- + +The `documentation`_ provides information and the API for the following: + +- Version Handling +- Specifiers +- Markers +- Requirements +- Tags +- Utilities + +Installation +------------ + +Use ``pip`` to install these utilities:: + + pip install packaging + +The ``packaging`` library uses calendar-based versioning (``YY.N``). + +Discussion +---------- + +If you run into bugs, you can file them in our `issue tracker`_. + +You can also join ``#pypa`` on Freenode to ask questions or get involved. + + +.. _`documentation`: https://packaging.pypa.io/ +.. _`issue tracker`: https://github.com/pypa/packaging/issues + + +Code of Conduct +--------------- + +Everyone interacting in the packaging project's codebases, issue trackers, chat +rooms, and mailing lists is expected to follow the `PSF Code of Conduct`_. + +.. _PSF Code of Conduct: https://github.com/pypa/.github/blob/main/CODE_OF_CONDUCT.md + +Contributing +------------ + +The ``CONTRIBUTING.rst`` file outlines how to contribute to this project as +well as how to report a potential security issue. The documentation for this +project also covers information about `project development`_ and `security`_. + +.. _`project development`: https://packaging.pypa.io/en/latest/development/ +.. _`security`: https://packaging.pypa.io/en/latest/security/ + +Project History +--------------- + +Please review the ``CHANGELOG.rst`` file or the `Changelog documentation`_ for +recent changes and project history. + +.. _`Changelog documentation`: https://packaging.pypa.io/en/latest/changelog/ + diff --git a/server/libs/packaging-25.0.dist-info/RECORD b/server/libs/packaging-25.0.dist-info/RECORD new file mode 100644 index 0000000..9346bb4 --- /dev/null +++ b/server/libs/packaging-25.0.dist-info/RECORD @@ -0,0 +1,41 @@ +packaging-25.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +packaging-25.0.dist-info/METADATA,sha256=W2EaYJw4_vw9YWv0XSCuyY-31T8kXayp4sMPyFx6woI,3281 +packaging-25.0.dist-info/RECORD,, +packaging-25.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +packaging-25.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +packaging-25.0.dist-info/licenses/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197 +packaging-25.0.dist-info/licenses/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174 +packaging-25.0.dist-info/licenses/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344 +packaging/__init__.py,sha256=_0cDiPVf2S-bNfVmZguxxzmrIYWlyASxpqph4qsJWUc,494 +packaging/__pycache__/__init__.cpython-311.pyc,, +packaging/__pycache__/_elffile.cpython-311.pyc,, +packaging/__pycache__/_manylinux.cpython-311.pyc,, +packaging/__pycache__/_musllinux.cpython-311.pyc,, +packaging/__pycache__/_parser.cpython-311.pyc,, +packaging/__pycache__/_structures.cpython-311.pyc,, +packaging/__pycache__/_tokenizer.cpython-311.pyc,, +packaging/__pycache__/markers.cpython-311.pyc,, +packaging/__pycache__/metadata.cpython-311.pyc,, +packaging/__pycache__/requirements.cpython-311.pyc,, +packaging/__pycache__/specifiers.cpython-311.pyc,, +packaging/__pycache__/tags.cpython-311.pyc,, +packaging/__pycache__/utils.cpython-311.pyc,, +packaging/__pycache__/version.cpython-311.pyc,, +packaging/_elffile.py,sha256=UkrbDtW7aeq3qqoAfU16ojyHZ1xsTvGke_WqMTKAKd0,3286 +packaging/_manylinux.py,sha256=t4y_-dTOcfr36gLY-ztiOpxxJFGO2ikC11HgfysGxiM,9596 +packaging/_musllinux.py,sha256=p9ZqNYiOItGee8KcZFeHF_YcdhVwGHdK6r-8lgixvGQ,2694 +packaging/_parser.py,sha256=gYfnj0pRHflVc4RHZit13KNTyN9iiVcU2RUCGi22BwM,10221 +packaging/_structures.py,sha256=q3eVNmbWJGG_S0Dit_S3Ao8qQqz_5PYTXFAKBZe5yr4,1431 +packaging/_tokenizer.py,sha256=OYzt7qKxylOAJ-q0XyK1qAycyPRYLfMPdGQKRXkZWyI,5310 +packaging/licenses/__init__.py,sha256=VsK4o27CJXWfTi8r2ybJmsBoCdhpnBWuNrskaCVKP7U,5715 +packaging/licenses/__pycache__/__init__.cpython-311.pyc,, +packaging/licenses/__pycache__/_spdx.cpython-311.pyc,, +packaging/licenses/_spdx.py,sha256=oAm1ztPFwlsmCKe7lAAsv_OIOfS1cWDu9bNBkeu-2ns,48398 +packaging/markers.py,sha256=P0we27jm1xUzgGMJxBjtUFCIWeBxTsMeJTOJ6chZmAY,12049 +packaging/metadata.py,sha256=8IZErqQQnNm53dZZuYq4FGU4_dpyinMeH1QFBIWIkfE,34739 +packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +packaging/requirements.py,sha256=gYyRSAdbrIyKDY66ugIDUQjRMvxkH2ALioTmX3tnL6o,2947 +packaging/specifiers.py,sha256=gtPu5DTc-F9baLq3FTGEK6dPhHGCuwwZetaY0PSV2gs,40055 +packaging/tags.py,sha256=41s97W9Zatrq2Ed7Rc3qeBDaHe8pKKvYq2mGjwahfXk,22745 +packaging/utils.py,sha256=0F3Hh9OFuRgrhTgGZUl5K22Fv1YP2tZl1z_2gO6kJiA,5050 +packaging/version.py,sha256=olfyuk_DPbflNkJ4wBWetXQ17c74x3DB501degUv7DY,16676 diff --git a/server/libs/packaging-25.0.dist-info/REQUESTED b/server/libs/packaging-25.0.dist-info/REQUESTED new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/packaging-25.0.dist-info/WHEEL b/server/libs/packaging-25.0.dist-info/WHEEL new file mode 100644 index 0000000..d8b9936 --- /dev/null +++ b/server/libs/packaging-25.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/server/libs/packaging-25.0.dist-info/licenses/LICENSE b/server/libs/packaging-25.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000..6f62d44 --- /dev/null +++ b/server/libs/packaging-25.0.dist-info/licenses/LICENSE @@ -0,0 +1,3 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made +under the terms of *both* these licenses. diff --git a/server/libs/packaging-25.0.dist-info/licenses/LICENSE.APACHE b/server/libs/packaging-25.0.dist-info/licenses/LICENSE.APACHE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/server/libs/packaging-25.0.dist-info/licenses/LICENSE.APACHE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/server/libs/packaging-25.0.dist-info/licenses/LICENSE.BSD b/server/libs/packaging-25.0.dist-info/licenses/LICENSE.BSD new file mode 100644 index 0000000..42ce7b7 --- /dev/null +++ b/server/libs/packaging-25.0.dist-info/licenses/LICENSE.BSD @@ -0,0 +1,23 @@ +Copyright (c) Donald Stufft and individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/server/libs/packaging/__init__.py b/server/libs/packaging/__init__.py new file mode 100644 index 0000000..d45c22c --- /dev/null +++ b/server/libs/packaging/__init__.py @@ -0,0 +1,15 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +__title__ = "packaging" +__summary__ = "Core utilities for Python packages" +__uri__ = "https://github.com/pypa/packaging" + +__version__ = "25.0" + +__author__ = "Donald Stufft and individual contributors" +__email__ = "donald@stufft.io" + +__license__ = "BSD-2-Clause or Apache-2.0" +__copyright__ = f"2014 {__author__}" diff --git a/server/libs/packaging/_elffile.py b/server/libs/packaging/_elffile.py new file mode 100644 index 0000000..7a5afc3 --- /dev/null +++ b/server/libs/packaging/_elffile.py @@ -0,0 +1,109 @@ +""" +ELF file parser. + +This provides a class ``ELFFile`` that parses an ELF executable in a similar +interface to ``ZipFile``. Only the read interface is implemented. + +Based on: https://gist.github.com/lyssdod/f51579ae8d93c8657a5564aefc2ffbca +ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html +""" + +from __future__ import annotations + +import enum +import os +import struct +from typing import IO + + +class ELFInvalid(ValueError): + pass + + +class EIClass(enum.IntEnum): + C32 = 1 + C64 = 2 + + +class EIData(enum.IntEnum): + Lsb = 1 + Msb = 2 + + +class EMachine(enum.IntEnum): + I386 = 3 + S390 = 22 + Arm = 40 + X8664 = 62 + AArc64 = 183 + + +class ELFFile: + """ + Representation of an ELF executable. + """ + + def __init__(self, f: IO[bytes]) -> None: + self._f = f + + try: + ident = self._read("16B") + except struct.error as e: + raise ELFInvalid("unable to parse identification") from e + magic = bytes(ident[:4]) + if magic != b"\x7fELF": + raise ELFInvalid(f"invalid magic: {magic!r}") + + self.capacity = ident[4] # Format for program header (bitness). + self.encoding = ident[5] # Data structure encoding (endianness). + + try: + # e_fmt: Format for program header. + # p_fmt: Format for section header. + # p_idx: Indexes to find p_type, p_offset, and p_filesz. + e_fmt, self._p_fmt, self._p_idx = { + (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)), # 32-bit MSB. + (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)), # 64-bit MSB. + }[(self.capacity, self.encoding)] + except KeyError as e: + raise ELFInvalid( + f"unrecognized capacity ({self.capacity}) or encoding ({self.encoding})" + ) from e + + try: + ( + _, + self.machine, # Architecture type. + _, + _, + self._e_phoff, # Offset of program header. + _, + self.flags, # Processor-specific flags. + _, + self._e_phentsize, # Size of section. + self._e_phnum, # Number of sections. + ) = self._read(e_fmt) + except struct.error as e: + raise ELFInvalid("unable to parse machine and section information") from e + + def _read(self, fmt: str) -> tuple[int, ...]: + return struct.unpack(fmt, self._f.read(struct.calcsize(fmt))) + + @property + def interpreter(self) -> str | None: + """ + The path recorded in the ``PT_INTERP`` section header. + """ + for index in range(self._e_phnum): + self._f.seek(self._e_phoff + self._e_phentsize * index) + try: + data = self._read(self._p_fmt) + except struct.error: + continue + if data[self._p_idx[0]] != 3: # Not PT_INTERP. + continue + self._f.seek(data[self._p_idx[1]]) + return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0") + return None diff --git a/server/libs/packaging/_manylinux.py b/server/libs/packaging/_manylinux.py new file mode 100644 index 0000000..95f5576 --- /dev/null +++ b/server/libs/packaging/_manylinux.py @@ -0,0 +1,262 @@ +from __future__ import annotations + +import collections +import contextlib +import functools +import os +import re +import sys +import warnings +from typing import Generator, Iterator, NamedTuple, Sequence + +from ._elffile import EIClass, EIData, ELFFile, EMachine + +EF_ARM_ABIMASK = 0xFF000000 +EF_ARM_ABI_VER5 = 0x05000000 +EF_ARM_ABI_FLOAT_HARD = 0x00000400 + + +# `os.PathLike` not a generic type until Python 3.9, so sticking with `str` +# as the type for `path` until then. +@contextlib.contextmanager +def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]: + try: + with open(path, "rb") as f: + yield ELFFile(f) + except (OSError, TypeError, ValueError): + yield None + + +def _is_linux_armhf(executable: str) -> bool: + # hard-float ABI can be detected from the ELF header of the running + # process + # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf + with _parse_elf(executable) as f: + return ( + f is not None + and f.capacity == EIClass.C32 + and f.encoding == EIData.Lsb + and f.machine == EMachine.Arm + and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5 + and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD + ) + + +def _is_linux_i686(executable: str) -> bool: + with _parse_elf(executable) as f: + return ( + f is not None + and f.capacity == EIClass.C32 + and f.encoding == EIData.Lsb + and f.machine == EMachine.I386 + ) + + +def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool: + if "armv7l" in archs: + return _is_linux_armhf(executable) + if "i686" in archs: + return _is_linux_i686(executable) + allowed_archs = { + "x86_64", + "aarch64", + "ppc64", + "ppc64le", + "s390x", + "loongarch64", + "riscv64", + } + return any(arch in allowed_archs for arch in archs) + + +# If glibc ever changes its major version, we need to know what the last +# minor version was, so we can build the complete list of all versions. +# For now, guess what the highest minor version might be, assume it will +# be 50 for testing. Once this actually happens, update the dictionary +# with the actual value. +_LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50) + + +class _GLibCVersion(NamedTuple): + major: int + minor: int + + +def _glibc_version_string_confstr() -> str | None: + """ + Primary implementation of glibc_version_string using os.confstr. + """ + # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely + # to be broken or missing. This strategy is used in the standard library + # platform module. + # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183 + try: + # Should be a string like "glibc 2.17". + version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION") + assert version_string is not None + _, version = version_string.rsplit() + except (AssertionError, AttributeError, OSError, ValueError): + # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)... + return None + return version + + +def _glibc_version_string_ctypes() -> str | None: + """ + Fallback implementation of glibc_version_string using ctypes. + """ + try: + import ctypes + except ImportError: + return None + + # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen + # manpage says, "If filename is NULL, then the returned handle is for the + # main program". This way we can let the linker do the work to figure out + # which libc our process is actually using. + # + # We must also handle the special case where the executable is not a + # dynamically linked executable. This can occur when using musl libc, + # for example. In this situation, dlopen() will error, leading to an + # OSError. Interestingly, at least in the case of musl, there is no + # errno set on the OSError. The single string argument used to construct + # OSError comes from libc itself and is therefore not portable to + # hard code here. In any case, failure to call dlopen() means we + # can proceed, so we bail on our attempt. + try: + process_namespace = ctypes.CDLL(None) + except OSError: + return None + + try: + gnu_get_libc_version = process_namespace.gnu_get_libc_version + except AttributeError: + # Symbol doesn't exist -> therefore, we are not linked to + # glibc. + return None + + # Call gnu_get_libc_version, which returns a string like "2.5" + gnu_get_libc_version.restype = ctypes.c_char_p + version_str: str = gnu_get_libc_version() + # py2 / py3 compatibility: + if not isinstance(version_str, str): + version_str = version_str.decode("ascii") + + return version_str + + +def _glibc_version_string() -> str | None: + """Returns glibc version string, or None if not using glibc.""" + return _glibc_version_string_confstr() or _glibc_version_string_ctypes() + + +def _parse_glibc_version(version_str: str) -> tuple[int, int]: + """Parse glibc version. + + We use a regexp instead of str.split because we want to discard any + random junk that might come after the minor version -- this might happen + in patched/forked versions of glibc (e.g. Linaro's version of glibc + uses version strings like "2.20-2014.11"). See gh-3588. + """ + m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str) + if not m: + warnings.warn( + f"Expected glibc version with 2 components major.minor, got: {version_str}", + RuntimeWarning, + stacklevel=2, + ) + return -1, -1 + return int(m.group("major")), int(m.group("minor")) + + +@functools.lru_cache +def _get_glibc_version() -> tuple[int, int]: + version_str = _glibc_version_string() + if version_str is None: + return (-1, -1) + return _parse_glibc_version(version_str) + + +# From PEP 513, PEP 600 +def _is_compatible(arch: str, version: _GLibCVersion) -> bool: + sys_glibc = _get_glibc_version() + if sys_glibc < version: + return False + # Check for presence of _manylinux module. + try: + import _manylinux + except ImportError: + return True + if hasattr(_manylinux, "manylinux_compatible"): + result = _manylinux.manylinux_compatible(version[0], version[1], arch) + if result is not None: + return bool(result) + return True + if version == _GLibCVersion(2, 5): + if hasattr(_manylinux, "manylinux1_compatible"): + return bool(_manylinux.manylinux1_compatible) + if version == _GLibCVersion(2, 12): + if hasattr(_manylinux, "manylinux2010_compatible"): + return bool(_manylinux.manylinux2010_compatible) + if version == _GLibCVersion(2, 17): + if hasattr(_manylinux, "manylinux2014_compatible"): + return bool(_manylinux.manylinux2014_compatible) + return True + + +_LEGACY_MANYLINUX_MAP = { + # CentOS 7 w/ glibc 2.17 (PEP 599) + (2, 17): "manylinux2014", + # CentOS 6 w/ glibc 2.12 (PEP 571) + (2, 12): "manylinux2010", + # CentOS 5 w/ glibc 2.5 (PEP 513) + (2, 5): "manylinux1", +} + + +def platform_tags(archs: Sequence[str]) -> Iterator[str]: + """Generate manylinux tags compatible to the current platform. + + :param archs: Sequence of compatible architectures. + The first one shall be the closest to the actual architecture and be the part of + platform tag after the ``linux_`` prefix, e.g. ``x86_64``. + The ``linux_`` prefix is assumed as a prerequisite for the current platform to + be manylinux-compatible. + + :returns: An iterator of compatible manylinux tags. + """ + if not _have_compatible_abi(sys.executable, archs): + return + # Oldest glibc to be supported regardless of architecture is (2, 17). + too_old_glibc2 = _GLibCVersion(2, 16) + if set(archs) & {"x86_64", "i686"}: + # On x86/i686 also oldest glibc to be supported is (2, 5). + too_old_glibc2 = _GLibCVersion(2, 4) + current_glibc = _GLibCVersion(*_get_glibc_version()) + glibc_max_list = [current_glibc] + # We can assume compatibility across glibc major versions. + # https://sourceware.org/bugzilla/show_bug.cgi?id=24636 + # + # Build a list of maximum glibc versions so that we can + # output the canonical list of all glibc from current_glibc + # down to too_old_glibc2, including all intermediary versions. + for glibc_major in range(current_glibc.major - 1, 1, -1): + glibc_minor = _LAST_GLIBC_MINOR[glibc_major] + glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor)) + for arch in archs: + for glibc_max in glibc_max_list: + if glibc_max.major == too_old_glibc2.major: + min_minor = too_old_glibc2.minor + else: + # For other glibc major versions oldest supported is (x, 0). + min_minor = -1 + for glibc_minor in range(glibc_max.minor, min_minor, -1): + glibc_version = _GLibCVersion(glibc_max.major, glibc_minor) + tag = "manylinux_{}_{}".format(*glibc_version) + if _is_compatible(arch, glibc_version): + yield f"{tag}_{arch}" + # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags. + if glibc_version in _LEGACY_MANYLINUX_MAP: + legacy_tag = _LEGACY_MANYLINUX_MAP[glibc_version] + if _is_compatible(arch, glibc_version): + yield f"{legacy_tag}_{arch}" diff --git a/server/libs/packaging/_musllinux.py b/server/libs/packaging/_musllinux.py new file mode 100644 index 0000000..d2bf30b --- /dev/null +++ b/server/libs/packaging/_musllinux.py @@ -0,0 +1,85 @@ +"""PEP 656 support. + +This module implements logic to detect if the currently running Python is +linked against musl, and what musl version is used. +""" + +from __future__ import annotations + +import functools +import re +import subprocess +import sys +from typing import Iterator, NamedTuple, Sequence + +from ._elffile import ELFFile + + +class _MuslVersion(NamedTuple): + major: int + minor: int + + +def _parse_musl_version(output: str) -> _MuslVersion | None: + lines = [n for n in (n.strip() for n in output.splitlines()) if n] + if len(lines) < 2 or lines[0][:4] != "musl": + return None + m = re.match(r"Version (\d+)\.(\d+)", lines[1]) + if not m: + return None + return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2))) + + +@functools.lru_cache +def _get_musl_version(executable: str) -> _MuslVersion | None: + """Detect currently-running musl runtime version. + + This is done by checking the specified executable's dynamic linking + information, and invoking the loader to parse its output for a version + string. If the loader is musl, the output would be something like:: + + musl libc (x86_64) + Version 1.2.2 + Dynamic Program Loader + """ + try: + with open(executable, "rb") as f: + ld = ELFFile(f).interpreter + except (OSError, TypeError, ValueError): + return None + if ld is None or "musl" not in ld: + return None + proc = subprocess.run([ld], stderr=subprocess.PIPE, text=True) + return _parse_musl_version(proc.stderr) + + +def platform_tags(archs: Sequence[str]) -> Iterator[str]: + """Generate musllinux tags compatible to the current platform. + + :param archs: Sequence of compatible architectures. + The first one shall be the closest to the actual architecture and be the part of + platform tag after the ``linux_`` prefix, e.g. ``x86_64``. + The ``linux_`` prefix is assumed as a prerequisite for the current platform to + be musllinux-compatible. + + :returns: An iterator of compatible musllinux tags. + """ + sys_musl = _get_musl_version(sys.executable) + if sys_musl is None: # Python not dynamically linked against musl. + return + for arch in archs: + for minor in range(sys_musl.minor, -1, -1): + yield f"musllinux_{sys_musl.major}_{minor}_{arch}" + + +if __name__ == "__main__": # pragma: no cover + import sysconfig + + plat = sysconfig.get_platform() + assert plat.startswith("linux-"), "not linux" + + print("plat:", plat) + print("musl:", _get_musl_version(sys.executable)) + print("tags:", end=" ") + for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])): + print(t, end="\n ") diff --git a/server/libs/packaging/_parser.py b/server/libs/packaging/_parser.py new file mode 100644 index 0000000..0007c0a --- /dev/null +++ b/server/libs/packaging/_parser.py @@ -0,0 +1,353 @@ +"""Handwritten parser of dependency specifiers. + +The docstring for each __parse_* function contains EBNF-inspired grammar representing +the implementation. +""" + +from __future__ import annotations + +import ast +from typing import NamedTuple, Sequence, Tuple, Union + +from ._tokenizer import DEFAULT_RULES, Tokenizer + + +class Node: + def __init__(self, value: str) -> None: + self.value = value + + def __str__(self) -> str: + return self.value + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}('{self}')>" + + def serialize(self) -> str: + raise NotImplementedError + + +class Variable(Node): + def serialize(self) -> str: + return str(self) + + +class Value(Node): + def serialize(self) -> str: + return f'"{self}"' + + +class Op(Node): + def serialize(self) -> str: + return str(self) + + +MarkerVar = Union[Variable, Value] +MarkerItem = Tuple[MarkerVar, Op, MarkerVar] +MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]] +MarkerList = Sequence[Union["MarkerList", MarkerAtom, str]] + + +class ParsedRequirement(NamedTuple): + name: str + url: str + extras: list[str] + specifier: str + marker: MarkerList | None + + +# -------------------------------------------------------------------------------------- +# Recursive descent parser for dependency specifier +# -------------------------------------------------------------------------------------- +def parse_requirement(source: str) -> ParsedRequirement: + return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES)) + + +def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement: + """ + requirement = WS? IDENTIFIER WS? extras WS? requirement_details + """ + tokenizer.consume("WS") + + name_token = tokenizer.expect( + "IDENTIFIER", expected="package name at the start of dependency specifier" + ) + name = name_token.text + tokenizer.consume("WS") + + extras = _parse_extras(tokenizer) + tokenizer.consume("WS") + + url, specifier, marker = _parse_requirement_details(tokenizer) + tokenizer.expect("END", expected="end of dependency specifier") + + return ParsedRequirement(name, url, extras, specifier, marker) + + +def _parse_requirement_details( + tokenizer: Tokenizer, +) -> tuple[str, str, MarkerList | None]: + """ + requirement_details = AT URL (WS requirement_marker?)? + | specifier WS? (requirement_marker)? + """ + + specifier = "" + url = "" + marker = None + + if tokenizer.check("AT"): + tokenizer.read() + tokenizer.consume("WS") + + url_start = tokenizer.position + url = tokenizer.expect("URL", expected="URL after @").text + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + tokenizer.expect("WS", expected="whitespace after URL") + + # The input might end after whitespace. + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + marker = _parse_requirement_marker( + tokenizer, span_start=url_start, after="URL and whitespace" + ) + else: + specifier_start = tokenizer.position + specifier = _parse_specifier(tokenizer) + tokenizer.consume("WS") + + if tokenizer.check("END", peek=True): + return (url, specifier, marker) + + marker = _parse_requirement_marker( + tokenizer, + span_start=specifier_start, + after=( + "version specifier" + if specifier + else "name and no valid version specifier" + ), + ) + + return (url, specifier, marker) + + +def _parse_requirement_marker( + tokenizer: Tokenizer, *, span_start: int, after: str +) -> MarkerList: + """ + requirement_marker = SEMICOLON marker WS? + """ + + if not tokenizer.check("SEMICOLON"): + tokenizer.raise_syntax_error( + f"Expected end or semicolon (after {after})", + span_start=span_start, + ) + tokenizer.read() + + marker = _parse_marker(tokenizer) + tokenizer.consume("WS") + + return marker + + +def _parse_extras(tokenizer: Tokenizer) -> list[str]: + """ + extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)? + """ + if not tokenizer.check("LEFT_BRACKET", peek=True): + return [] + + with tokenizer.enclosing_tokens( + "LEFT_BRACKET", + "RIGHT_BRACKET", + around="extras", + ): + tokenizer.consume("WS") + extras = _parse_extras_list(tokenizer) + tokenizer.consume("WS") + + return extras + + +def _parse_extras_list(tokenizer: Tokenizer) -> list[str]: + """ + extras_list = identifier (wsp* ',' wsp* identifier)* + """ + extras: list[str] = [] + + if not tokenizer.check("IDENTIFIER"): + return extras + + extras.append(tokenizer.read().text) + + while True: + tokenizer.consume("WS") + if tokenizer.check("IDENTIFIER", peek=True): + tokenizer.raise_syntax_error("Expected comma between extra names") + elif not tokenizer.check("COMMA"): + break + + tokenizer.read() + tokenizer.consume("WS") + + extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma") + extras.append(extra_token.text) + + return extras + + +def _parse_specifier(tokenizer: Tokenizer) -> str: + """ + specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS + | WS? version_many WS? + """ + with tokenizer.enclosing_tokens( + "LEFT_PARENTHESIS", + "RIGHT_PARENTHESIS", + around="version specifier", + ): + tokenizer.consume("WS") + parsed_specifiers = _parse_version_many(tokenizer) + tokenizer.consume("WS") + + return parsed_specifiers + + +def _parse_version_many(tokenizer: Tokenizer) -> str: + """ + version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)? + """ + parsed_specifiers = "" + while tokenizer.check("SPECIFIER"): + span_start = tokenizer.position + parsed_specifiers += tokenizer.read().text + if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True): + tokenizer.raise_syntax_error( + ".* suffix can only be used with `==` or `!=` operators", + span_start=span_start, + span_end=tokenizer.position + 1, + ) + if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True): + tokenizer.raise_syntax_error( + "Local version label can only be used with `==` or `!=` operators", + span_start=span_start, + span_end=tokenizer.position, + ) + tokenizer.consume("WS") + if not tokenizer.check("COMMA"): + break + parsed_specifiers += tokenizer.read().text + tokenizer.consume("WS") + + return parsed_specifiers + + +# -------------------------------------------------------------------------------------- +# Recursive descent parser for marker expression +# -------------------------------------------------------------------------------------- +def parse_marker(source: str) -> MarkerList: + return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES)) + + +def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList: + retval = _parse_marker(tokenizer) + tokenizer.expect("END", expected="end of marker expression") + return retval + + +def _parse_marker(tokenizer: Tokenizer) -> MarkerList: + """ + marker = marker_atom (BOOLOP marker_atom)+ + """ + expression = [_parse_marker_atom(tokenizer)] + while tokenizer.check("BOOLOP"): + token = tokenizer.read() + expr_right = _parse_marker_atom(tokenizer) + expression.extend((token.text, expr_right)) + return expression + + +def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom: + """ + marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS? + | WS? marker_item WS? + """ + + tokenizer.consume("WS") + if tokenizer.check("LEFT_PARENTHESIS", peek=True): + with tokenizer.enclosing_tokens( + "LEFT_PARENTHESIS", + "RIGHT_PARENTHESIS", + around="marker expression", + ): + tokenizer.consume("WS") + marker: MarkerAtom = _parse_marker(tokenizer) + tokenizer.consume("WS") + else: + marker = _parse_marker_item(tokenizer) + tokenizer.consume("WS") + return marker + + +def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem: + """ + marker_item = WS? marker_var WS? marker_op WS? marker_var WS? + """ + tokenizer.consume("WS") + marker_var_left = _parse_marker_var(tokenizer) + tokenizer.consume("WS") + marker_op = _parse_marker_op(tokenizer) + tokenizer.consume("WS") + marker_var_right = _parse_marker_var(tokenizer) + tokenizer.consume("WS") + return (marker_var_left, marker_op, marker_var_right) + + +def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar: + """ + marker_var = VARIABLE | QUOTED_STRING + """ + if tokenizer.check("VARIABLE"): + return process_env_var(tokenizer.read().text.replace(".", "_")) + elif tokenizer.check("QUOTED_STRING"): + return process_python_str(tokenizer.read().text) + else: + tokenizer.raise_syntax_error( + message="Expected a marker variable or quoted string" + ) + + +def process_env_var(env_var: str) -> Variable: + if env_var in ("platform_python_implementation", "python_implementation"): + return Variable("platform_python_implementation") + else: + return Variable(env_var) + + +def process_python_str(python_str: str) -> Value: + value = ast.literal_eval(python_str) + return Value(str(value)) + + +def _parse_marker_op(tokenizer: Tokenizer) -> Op: + """ + marker_op = IN | NOT IN | OP + """ + if tokenizer.check("IN"): + tokenizer.read() + return Op("in") + elif tokenizer.check("NOT"): + tokenizer.read() + tokenizer.expect("WS", expected="whitespace after 'not'") + tokenizer.expect("IN", expected="'in' after 'not'") + return Op("not in") + elif tokenizer.check("OP"): + return Op(tokenizer.read().text) + else: + return tokenizer.raise_syntax_error( + "Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in" + ) diff --git a/server/libs/packaging/_structures.py b/server/libs/packaging/_structures.py new file mode 100644 index 0000000..90a6465 --- /dev/null +++ b/server/libs/packaging/_structures.py @@ -0,0 +1,61 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + + +class InfinityType: + def __repr__(self) -> str: + return "Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return False + + def __le__(self, other: object) -> bool: + return False + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return True + + def __ge__(self, other: object) -> bool: + return True + + def __neg__(self: object) -> "NegativeInfinityType": + return NegativeInfinity + + +Infinity = InfinityType() + + +class NegativeInfinityType: + def __repr__(self) -> str: + return "-Infinity" + + def __hash__(self) -> int: + return hash(repr(self)) + + def __lt__(self, other: object) -> bool: + return True + + def __le__(self, other: object) -> bool: + return True + + def __eq__(self, other: object) -> bool: + return isinstance(other, self.__class__) + + def __gt__(self, other: object) -> bool: + return False + + def __ge__(self, other: object) -> bool: + return False + + def __neg__(self: object) -> InfinityType: + return Infinity + + +NegativeInfinity = NegativeInfinityType() diff --git a/server/libs/packaging/_tokenizer.py b/server/libs/packaging/_tokenizer.py new file mode 100644 index 0000000..d28a9b6 --- /dev/null +++ b/server/libs/packaging/_tokenizer.py @@ -0,0 +1,195 @@ +from __future__ import annotations + +import contextlib +import re +from dataclasses import dataclass +from typing import Iterator, NoReturn + +from .specifiers import Specifier + + +@dataclass +class Token: + name: str + text: str + position: int + + +class ParserSyntaxError(Exception): + """The provided source text could not be parsed correctly.""" + + def __init__( + self, + message: str, + *, + source: str, + span: tuple[int, int], + ) -> None: + self.span = span + self.message = message + self.source = source + + super().__init__() + + def __str__(self) -> str: + marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^" + return "\n ".join([self.message, self.source, marker]) + + +DEFAULT_RULES: dict[str, str | re.Pattern[str]] = { + "LEFT_PARENTHESIS": r"\(", + "RIGHT_PARENTHESIS": r"\)", + "LEFT_BRACKET": r"\[", + "RIGHT_BRACKET": r"\]", + "SEMICOLON": r";", + "COMMA": r",", + "QUOTED_STRING": re.compile( + r""" + ( + ('[^']*') + | + ("[^"]*") + ) + """, + re.VERBOSE, + ), + "OP": r"(===|==|~=|!=|<=|>=|<|>)", + "BOOLOP": r"\b(or|and)\b", + "IN": r"\bin\b", + "NOT": r"\bnot\b", + "VARIABLE": re.compile( + r""" + \b( + python_version + |python_full_version + |os[._]name + |sys[._]platform + |platform_(release|system) + |platform[._](version|machine|python_implementation) + |python_implementation + |implementation_(name|version) + |extras? + |dependency_groups + )\b + """, + re.VERBOSE, + ), + "SPECIFIER": re.compile( + Specifier._operator_regex_str + Specifier._version_regex_str, + re.VERBOSE | re.IGNORECASE, + ), + "AT": r"\@", + "URL": r"[^ \t]+", + "IDENTIFIER": r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b", + "VERSION_PREFIX_TRAIL": r"\.\*", + "VERSION_LOCAL_LABEL_TRAIL": r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*", + "WS": r"[ \t]+", + "END": r"$", +} + + +class Tokenizer: + """Context-sensitive token parsing. + + Provides methods to examine the input stream to check whether the next token + matches. + """ + + def __init__( + self, + source: str, + *, + rules: dict[str, str | re.Pattern[str]], + ) -> None: + self.source = source + self.rules: dict[str, re.Pattern[str]] = { + name: re.compile(pattern) for name, pattern in rules.items() + } + self.next_token: Token | None = None + self.position = 0 + + def consume(self, name: str) -> None: + """Move beyond provided token name, if at current position.""" + if self.check(name): + self.read() + + def check(self, name: str, *, peek: bool = False) -> bool: + """Check whether the next token has the provided name. + + By default, if the check succeeds, the token *must* be read before + another check. If `peek` is set to `True`, the token is not loaded and + would need to be checked again. + """ + assert self.next_token is None, ( + f"Cannot check for {name!r}, already have {self.next_token!r}" + ) + assert name in self.rules, f"Unknown token name: {name!r}" + + expression = self.rules[name] + + match = expression.match(self.source, self.position) + if match is None: + return False + if not peek: + self.next_token = Token(name, match[0], self.position) + return True + + def expect(self, name: str, *, expected: str) -> Token: + """Expect a certain token name next, failing with a syntax error otherwise. + + The token is *not* read. + """ + if not self.check(name): + raise self.raise_syntax_error(f"Expected {expected}") + return self.read() + + def read(self) -> Token: + """Consume the next token and return it.""" + token = self.next_token + assert token is not None + + self.position += len(token.text) + self.next_token = None + + return token + + def raise_syntax_error( + self, + message: str, + *, + span_start: int | None = None, + span_end: int | None = None, + ) -> NoReturn: + """Raise ParserSyntaxError at the given position.""" + span = ( + self.position if span_start is None else span_start, + self.position if span_end is None else span_end, + ) + raise ParserSyntaxError( + message, + source=self.source, + span=span, + ) + + @contextlib.contextmanager + def enclosing_tokens( + self, open_token: str, close_token: str, *, around: str + ) -> Iterator[None]: + if self.check(open_token): + open_position = self.position + self.read() + else: + open_position = None + + yield + + if open_position is None: + return + + if not self.check(close_token): + self.raise_syntax_error( + f"Expected matching {close_token} for {open_token}, after {around}", + span_start=open_position, + ) + + self.read() diff --git a/server/libs/packaging/licenses/__init__.py b/server/libs/packaging/licenses/__init__.py new file mode 100644 index 0000000..6f7f9e6 --- /dev/null +++ b/server/libs/packaging/licenses/__init__.py @@ -0,0 +1,145 @@ +####################################################################################### +# +# Adapted from: +# https://github.com/pypa/hatch/blob/5352e44/backend/src/hatchling/licenses/parse.py +# +# MIT License +# +# Copyright (c) 2017-present Ofek Lev +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this +# software and associated documentation files (the "Software"), to deal in the Software +# without restriction, including without limitation the rights to use, copy, modify, +# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to the following +# conditions: +# +# The above copyright notice and this permission notice shall be included in all copies +# or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF +# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# +# With additional allowance of arbitrary `LicenseRef-` identifiers, not just +# `LicenseRef-Public-Domain` and `LicenseRef-Proprietary`. +# +####################################################################################### +from __future__ import annotations + +import re +from typing import NewType, cast + +from packaging.licenses._spdx import EXCEPTIONS, LICENSES + +__all__ = [ + "InvalidLicenseExpression", + "NormalizedLicenseExpression", + "canonicalize_license_expression", +] + +license_ref_allowed = re.compile("^[A-Za-z0-9.-]*$") + +NormalizedLicenseExpression = NewType("NormalizedLicenseExpression", str) + + +class InvalidLicenseExpression(ValueError): + """Raised when a license-expression string is invalid + + >>> canonicalize_license_expression("invalid") + Traceback (most recent call last): + ... + packaging.licenses.InvalidLicenseExpression: Invalid license expression: 'invalid' + """ + + +def canonicalize_license_expression( + raw_license_expression: str, +) -> NormalizedLicenseExpression: + if not raw_license_expression: + message = f"Invalid license expression: {raw_license_expression!r}" + raise InvalidLicenseExpression(message) + + # Pad any parentheses so tokenization can be achieved by merely splitting on + # whitespace. + license_expression = raw_license_expression.replace("(", " ( ").replace(")", " ) ") + licenseref_prefix = "LicenseRef-" + license_refs = { + ref.lower(): "LicenseRef-" + ref[len(licenseref_prefix) :] + for ref in license_expression.split() + if ref.lower().startswith(licenseref_prefix.lower()) + } + + # Normalize to lower case so we can look up licenses/exceptions + # and so boolean operators are Python-compatible. + license_expression = license_expression.lower() + + tokens = license_expression.split() + + # Rather than implementing boolean logic, we create an expression that Python can + # parse. Everything that is not involved with the grammar itself is treated as + # `False` and the expression should evaluate as such. + python_tokens = [] + for token in tokens: + if token not in {"or", "and", "with", "(", ")"}: + python_tokens.append("False") + elif token == "with": + python_tokens.append("or") + elif token == "(" and python_tokens and python_tokens[-1] not in {"or", "and"}: + message = f"Invalid license expression: {raw_license_expression!r}" + raise InvalidLicenseExpression(message) + else: + python_tokens.append(token) + + python_expression = " ".join(python_tokens) + try: + invalid = eval(python_expression, globals(), locals()) + except Exception: + invalid = True + + if invalid is not False: + message = f"Invalid license expression: {raw_license_expression!r}" + raise InvalidLicenseExpression(message) from None + + # Take a final pass to check for unknown licenses/exceptions. + normalized_tokens = [] + for token in tokens: + if token in {"or", "and", "with", "(", ")"}: + normalized_tokens.append(token.upper()) + continue + + if normalized_tokens and normalized_tokens[-1] == "WITH": + if token not in EXCEPTIONS: + message = f"Unknown license exception: {token!r}" + raise InvalidLicenseExpression(message) + + normalized_tokens.append(EXCEPTIONS[token]["id"]) + else: + if token.endswith("+"): + final_token = token[:-1] + suffix = "+" + else: + final_token = token + suffix = "" + + if final_token.startswith("licenseref-"): + if not license_ref_allowed.match(final_token): + message = f"Invalid licenseref: {final_token!r}" + raise InvalidLicenseExpression(message) + normalized_tokens.append(license_refs[final_token] + suffix) + else: + if final_token not in LICENSES: + message = f"Unknown license: {final_token!r}" + raise InvalidLicenseExpression(message) + normalized_tokens.append(LICENSES[final_token]["id"] + suffix) + + normalized_expression = " ".join(normalized_tokens) + + return cast( + NormalizedLicenseExpression, + normalized_expression.replace("( ", "(").replace(" )", ")"), + ) diff --git a/server/libs/packaging/licenses/_spdx.py b/server/libs/packaging/licenses/_spdx.py new file mode 100644 index 0000000..eac2227 --- /dev/null +++ b/server/libs/packaging/licenses/_spdx.py @@ -0,0 +1,759 @@ + +from __future__ import annotations + +from typing import TypedDict + +class SPDXLicense(TypedDict): + id: str + deprecated: bool + +class SPDXException(TypedDict): + id: str + deprecated: bool + + +VERSION = '3.25.0' + +LICENSES: dict[str, SPDXLicense] = { + '0bsd': {'id': '0BSD', 'deprecated': False}, + '3d-slicer-1.0': {'id': '3D-Slicer-1.0', 'deprecated': False}, + 'aal': {'id': 'AAL', 'deprecated': False}, + 'abstyles': {'id': 'Abstyles', 'deprecated': False}, + 'adacore-doc': {'id': 'AdaCore-doc', 'deprecated': False}, + 'adobe-2006': {'id': 'Adobe-2006', 'deprecated': False}, + 'adobe-display-postscript': {'id': 'Adobe-Display-PostScript', 'deprecated': False}, + 'adobe-glyph': {'id': 'Adobe-Glyph', 'deprecated': False}, + 'adobe-utopia': {'id': 'Adobe-Utopia', 'deprecated': False}, + 'adsl': {'id': 'ADSL', 'deprecated': False}, + 'afl-1.1': {'id': 'AFL-1.1', 'deprecated': False}, + 'afl-1.2': {'id': 'AFL-1.2', 'deprecated': False}, + 'afl-2.0': {'id': 'AFL-2.0', 'deprecated': False}, + 'afl-2.1': {'id': 'AFL-2.1', 'deprecated': False}, + 'afl-3.0': {'id': 'AFL-3.0', 'deprecated': False}, + 'afmparse': {'id': 'Afmparse', 'deprecated': False}, + 'agpl-1.0': {'id': 'AGPL-1.0', 'deprecated': True}, + 'agpl-1.0-only': {'id': 'AGPL-1.0-only', 'deprecated': False}, + 'agpl-1.0-or-later': {'id': 'AGPL-1.0-or-later', 'deprecated': False}, + 'agpl-3.0': {'id': 'AGPL-3.0', 'deprecated': True}, + 'agpl-3.0-only': {'id': 'AGPL-3.0-only', 'deprecated': False}, + 'agpl-3.0-or-later': {'id': 'AGPL-3.0-or-later', 'deprecated': False}, + 'aladdin': {'id': 'Aladdin', 'deprecated': False}, + 'amd-newlib': {'id': 'AMD-newlib', 'deprecated': False}, + 'amdplpa': {'id': 'AMDPLPA', 'deprecated': False}, + 'aml': {'id': 'AML', 'deprecated': False}, + 'aml-glslang': {'id': 'AML-glslang', 'deprecated': False}, + 'ampas': {'id': 'AMPAS', 'deprecated': False}, + 'antlr-pd': {'id': 'ANTLR-PD', 'deprecated': False}, + 'antlr-pd-fallback': {'id': 'ANTLR-PD-fallback', 'deprecated': False}, + 'any-osi': {'id': 'any-OSI', 'deprecated': False}, + 'apache-1.0': {'id': 'Apache-1.0', 'deprecated': False}, + 'apache-1.1': {'id': 'Apache-1.1', 'deprecated': False}, + 'apache-2.0': {'id': 'Apache-2.0', 'deprecated': False}, + 'apafml': {'id': 'APAFML', 'deprecated': False}, + 'apl-1.0': {'id': 'APL-1.0', 'deprecated': False}, + 'app-s2p': {'id': 'App-s2p', 'deprecated': False}, + 'apsl-1.0': {'id': 'APSL-1.0', 'deprecated': False}, + 'apsl-1.1': {'id': 'APSL-1.1', 'deprecated': False}, + 'apsl-1.2': {'id': 'APSL-1.2', 'deprecated': False}, + 'apsl-2.0': {'id': 'APSL-2.0', 'deprecated': False}, + 'arphic-1999': {'id': 'Arphic-1999', 'deprecated': False}, + 'artistic-1.0': {'id': 'Artistic-1.0', 'deprecated': False}, + 'artistic-1.0-cl8': {'id': 'Artistic-1.0-cl8', 'deprecated': False}, + 'artistic-1.0-perl': {'id': 'Artistic-1.0-Perl', 'deprecated': False}, + 'artistic-2.0': {'id': 'Artistic-2.0', 'deprecated': False}, + 'aswf-digital-assets-1.0': {'id': 'ASWF-Digital-Assets-1.0', 'deprecated': False}, + 'aswf-digital-assets-1.1': {'id': 'ASWF-Digital-Assets-1.1', 'deprecated': False}, + 'baekmuk': {'id': 'Baekmuk', 'deprecated': False}, + 'bahyph': {'id': 'Bahyph', 'deprecated': False}, + 'barr': {'id': 'Barr', 'deprecated': False}, + 'bcrypt-solar-designer': {'id': 'bcrypt-Solar-Designer', 'deprecated': False}, + 'beerware': {'id': 'Beerware', 'deprecated': False}, + 'bitstream-charter': {'id': 'Bitstream-Charter', 'deprecated': False}, + 'bitstream-vera': {'id': 'Bitstream-Vera', 'deprecated': False}, + 'bittorrent-1.0': {'id': 'BitTorrent-1.0', 'deprecated': False}, + 'bittorrent-1.1': {'id': 'BitTorrent-1.1', 'deprecated': False}, + 'blessing': {'id': 'blessing', 'deprecated': False}, + 'blueoak-1.0.0': {'id': 'BlueOak-1.0.0', 'deprecated': False}, + 'boehm-gc': {'id': 'Boehm-GC', 'deprecated': False}, + 'borceux': {'id': 'Borceux', 'deprecated': False}, + 'brian-gladman-2-clause': {'id': 'Brian-Gladman-2-Clause', 'deprecated': False}, + 'brian-gladman-3-clause': {'id': 'Brian-Gladman-3-Clause', 'deprecated': False}, + 'bsd-1-clause': {'id': 'BSD-1-Clause', 'deprecated': False}, + 'bsd-2-clause': {'id': 'BSD-2-Clause', 'deprecated': False}, + 'bsd-2-clause-darwin': {'id': 'BSD-2-Clause-Darwin', 'deprecated': False}, + 'bsd-2-clause-first-lines': {'id': 'BSD-2-Clause-first-lines', 'deprecated': False}, + 'bsd-2-clause-freebsd': {'id': 'BSD-2-Clause-FreeBSD', 'deprecated': True}, + 'bsd-2-clause-netbsd': {'id': 'BSD-2-Clause-NetBSD', 'deprecated': True}, + 'bsd-2-clause-patent': {'id': 'BSD-2-Clause-Patent', 'deprecated': False}, + 'bsd-2-clause-views': {'id': 'BSD-2-Clause-Views', 'deprecated': False}, + 'bsd-3-clause': {'id': 'BSD-3-Clause', 'deprecated': False}, + 'bsd-3-clause-acpica': {'id': 'BSD-3-Clause-acpica', 'deprecated': False}, + 'bsd-3-clause-attribution': {'id': 'BSD-3-Clause-Attribution', 'deprecated': False}, + 'bsd-3-clause-clear': {'id': 'BSD-3-Clause-Clear', 'deprecated': False}, + 'bsd-3-clause-flex': {'id': 'BSD-3-Clause-flex', 'deprecated': False}, + 'bsd-3-clause-hp': {'id': 'BSD-3-Clause-HP', 'deprecated': False}, + 'bsd-3-clause-lbnl': {'id': 'BSD-3-Clause-LBNL', 'deprecated': False}, + 'bsd-3-clause-modification': {'id': 'BSD-3-Clause-Modification', 'deprecated': False}, + 'bsd-3-clause-no-military-license': {'id': 'BSD-3-Clause-No-Military-License', 'deprecated': False}, + 'bsd-3-clause-no-nuclear-license': {'id': 'BSD-3-Clause-No-Nuclear-License', 'deprecated': False}, + 'bsd-3-clause-no-nuclear-license-2014': {'id': 'BSD-3-Clause-No-Nuclear-License-2014', 'deprecated': False}, + 'bsd-3-clause-no-nuclear-warranty': {'id': 'BSD-3-Clause-No-Nuclear-Warranty', 'deprecated': False}, + 'bsd-3-clause-open-mpi': {'id': 'BSD-3-Clause-Open-MPI', 'deprecated': False}, + 'bsd-3-clause-sun': {'id': 'BSD-3-Clause-Sun', 'deprecated': False}, + 'bsd-4-clause': {'id': 'BSD-4-Clause', 'deprecated': False}, + 'bsd-4-clause-shortened': {'id': 'BSD-4-Clause-Shortened', 'deprecated': False}, + 'bsd-4-clause-uc': {'id': 'BSD-4-Clause-UC', 'deprecated': False}, + 'bsd-4.3reno': {'id': 'BSD-4.3RENO', 'deprecated': False}, + 'bsd-4.3tahoe': {'id': 'BSD-4.3TAHOE', 'deprecated': False}, + 'bsd-advertising-acknowledgement': {'id': 'BSD-Advertising-Acknowledgement', 'deprecated': False}, + 'bsd-attribution-hpnd-disclaimer': {'id': 'BSD-Attribution-HPND-disclaimer', 'deprecated': False}, + 'bsd-inferno-nettverk': {'id': 'BSD-Inferno-Nettverk', 'deprecated': False}, + 'bsd-protection': {'id': 'BSD-Protection', 'deprecated': False}, + 'bsd-source-beginning-file': {'id': 'BSD-Source-beginning-file', 'deprecated': False}, + 'bsd-source-code': {'id': 'BSD-Source-Code', 'deprecated': False}, + 'bsd-systemics': {'id': 'BSD-Systemics', 'deprecated': False}, + 'bsd-systemics-w3works': {'id': 'BSD-Systemics-W3Works', 'deprecated': False}, + 'bsl-1.0': {'id': 'BSL-1.0', 'deprecated': False}, + 'busl-1.1': {'id': 'BUSL-1.1', 'deprecated': False}, + 'bzip2-1.0.5': {'id': 'bzip2-1.0.5', 'deprecated': True}, + 'bzip2-1.0.6': {'id': 'bzip2-1.0.6', 'deprecated': False}, + 'c-uda-1.0': {'id': 'C-UDA-1.0', 'deprecated': False}, + 'cal-1.0': {'id': 'CAL-1.0', 'deprecated': False}, + 'cal-1.0-combined-work-exception': {'id': 'CAL-1.0-Combined-Work-Exception', 'deprecated': False}, + 'caldera': {'id': 'Caldera', 'deprecated': False}, + 'caldera-no-preamble': {'id': 'Caldera-no-preamble', 'deprecated': False}, + 'catharon': {'id': 'Catharon', 'deprecated': False}, + 'catosl-1.1': {'id': 'CATOSL-1.1', 'deprecated': False}, + 'cc-by-1.0': {'id': 'CC-BY-1.0', 'deprecated': False}, + 'cc-by-2.0': {'id': 'CC-BY-2.0', 'deprecated': False}, + 'cc-by-2.5': {'id': 'CC-BY-2.5', 'deprecated': False}, + 'cc-by-2.5-au': {'id': 'CC-BY-2.5-AU', 'deprecated': False}, + 'cc-by-3.0': {'id': 'CC-BY-3.0', 'deprecated': False}, + 'cc-by-3.0-at': {'id': 'CC-BY-3.0-AT', 'deprecated': False}, + 'cc-by-3.0-au': {'id': 'CC-BY-3.0-AU', 'deprecated': False}, + 'cc-by-3.0-de': {'id': 'CC-BY-3.0-DE', 'deprecated': False}, + 'cc-by-3.0-igo': {'id': 'CC-BY-3.0-IGO', 'deprecated': False}, + 'cc-by-3.0-nl': {'id': 'CC-BY-3.0-NL', 'deprecated': False}, + 'cc-by-3.0-us': {'id': 'CC-BY-3.0-US', 'deprecated': False}, + 'cc-by-4.0': {'id': 'CC-BY-4.0', 'deprecated': False}, + 'cc-by-nc-1.0': {'id': 'CC-BY-NC-1.0', 'deprecated': False}, + 'cc-by-nc-2.0': {'id': 'CC-BY-NC-2.0', 'deprecated': False}, + 'cc-by-nc-2.5': {'id': 'CC-BY-NC-2.5', 'deprecated': False}, + 'cc-by-nc-3.0': {'id': 'CC-BY-NC-3.0', 'deprecated': False}, + 'cc-by-nc-3.0-de': {'id': 'CC-BY-NC-3.0-DE', 'deprecated': False}, + 'cc-by-nc-4.0': {'id': 'CC-BY-NC-4.0', 'deprecated': False}, + 'cc-by-nc-nd-1.0': {'id': 'CC-BY-NC-ND-1.0', 'deprecated': False}, + 'cc-by-nc-nd-2.0': {'id': 'CC-BY-NC-ND-2.0', 'deprecated': False}, + 'cc-by-nc-nd-2.5': {'id': 'CC-BY-NC-ND-2.5', 'deprecated': False}, + 'cc-by-nc-nd-3.0': {'id': 'CC-BY-NC-ND-3.0', 'deprecated': False}, + 'cc-by-nc-nd-3.0-de': {'id': 'CC-BY-NC-ND-3.0-DE', 'deprecated': False}, + 'cc-by-nc-nd-3.0-igo': {'id': 'CC-BY-NC-ND-3.0-IGO', 'deprecated': False}, + 'cc-by-nc-nd-4.0': {'id': 'CC-BY-NC-ND-4.0', 'deprecated': False}, + 'cc-by-nc-sa-1.0': {'id': 'CC-BY-NC-SA-1.0', 'deprecated': False}, + 'cc-by-nc-sa-2.0': {'id': 'CC-BY-NC-SA-2.0', 'deprecated': False}, + 'cc-by-nc-sa-2.0-de': {'id': 'CC-BY-NC-SA-2.0-DE', 'deprecated': False}, + 'cc-by-nc-sa-2.0-fr': {'id': 'CC-BY-NC-SA-2.0-FR', 'deprecated': False}, + 'cc-by-nc-sa-2.0-uk': {'id': 'CC-BY-NC-SA-2.0-UK', 'deprecated': False}, + 'cc-by-nc-sa-2.5': {'id': 'CC-BY-NC-SA-2.5', 'deprecated': False}, + 'cc-by-nc-sa-3.0': {'id': 'CC-BY-NC-SA-3.0', 'deprecated': False}, + 'cc-by-nc-sa-3.0-de': {'id': 'CC-BY-NC-SA-3.0-DE', 'deprecated': False}, + 'cc-by-nc-sa-3.0-igo': {'id': 'CC-BY-NC-SA-3.0-IGO', 'deprecated': False}, + 'cc-by-nc-sa-4.0': {'id': 'CC-BY-NC-SA-4.0', 'deprecated': False}, + 'cc-by-nd-1.0': {'id': 'CC-BY-ND-1.0', 'deprecated': False}, + 'cc-by-nd-2.0': {'id': 'CC-BY-ND-2.0', 'deprecated': False}, + 'cc-by-nd-2.5': {'id': 'CC-BY-ND-2.5', 'deprecated': False}, + 'cc-by-nd-3.0': {'id': 'CC-BY-ND-3.0', 'deprecated': False}, + 'cc-by-nd-3.0-de': {'id': 'CC-BY-ND-3.0-DE', 'deprecated': False}, + 'cc-by-nd-4.0': {'id': 'CC-BY-ND-4.0', 'deprecated': False}, + 'cc-by-sa-1.0': {'id': 'CC-BY-SA-1.0', 'deprecated': False}, + 'cc-by-sa-2.0': {'id': 'CC-BY-SA-2.0', 'deprecated': False}, + 'cc-by-sa-2.0-uk': {'id': 'CC-BY-SA-2.0-UK', 'deprecated': False}, + 'cc-by-sa-2.1-jp': {'id': 'CC-BY-SA-2.1-JP', 'deprecated': False}, + 'cc-by-sa-2.5': {'id': 'CC-BY-SA-2.5', 'deprecated': False}, + 'cc-by-sa-3.0': {'id': 'CC-BY-SA-3.0', 'deprecated': False}, + 'cc-by-sa-3.0-at': {'id': 'CC-BY-SA-3.0-AT', 'deprecated': False}, + 'cc-by-sa-3.0-de': {'id': 'CC-BY-SA-3.0-DE', 'deprecated': False}, + 'cc-by-sa-3.0-igo': {'id': 'CC-BY-SA-3.0-IGO', 'deprecated': False}, + 'cc-by-sa-4.0': {'id': 'CC-BY-SA-4.0', 'deprecated': False}, + 'cc-pddc': {'id': 'CC-PDDC', 'deprecated': False}, + 'cc0-1.0': {'id': 'CC0-1.0', 'deprecated': False}, + 'cddl-1.0': {'id': 'CDDL-1.0', 'deprecated': False}, + 'cddl-1.1': {'id': 'CDDL-1.1', 'deprecated': False}, + 'cdl-1.0': {'id': 'CDL-1.0', 'deprecated': False}, + 'cdla-permissive-1.0': {'id': 'CDLA-Permissive-1.0', 'deprecated': False}, + 'cdla-permissive-2.0': {'id': 'CDLA-Permissive-2.0', 'deprecated': False}, + 'cdla-sharing-1.0': {'id': 'CDLA-Sharing-1.0', 'deprecated': False}, + 'cecill-1.0': {'id': 'CECILL-1.0', 'deprecated': False}, + 'cecill-1.1': {'id': 'CECILL-1.1', 'deprecated': False}, + 'cecill-2.0': {'id': 'CECILL-2.0', 'deprecated': False}, + 'cecill-2.1': {'id': 'CECILL-2.1', 'deprecated': False}, + 'cecill-b': {'id': 'CECILL-B', 'deprecated': False}, + 'cecill-c': {'id': 'CECILL-C', 'deprecated': False}, + 'cern-ohl-1.1': {'id': 'CERN-OHL-1.1', 'deprecated': False}, + 'cern-ohl-1.2': {'id': 'CERN-OHL-1.2', 'deprecated': False}, + 'cern-ohl-p-2.0': {'id': 'CERN-OHL-P-2.0', 'deprecated': False}, + 'cern-ohl-s-2.0': {'id': 'CERN-OHL-S-2.0', 'deprecated': False}, + 'cern-ohl-w-2.0': {'id': 'CERN-OHL-W-2.0', 'deprecated': False}, + 'cfitsio': {'id': 'CFITSIO', 'deprecated': False}, + 'check-cvs': {'id': 'check-cvs', 'deprecated': False}, + 'checkmk': {'id': 'checkmk', 'deprecated': False}, + 'clartistic': {'id': 'ClArtistic', 'deprecated': False}, + 'clips': {'id': 'Clips', 'deprecated': False}, + 'cmu-mach': {'id': 'CMU-Mach', 'deprecated': False}, + 'cmu-mach-nodoc': {'id': 'CMU-Mach-nodoc', 'deprecated': False}, + 'cnri-jython': {'id': 'CNRI-Jython', 'deprecated': False}, + 'cnri-python': {'id': 'CNRI-Python', 'deprecated': False}, + 'cnri-python-gpl-compatible': {'id': 'CNRI-Python-GPL-Compatible', 'deprecated': False}, + 'coil-1.0': {'id': 'COIL-1.0', 'deprecated': False}, + 'community-spec-1.0': {'id': 'Community-Spec-1.0', 'deprecated': False}, + 'condor-1.1': {'id': 'Condor-1.1', 'deprecated': False}, + 'copyleft-next-0.3.0': {'id': 'copyleft-next-0.3.0', 'deprecated': False}, + 'copyleft-next-0.3.1': {'id': 'copyleft-next-0.3.1', 'deprecated': False}, + 'cornell-lossless-jpeg': {'id': 'Cornell-Lossless-JPEG', 'deprecated': False}, + 'cpal-1.0': {'id': 'CPAL-1.0', 'deprecated': False}, + 'cpl-1.0': {'id': 'CPL-1.0', 'deprecated': False}, + 'cpol-1.02': {'id': 'CPOL-1.02', 'deprecated': False}, + 'cronyx': {'id': 'Cronyx', 'deprecated': False}, + 'crossword': {'id': 'Crossword', 'deprecated': False}, + 'crystalstacker': {'id': 'CrystalStacker', 'deprecated': False}, + 'cua-opl-1.0': {'id': 'CUA-OPL-1.0', 'deprecated': False}, + 'cube': {'id': 'Cube', 'deprecated': False}, + 'curl': {'id': 'curl', 'deprecated': False}, + 'cve-tou': {'id': 'cve-tou', 'deprecated': False}, + 'd-fsl-1.0': {'id': 'D-FSL-1.0', 'deprecated': False}, + 'dec-3-clause': {'id': 'DEC-3-Clause', 'deprecated': False}, + 'diffmark': {'id': 'diffmark', 'deprecated': False}, + 'dl-de-by-2.0': {'id': 'DL-DE-BY-2.0', 'deprecated': False}, + 'dl-de-zero-2.0': {'id': 'DL-DE-ZERO-2.0', 'deprecated': False}, + 'doc': {'id': 'DOC', 'deprecated': False}, + 'docbook-schema': {'id': 'DocBook-Schema', 'deprecated': False}, + 'docbook-xml': {'id': 'DocBook-XML', 'deprecated': False}, + 'dotseqn': {'id': 'Dotseqn', 'deprecated': False}, + 'drl-1.0': {'id': 'DRL-1.0', 'deprecated': False}, + 'drl-1.1': {'id': 'DRL-1.1', 'deprecated': False}, + 'dsdp': {'id': 'DSDP', 'deprecated': False}, + 'dtoa': {'id': 'dtoa', 'deprecated': False}, + 'dvipdfm': {'id': 'dvipdfm', 'deprecated': False}, + 'ecl-1.0': {'id': 'ECL-1.0', 'deprecated': False}, + 'ecl-2.0': {'id': 'ECL-2.0', 'deprecated': False}, + 'ecos-2.0': {'id': 'eCos-2.0', 'deprecated': True}, + 'efl-1.0': {'id': 'EFL-1.0', 'deprecated': False}, + 'efl-2.0': {'id': 'EFL-2.0', 'deprecated': False}, + 'egenix': {'id': 'eGenix', 'deprecated': False}, + 'elastic-2.0': {'id': 'Elastic-2.0', 'deprecated': False}, + 'entessa': {'id': 'Entessa', 'deprecated': False}, + 'epics': {'id': 'EPICS', 'deprecated': False}, + 'epl-1.0': {'id': 'EPL-1.0', 'deprecated': False}, + 'epl-2.0': {'id': 'EPL-2.0', 'deprecated': False}, + 'erlpl-1.1': {'id': 'ErlPL-1.1', 'deprecated': False}, + 'etalab-2.0': {'id': 'etalab-2.0', 'deprecated': False}, + 'eudatagrid': {'id': 'EUDatagrid', 'deprecated': False}, + 'eupl-1.0': {'id': 'EUPL-1.0', 'deprecated': False}, + 'eupl-1.1': {'id': 'EUPL-1.1', 'deprecated': False}, + 'eupl-1.2': {'id': 'EUPL-1.2', 'deprecated': False}, + 'eurosym': {'id': 'Eurosym', 'deprecated': False}, + 'fair': {'id': 'Fair', 'deprecated': False}, + 'fbm': {'id': 'FBM', 'deprecated': False}, + 'fdk-aac': {'id': 'FDK-AAC', 'deprecated': False}, + 'ferguson-twofish': {'id': 'Ferguson-Twofish', 'deprecated': False}, + 'frameworx-1.0': {'id': 'Frameworx-1.0', 'deprecated': False}, + 'freebsd-doc': {'id': 'FreeBSD-DOC', 'deprecated': False}, + 'freeimage': {'id': 'FreeImage', 'deprecated': False}, + 'fsfap': {'id': 'FSFAP', 'deprecated': False}, + 'fsfap-no-warranty-disclaimer': {'id': 'FSFAP-no-warranty-disclaimer', 'deprecated': False}, + 'fsful': {'id': 'FSFUL', 'deprecated': False}, + 'fsfullr': {'id': 'FSFULLR', 'deprecated': False}, + 'fsfullrwd': {'id': 'FSFULLRWD', 'deprecated': False}, + 'ftl': {'id': 'FTL', 'deprecated': False}, + 'furuseth': {'id': 'Furuseth', 'deprecated': False}, + 'fwlw': {'id': 'fwlw', 'deprecated': False}, + 'gcr-docs': {'id': 'GCR-docs', 'deprecated': False}, + 'gd': {'id': 'GD', 'deprecated': False}, + 'gfdl-1.1': {'id': 'GFDL-1.1', 'deprecated': True}, + 'gfdl-1.1-invariants-only': {'id': 'GFDL-1.1-invariants-only', 'deprecated': False}, + 'gfdl-1.1-invariants-or-later': {'id': 'GFDL-1.1-invariants-or-later', 'deprecated': False}, + 'gfdl-1.1-no-invariants-only': {'id': 'GFDL-1.1-no-invariants-only', 'deprecated': False}, + 'gfdl-1.1-no-invariants-or-later': {'id': 'GFDL-1.1-no-invariants-or-later', 'deprecated': False}, + 'gfdl-1.1-only': {'id': 'GFDL-1.1-only', 'deprecated': False}, + 'gfdl-1.1-or-later': {'id': 'GFDL-1.1-or-later', 'deprecated': False}, + 'gfdl-1.2': {'id': 'GFDL-1.2', 'deprecated': True}, + 'gfdl-1.2-invariants-only': {'id': 'GFDL-1.2-invariants-only', 'deprecated': False}, + 'gfdl-1.2-invariants-or-later': {'id': 'GFDL-1.2-invariants-or-later', 'deprecated': False}, + 'gfdl-1.2-no-invariants-only': {'id': 'GFDL-1.2-no-invariants-only', 'deprecated': False}, + 'gfdl-1.2-no-invariants-or-later': {'id': 'GFDL-1.2-no-invariants-or-later', 'deprecated': False}, + 'gfdl-1.2-only': {'id': 'GFDL-1.2-only', 'deprecated': False}, + 'gfdl-1.2-or-later': {'id': 'GFDL-1.2-or-later', 'deprecated': False}, + 'gfdl-1.3': {'id': 'GFDL-1.3', 'deprecated': True}, + 'gfdl-1.3-invariants-only': {'id': 'GFDL-1.3-invariants-only', 'deprecated': False}, + 'gfdl-1.3-invariants-or-later': {'id': 'GFDL-1.3-invariants-or-later', 'deprecated': False}, + 'gfdl-1.3-no-invariants-only': {'id': 'GFDL-1.3-no-invariants-only', 'deprecated': False}, + 'gfdl-1.3-no-invariants-or-later': {'id': 'GFDL-1.3-no-invariants-or-later', 'deprecated': False}, + 'gfdl-1.3-only': {'id': 'GFDL-1.3-only', 'deprecated': False}, + 'gfdl-1.3-or-later': {'id': 'GFDL-1.3-or-later', 'deprecated': False}, + 'giftware': {'id': 'Giftware', 'deprecated': False}, + 'gl2ps': {'id': 'GL2PS', 'deprecated': False}, + 'glide': {'id': 'Glide', 'deprecated': False}, + 'glulxe': {'id': 'Glulxe', 'deprecated': False}, + 'glwtpl': {'id': 'GLWTPL', 'deprecated': False}, + 'gnuplot': {'id': 'gnuplot', 'deprecated': False}, + 'gpl-1.0': {'id': 'GPL-1.0', 'deprecated': True}, + 'gpl-1.0+': {'id': 'GPL-1.0+', 'deprecated': True}, + 'gpl-1.0-only': {'id': 'GPL-1.0-only', 'deprecated': False}, + 'gpl-1.0-or-later': {'id': 'GPL-1.0-or-later', 'deprecated': False}, + 'gpl-2.0': {'id': 'GPL-2.0', 'deprecated': True}, + 'gpl-2.0+': {'id': 'GPL-2.0+', 'deprecated': True}, + 'gpl-2.0-only': {'id': 'GPL-2.0-only', 'deprecated': False}, + 'gpl-2.0-or-later': {'id': 'GPL-2.0-or-later', 'deprecated': False}, + 'gpl-2.0-with-autoconf-exception': {'id': 'GPL-2.0-with-autoconf-exception', 'deprecated': True}, + 'gpl-2.0-with-bison-exception': {'id': 'GPL-2.0-with-bison-exception', 'deprecated': True}, + 'gpl-2.0-with-classpath-exception': {'id': 'GPL-2.0-with-classpath-exception', 'deprecated': True}, + 'gpl-2.0-with-font-exception': {'id': 'GPL-2.0-with-font-exception', 'deprecated': True}, + 'gpl-2.0-with-gcc-exception': {'id': 'GPL-2.0-with-GCC-exception', 'deprecated': True}, + 'gpl-3.0': {'id': 'GPL-3.0', 'deprecated': True}, + 'gpl-3.0+': {'id': 'GPL-3.0+', 'deprecated': True}, + 'gpl-3.0-only': {'id': 'GPL-3.0-only', 'deprecated': False}, + 'gpl-3.0-or-later': {'id': 'GPL-3.0-or-later', 'deprecated': False}, + 'gpl-3.0-with-autoconf-exception': {'id': 'GPL-3.0-with-autoconf-exception', 'deprecated': True}, + 'gpl-3.0-with-gcc-exception': {'id': 'GPL-3.0-with-GCC-exception', 'deprecated': True}, + 'graphics-gems': {'id': 'Graphics-Gems', 'deprecated': False}, + 'gsoap-1.3b': {'id': 'gSOAP-1.3b', 'deprecated': False}, + 'gtkbook': {'id': 'gtkbook', 'deprecated': False}, + 'gutmann': {'id': 'Gutmann', 'deprecated': False}, + 'haskellreport': {'id': 'HaskellReport', 'deprecated': False}, + 'hdparm': {'id': 'hdparm', 'deprecated': False}, + 'hidapi': {'id': 'HIDAPI', 'deprecated': False}, + 'hippocratic-2.1': {'id': 'Hippocratic-2.1', 'deprecated': False}, + 'hp-1986': {'id': 'HP-1986', 'deprecated': False}, + 'hp-1989': {'id': 'HP-1989', 'deprecated': False}, + 'hpnd': {'id': 'HPND', 'deprecated': False}, + 'hpnd-dec': {'id': 'HPND-DEC', 'deprecated': False}, + 'hpnd-doc': {'id': 'HPND-doc', 'deprecated': False}, + 'hpnd-doc-sell': {'id': 'HPND-doc-sell', 'deprecated': False}, + 'hpnd-export-us': {'id': 'HPND-export-US', 'deprecated': False}, + 'hpnd-export-us-acknowledgement': {'id': 'HPND-export-US-acknowledgement', 'deprecated': False}, + 'hpnd-export-us-modify': {'id': 'HPND-export-US-modify', 'deprecated': False}, + 'hpnd-export2-us': {'id': 'HPND-export2-US', 'deprecated': False}, + 'hpnd-fenneberg-livingston': {'id': 'HPND-Fenneberg-Livingston', 'deprecated': False}, + 'hpnd-inria-imag': {'id': 'HPND-INRIA-IMAG', 'deprecated': False}, + 'hpnd-intel': {'id': 'HPND-Intel', 'deprecated': False}, + 'hpnd-kevlin-henney': {'id': 'HPND-Kevlin-Henney', 'deprecated': False}, + 'hpnd-markus-kuhn': {'id': 'HPND-Markus-Kuhn', 'deprecated': False}, + 'hpnd-merchantability-variant': {'id': 'HPND-merchantability-variant', 'deprecated': False}, + 'hpnd-mit-disclaimer': {'id': 'HPND-MIT-disclaimer', 'deprecated': False}, + 'hpnd-netrek': {'id': 'HPND-Netrek', 'deprecated': False}, + 'hpnd-pbmplus': {'id': 'HPND-Pbmplus', 'deprecated': False}, + 'hpnd-sell-mit-disclaimer-xserver': {'id': 'HPND-sell-MIT-disclaimer-xserver', 'deprecated': False}, + 'hpnd-sell-regexpr': {'id': 'HPND-sell-regexpr', 'deprecated': False}, + 'hpnd-sell-variant': {'id': 'HPND-sell-variant', 'deprecated': False}, + 'hpnd-sell-variant-mit-disclaimer': {'id': 'HPND-sell-variant-MIT-disclaimer', 'deprecated': False}, + 'hpnd-sell-variant-mit-disclaimer-rev': {'id': 'HPND-sell-variant-MIT-disclaimer-rev', 'deprecated': False}, + 'hpnd-uc': {'id': 'HPND-UC', 'deprecated': False}, + 'hpnd-uc-export-us': {'id': 'HPND-UC-export-US', 'deprecated': False}, + 'htmltidy': {'id': 'HTMLTIDY', 'deprecated': False}, + 'ibm-pibs': {'id': 'IBM-pibs', 'deprecated': False}, + 'icu': {'id': 'ICU', 'deprecated': False}, + 'iec-code-components-eula': {'id': 'IEC-Code-Components-EULA', 'deprecated': False}, + 'ijg': {'id': 'IJG', 'deprecated': False}, + 'ijg-short': {'id': 'IJG-short', 'deprecated': False}, + 'imagemagick': {'id': 'ImageMagick', 'deprecated': False}, + 'imatix': {'id': 'iMatix', 'deprecated': False}, + 'imlib2': {'id': 'Imlib2', 'deprecated': False}, + 'info-zip': {'id': 'Info-ZIP', 'deprecated': False}, + 'inner-net-2.0': {'id': 'Inner-Net-2.0', 'deprecated': False}, + 'intel': {'id': 'Intel', 'deprecated': False}, + 'intel-acpi': {'id': 'Intel-ACPI', 'deprecated': False}, + 'interbase-1.0': {'id': 'Interbase-1.0', 'deprecated': False}, + 'ipa': {'id': 'IPA', 'deprecated': False}, + 'ipl-1.0': {'id': 'IPL-1.0', 'deprecated': False}, + 'isc': {'id': 'ISC', 'deprecated': False}, + 'isc-veillard': {'id': 'ISC-Veillard', 'deprecated': False}, + 'jam': {'id': 'Jam', 'deprecated': False}, + 'jasper-2.0': {'id': 'JasPer-2.0', 'deprecated': False}, + 'jpl-image': {'id': 'JPL-image', 'deprecated': False}, + 'jpnic': {'id': 'JPNIC', 'deprecated': False}, + 'json': {'id': 'JSON', 'deprecated': False}, + 'kastrup': {'id': 'Kastrup', 'deprecated': False}, + 'kazlib': {'id': 'Kazlib', 'deprecated': False}, + 'knuth-ctan': {'id': 'Knuth-CTAN', 'deprecated': False}, + 'lal-1.2': {'id': 'LAL-1.2', 'deprecated': False}, + 'lal-1.3': {'id': 'LAL-1.3', 'deprecated': False}, + 'latex2e': {'id': 'Latex2e', 'deprecated': False}, + 'latex2e-translated-notice': {'id': 'Latex2e-translated-notice', 'deprecated': False}, + 'leptonica': {'id': 'Leptonica', 'deprecated': False}, + 'lgpl-2.0': {'id': 'LGPL-2.0', 'deprecated': True}, + 'lgpl-2.0+': {'id': 'LGPL-2.0+', 'deprecated': True}, + 'lgpl-2.0-only': {'id': 'LGPL-2.0-only', 'deprecated': False}, + 'lgpl-2.0-or-later': {'id': 'LGPL-2.0-or-later', 'deprecated': False}, + 'lgpl-2.1': {'id': 'LGPL-2.1', 'deprecated': True}, + 'lgpl-2.1+': {'id': 'LGPL-2.1+', 'deprecated': True}, + 'lgpl-2.1-only': {'id': 'LGPL-2.1-only', 'deprecated': False}, + 'lgpl-2.1-or-later': {'id': 'LGPL-2.1-or-later', 'deprecated': False}, + 'lgpl-3.0': {'id': 'LGPL-3.0', 'deprecated': True}, + 'lgpl-3.0+': {'id': 'LGPL-3.0+', 'deprecated': True}, + 'lgpl-3.0-only': {'id': 'LGPL-3.0-only', 'deprecated': False}, + 'lgpl-3.0-or-later': {'id': 'LGPL-3.0-or-later', 'deprecated': False}, + 'lgpllr': {'id': 'LGPLLR', 'deprecated': False}, + 'libpng': {'id': 'Libpng', 'deprecated': False}, + 'libpng-2.0': {'id': 'libpng-2.0', 'deprecated': False}, + 'libselinux-1.0': {'id': 'libselinux-1.0', 'deprecated': False}, + 'libtiff': {'id': 'libtiff', 'deprecated': False}, + 'libutil-david-nugent': {'id': 'libutil-David-Nugent', 'deprecated': False}, + 'liliq-p-1.1': {'id': 'LiLiQ-P-1.1', 'deprecated': False}, + 'liliq-r-1.1': {'id': 'LiLiQ-R-1.1', 'deprecated': False}, + 'liliq-rplus-1.1': {'id': 'LiLiQ-Rplus-1.1', 'deprecated': False}, + 'linux-man-pages-1-para': {'id': 'Linux-man-pages-1-para', 'deprecated': False}, + 'linux-man-pages-copyleft': {'id': 'Linux-man-pages-copyleft', 'deprecated': False}, + 'linux-man-pages-copyleft-2-para': {'id': 'Linux-man-pages-copyleft-2-para', 'deprecated': False}, + 'linux-man-pages-copyleft-var': {'id': 'Linux-man-pages-copyleft-var', 'deprecated': False}, + 'linux-openib': {'id': 'Linux-OpenIB', 'deprecated': False}, + 'loop': {'id': 'LOOP', 'deprecated': False}, + 'lpd-document': {'id': 'LPD-document', 'deprecated': False}, + 'lpl-1.0': {'id': 'LPL-1.0', 'deprecated': False}, + 'lpl-1.02': {'id': 'LPL-1.02', 'deprecated': False}, + 'lppl-1.0': {'id': 'LPPL-1.0', 'deprecated': False}, + 'lppl-1.1': {'id': 'LPPL-1.1', 'deprecated': False}, + 'lppl-1.2': {'id': 'LPPL-1.2', 'deprecated': False}, + 'lppl-1.3a': {'id': 'LPPL-1.3a', 'deprecated': False}, + 'lppl-1.3c': {'id': 'LPPL-1.3c', 'deprecated': False}, + 'lsof': {'id': 'lsof', 'deprecated': False}, + 'lucida-bitmap-fonts': {'id': 'Lucida-Bitmap-Fonts', 'deprecated': False}, + 'lzma-sdk-9.11-to-9.20': {'id': 'LZMA-SDK-9.11-to-9.20', 'deprecated': False}, + 'lzma-sdk-9.22': {'id': 'LZMA-SDK-9.22', 'deprecated': False}, + 'mackerras-3-clause': {'id': 'Mackerras-3-Clause', 'deprecated': False}, + 'mackerras-3-clause-acknowledgment': {'id': 'Mackerras-3-Clause-acknowledgment', 'deprecated': False}, + 'magaz': {'id': 'magaz', 'deprecated': False}, + 'mailprio': {'id': 'mailprio', 'deprecated': False}, + 'makeindex': {'id': 'MakeIndex', 'deprecated': False}, + 'martin-birgmeier': {'id': 'Martin-Birgmeier', 'deprecated': False}, + 'mcphee-slideshow': {'id': 'McPhee-slideshow', 'deprecated': False}, + 'metamail': {'id': 'metamail', 'deprecated': False}, + 'minpack': {'id': 'Minpack', 'deprecated': False}, + 'miros': {'id': 'MirOS', 'deprecated': False}, + 'mit': {'id': 'MIT', 'deprecated': False}, + 'mit-0': {'id': 'MIT-0', 'deprecated': False}, + 'mit-advertising': {'id': 'MIT-advertising', 'deprecated': False}, + 'mit-cmu': {'id': 'MIT-CMU', 'deprecated': False}, + 'mit-enna': {'id': 'MIT-enna', 'deprecated': False}, + 'mit-feh': {'id': 'MIT-feh', 'deprecated': False}, + 'mit-festival': {'id': 'MIT-Festival', 'deprecated': False}, + 'mit-khronos-old': {'id': 'MIT-Khronos-old', 'deprecated': False}, + 'mit-modern-variant': {'id': 'MIT-Modern-Variant', 'deprecated': False}, + 'mit-open-group': {'id': 'MIT-open-group', 'deprecated': False}, + 'mit-testregex': {'id': 'MIT-testregex', 'deprecated': False}, + 'mit-wu': {'id': 'MIT-Wu', 'deprecated': False}, + 'mitnfa': {'id': 'MITNFA', 'deprecated': False}, + 'mmixware': {'id': 'MMIXware', 'deprecated': False}, + 'motosoto': {'id': 'Motosoto', 'deprecated': False}, + 'mpeg-ssg': {'id': 'MPEG-SSG', 'deprecated': False}, + 'mpi-permissive': {'id': 'mpi-permissive', 'deprecated': False}, + 'mpich2': {'id': 'mpich2', 'deprecated': False}, + 'mpl-1.0': {'id': 'MPL-1.0', 'deprecated': False}, + 'mpl-1.1': {'id': 'MPL-1.1', 'deprecated': False}, + 'mpl-2.0': {'id': 'MPL-2.0', 'deprecated': False}, + 'mpl-2.0-no-copyleft-exception': {'id': 'MPL-2.0-no-copyleft-exception', 'deprecated': False}, + 'mplus': {'id': 'mplus', 'deprecated': False}, + 'ms-lpl': {'id': 'MS-LPL', 'deprecated': False}, + 'ms-pl': {'id': 'MS-PL', 'deprecated': False}, + 'ms-rl': {'id': 'MS-RL', 'deprecated': False}, + 'mtll': {'id': 'MTLL', 'deprecated': False}, + 'mulanpsl-1.0': {'id': 'MulanPSL-1.0', 'deprecated': False}, + 'mulanpsl-2.0': {'id': 'MulanPSL-2.0', 'deprecated': False}, + 'multics': {'id': 'Multics', 'deprecated': False}, + 'mup': {'id': 'Mup', 'deprecated': False}, + 'naist-2003': {'id': 'NAIST-2003', 'deprecated': False}, + 'nasa-1.3': {'id': 'NASA-1.3', 'deprecated': False}, + 'naumen': {'id': 'Naumen', 'deprecated': False}, + 'nbpl-1.0': {'id': 'NBPL-1.0', 'deprecated': False}, + 'ncbi-pd': {'id': 'NCBI-PD', 'deprecated': False}, + 'ncgl-uk-2.0': {'id': 'NCGL-UK-2.0', 'deprecated': False}, + 'ncl': {'id': 'NCL', 'deprecated': False}, + 'ncsa': {'id': 'NCSA', 'deprecated': False}, + 'net-snmp': {'id': 'Net-SNMP', 'deprecated': True}, + 'netcdf': {'id': 'NetCDF', 'deprecated': False}, + 'newsletr': {'id': 'Newsletr', 'deprecated': False}, + 'ngpl': {'id': 'NGPL', 'deprecated': False}, + 'nicta-1.0': {'id': 'NICTA-1.0', 'deprecated': False}, + 'nist-pd': {'id': 'NIST-PD', 'deprecated': False}, + 'nist-pd-fallback': {'id': 'NIST-PD-fallback', 'deprecated': False}, + 'nist-software': {'id': 'NIST-Software', 'deprecated': False}, + 'nlod-1.0': {'id': 'NLOD-1.0', 'deprecated': False}, + 'nlod-2.0': {'id': 'NLOD-2.0', 'deprecated': False}, + 'nlpl': {'id': 'NLPL', 'deprecated': False}, + 'nokia': {'id': 'Nokia', 'deprecated': False}, + 'nosl': {'id': 'NOSL', 'deprecated': False}, + 'noweb': {'id': 'Noweb', 'deprecated': False}, + 'npl-1.0': {'id': 'NPL-1.0', 'deprecated': False}, + 'npl-1.1': {'id': 'NPL-1.1', 'deprecated': False}, + 'nposl-3.0': {'id': 'NPOSL-3.0', 'deprecated': False}, + 'nrl': {'id': 'NRL', 'deprecated': False}, + 'ntp': {'id': 'NTP', 'deprecated': False}, + 'ntp-0': {'id': 'NTP-0', 'deprecated': False}, + 'nunit': {'id': 'Nunit', 'deprecated': True}, + 'o-uda-1.0': {'id': 'O-UDA-1.0', 'deprecated': False}, + 'oar': {'id': 'OAR', 'deprecated': False}, + 'occt-pl': {'id': 'OCCT-PL', 'deprecated': False}, + 'oclc-2.0': {'id': 'OCLC-2.0', 'deprecated': False}, + 'odbl-1.0': {'id': 'ODbL-1.0', 'deprecated': False}, + 'odc-by-1.0': {'id': 'ODC-By-1.0', 'deprecated': False}, + 'offis': {'id': 'OFFIS', 'deprecated': False}, + 'ofl-1.0': {'id': 'OFL-1.0', 'deprecated': False}, + 'ofl-1.0-no-rfn': {'id': 'OFL-1.0-no-RFN', 'deprecated': False}, + 'ofl-1.0-rfn': {'id': 'OFL-1.0-RFN', 'deprecated': False}, + 'ofl-1.1': {'id': 'OFL-1.1', 'deprecated': False}, + 'ofl-1.1-no-rfn': {'id': 'OFL-1.1-no-RFN', 'deprecated': False}, + 'ofl-1.1-rfn': {'id': 'OFL-1.1-RFN', 'deprecated': False}, + 'ogc-1.0': {'id': 'OGC-1.0', 'deprecated': False}, + 'ogdl-taiwan-1.0': {'id': 'OGDL-Taiwan-1.0', 'deprecated': False}, + 'ogl-canada-2.0': {'id': 'OGL-Canada-2.0', 'deprecated': False}, + 'ogl-uk-1.0': {'id': 'OGL-UK-1.0', 'deprecated': False}, + 'ogl-uk-2.0': {'id': 'OGL-UK-2.0', 'deprecated': False}, + 'ogl-uk-3.0': {'id': 'OGL-UK-3.0', 'deprecated': False}, + 'ogtsl': {'id': 'OGTSL', 'deprecated': False}, + 'oldap-1.1': {'id': 'OLDAP-1.1', 'deprecated': False}, + 'oldap-1.2': {'id': 'OLDAP-1.2', 'deprecated': False}, + 'oldap-1.3': {'id': 'OLDAP-1.3', 'deprecated': False}, + 'oldap-1.4': {'id': 'OLDAP-1.4', 'deprecated': False}, + 'oldap-2.0': {'id': 'OLDAP-2.0', 'deprecated': False}, + 'oldap-2.0.1': {'id': 'OLDAP-2.0.1', 'deprecated': False}, + 'oldap-2.1': {'id': 'OLDAP-2.1', 'deprecated': False}, + 'oldap-2.2': {'id': 'OLDAP-2.2', 'deprecated': False}, + 'oldap-2.2.1': {'id': 'OLDAP-2.2.1', 'deprecated': False}, + 'oldap-2.2.2': {'id': 'OLDAP-2.2.2', 'deprecated': False}, + 'oldap-2.3': {'id': 'OLDAP-2.3', 'deprecated': False}, + 'oldap-2.4': {'id': 'OLDAP-2.4', 'deprecated': False}, + 'oldap-2.5': {'id': 'OLDAP-2.5', 'deprecated': False}, + 'oldap-2.6': {'id': 'OLDAP-2.6', 'deprecated': False}, + 'oldap-2.7': {'id': 'OLDAP-2.7', 'deprecated': False}, + 'oldap-2.8': {'id': 'OLDAP-2.8', 'deprecated': False}, + 'olfl-1.3': {'id': 'OLFL-1.3', 'deprecated': False}, + 'oml': {'id': 'OML', 'deprecated': False}, + 'openpbs-2.3': {'id': 'OpenPBS-2.3', 'deprecated': False}, + 'openssl': {'id': 'OpenSSL', 'deprecated': False}, + 'openssl-standalone': {'id': 'OpenSSL-standalone', 'deprecated': False}, + 'openvision': {'id': 'OpenVision', 'deprecated': False}, + 'opl-1.0': {'id': 'OPL-1.0', 'deprecated': False}, + 'opl-uk-3.0': {'id': 'OPL-UK-3.0', 'deprecated': False}, + 'opubl-1.0': {'id': 'OPUBL-1.0', 'deprecated': False}, + 'oset-pl-2.1': {'id': 'OSET-PL-2.1', 'deprecated': False}, + 'osl-1.0': {'id': 'OSL-1.0', 'deprecated': False}, + 'osl-1.1': {'id': 'OSL-1.1', 'deprecated': False}, + 'osl-2.0': {'id': 'OSL-2.0', 'deprecated': False}, + 'osl-2.1': {'id': 'OSL-2.1', 'deprecated': False}, + 'osl-3.0': {'id': 'OSL-3.0', 'deprecated': False}, + 'padl': {'id': 'PADL', 'deprecated': False}, + 'parity-6.0.0': {'id': 'Parity-6.0.0', 'deprecated': False}, + 'parity-7.0.0': {'id': 'Parity-7.0.0', 'deprecated': False}, + 'pddl-1.0': {'id': 'PDDL-1.0', 'deprecated': False}, + 'php-3.0': {'id': 'PHP-3.0', 'deprecated': False}, + 'php-3.01': {'id': 'PHP-3.01', 'deprecated': False}, + 'pixar': {'id': 'Pixar', 'deprecated': False}, + 'pkgconf': {'id': 'pkgconf', 'deprecated': False}, + 'plexus': {'id': 'Plexus', 'deprecated': False}, + 'pnmstitch': {'id': 'pnmstitch', 'deprecated': False}, + 'polyform-noncommercial-1.0.0': {'id': 'PolyForm-Noncommercial-1.0.0', 'deprecated': False}, + 'polyform-small-business-1.0.0': {'id': 'PolyForm-Small-Business-1.0.0', 'deprecated': False}, + 'postgresql': {'id': 'PostgreSQL', 'deprecated': False}, + 'ppl': {'id': 'PPL', 'deprecated': False}, + 'psf-2.0': {'id': 'PSF-2.0', 'deprecated': False}, + 'psfrag': {'id': 'psfrag', 'deprecated': False}, + 'psutils': {'id': 'psutils', 'deprecated': False}, + 'python-2.0': {'id': 'Python-2.0', 'deprecated': False}, + 'python-2.0.1': {'id': 'Python-2.0.1', 'deprecated': False}, + 'python-ldap': {'id': 'python-ldap', 'deprecated': False}, + 'qhull': {'id': 'Qhull', 'deprecated': False}, + 'qpl-1.0': {'id': 'QPL-1.0', 'deprecated': False}, + 'qpl-1.0-inria-2004': {'id': 'QPL-1.0-INRIA-2004', 'deprecated': False}, + 'radvd': {'id': 'radvd', 'deprecated': False}, + 'rdisc': {'id': 'Rdisc', 'deprecated': False}, + 'rhecos-1.1': {'id': 'RHeCos-1.1', 'deprecated': False}, + 'rpl-1.1': {'id': 'RPL-1.1', 'deprecated': False}, + 'rpl-1.5': {'id': 'RPL-1.5', 'deprecated': False}, + 'rpsl-1.0': {'id': 'RPSL-1.0', 'deprecated': False}, + 'rsa-md': {'id': 'RSA-MD', 'deprecated': False}, + 'rscpl': {'id': 'RSCPL', 'deprecated': False}, + 'ruby': {'id': 'Ruby', 'deprecated': False}, + 'ruby-pty': {'id': 'Ruby-pty', 'deprecated': False}, + 'sax-pd': {'id': 'SAX-PD', 'deprecated': False}, + 'sax-pd-2.0': {'id': 'SAX-PD-2.0', 'deprecated': False}, + 'saxpath': {'id': 'Saxpath', 'deprecated': False}, + 'scea': {'id': 'SCEA', 'deprecated': False}, + 'schemereport': {'id': 'SchemeReport', 'deprecated': False}, + 'sendmail': {'id': 'Sendmail', 'deprecated': False}, + 'sendmail-8.23': {'id': 'Sendmail-8.23', 'deprecated': False}, + 'sgi-b-1.0': {'id': 'SGI-B-1.0', 'deprecated': False}, + 'sgi-b-1.1': {'id': 'SGI-B-1.1', 'deprecated': False}, + 'sgi-b-2.0': {'id': 'SGI-B-2.0', 'deprecated': False}, + 'sgi-opengl': {'id': 'SGI-OpenGL', 'deprecated': False}, + 'sgp4': {'id': 'SGP4', 'deprecated': False}, + 'shl-0.5': {'id': 'SHL-0.5', 'deprecated': False}, + 'shl-0.51': {'id': 'SHL-0.51', 'deprecated': False}, + 'simpl-2.0': {'id': 'SimPL-2.0', 'deprecated': False}, + 'sissl': {'id': 'SISSL', 'deprecated': False}, + 'sissl-1.2': {'id': 'SISSL-1.2', 'deprecated': False}, + 'sl': {'id': 'SL', 'deprecated': False}, + 'sleepycat': {'id': 'Sleepycat', 'deprecated': False}, + 'smlnj': {'id': 'SMLNJ', 'deprecated': False}, + 'smppl': {'id': 'SMPPL', 'deprecated': False}, + 'snia': {'id': 'SNIA', 'deprecated': False}, + 'snprintf': {'id': 'snprintf', 'deprecated': False}, + 'softsurfer': {'id': 'softSurfer', 'deprecated': False}, + 'soundex': {'id': 'Soundex', 'deprecated': False}, + 'spencer-86': {'id': 'Spencer-86', 'deprecated': False}, + 'spencer-94': {'id': 'Spencer-94', 'deprecated': False}, + 'spencer-99': {'id': 'Spencer-99', 'deprecated': False}, + 'spl-1.0': {'id': 'SPL-1.0', 'deprecated': False}, + 'ssh-keyscan': {'id': 'ssh-keyscan', 'deprecated': False}, + 'ssh-openssh': {'id': 'SSH-OpenSSH', 'deprecated': False}, + 'ssh-short': {'id': 'SSH-short', 'deprecated': False}, + 'ssleay-standalone': {'id': 'SSLeay-standalone', 'deprecated': False}, + 'sspl-1.0': {'id': 'SSPL-1.0', 'deprecated': False}, + 'standardml-nj': {'id': 'StandardML-NJ', 'deprecated': True}, + 'sugarcrm-1.1.3': {'id': 'SugarCRM-1.1.3', 'deprecated': False}, + 'sun-ppp': {'id': 'Sun-PPP', 'deprecated': False}, + 'sun-ppp-2000': {'id': 'Sun-PPP-2000', 'deprecated': False}, + 'sunpro': {'id': 'SunPro', 'deprecated': False}, + 'swl': {'id': 'SWL', 'deprecated': False}, + 'swrule': {'id': 'swrule', 'deprecated': False}, + 'symlinks': {'id': 'Symlinks', 'deprecated': False}, + 'tapr-ohl-1.0': {'id': 'TAPR-OHL-1.0', 'deprecated': False}, + 'tcl': {'id': 'TCL', 'deprecated': False}, + 'tcp-wrappers': {'id': 'TCP-wrappers', 'deprecated': False}, + 'termreadkey': {'id': 'TermReadKey', 'deprecated': False}, + 'tgppl-1.0': {'id': 'TGPPL-1.0', 'deprecated': False}, + 'threeparttable': {'id': 'threeparttable', 'deprecated': False}, + 'tmate': {'id': 'TMate', 'deprecated': False}, + 'torque-1.1': {'id': 'TORQUE-1.1', 'deprecated': False}, + 'tosl': {'id': 'TOSL', 'deprecated': False}, + 'tpdl': {'id': 'TPDL', 'deprecated': False}, + 'tpl-1.0': {'id': 'TPL-1.0', 'deprecated': False}, + 'ttwl': {'id': 'TTWL', 'deprecated': False}, + 'ttyp0': {'id': 'TTYP0', 'deprecated': False}, + 'tu-berlin-1.0': {'id': 'TU-Berlin-1.0', 'deprecated': False}, + 'tu-berlin-2.0': {'id': 'TU-Berlin-2.0', 'deprecated': False}, + 'ubuntu-font-1.0': {'id': 'Ubuntu-font-1.0', 'deprecated': False}, + 'ucar': {'id': 'UCAR', 'deprecated': False}, + 'ucl-1.0': {'id': 'UCL-1.0', 'deprecated': False}, + 'ulem': {'id': 'ulem', 'deprecated': False}, + 'umich-merit': {'id': 'UMich-Merit', 'deprecated': False}, + 'unicode-3.0': {'id': 'Unicode-3.0', 'deprecated': False}, + 'unicode-dfs-2015': {'id': 'Unicode-DFS-2015', 'deprecated': False}, + 'unicode-dfs-2016': {'id': 'Unicode-DFS-2016', 'deprecated': False}, + 'unicode-tou': {'id': 'Unicode-TOU', 'deprecated': False}, + 'unixcrypt': {'id': 'UnixCrypt', 'deprecated': False}, + 'unlicense': {'id': 'Unlicense', 'deprecated': False}, + 'upl-1.0': {'id': 'UPL-1.0', 'deprecated': False}, + 'urt-rle': {'id': 'URT-RLE', 'deprecated': False}, + 'vim': {'id': 'Vim', 'deprecated': False}, + 'vostrom': {'id': 'VOSTROM', 'deprecated': False}, + 'vsl-1.0': {'id': 'VSL-1.0', 'deprecated': False}, + 'w3c': {'id': 'W3C', 'deprecated': False}, + 'w3c-19980720': {'id': 'W3C-19980720', 'deprecated': False}, + 'w3c-20150513': {'id': 'W3C-20150513', 'deprecated': False}, + 'w3m': {'id': 'w3m', 'deprecated': False}, + 'watcom-1.0': {'id': 'Watcom-1.0', 'deprecated': False}, + 'widget-workshop': {'id': 'Widget-Workshop', 'deprecated': False}, + 'wsuipa': {'id': 'Wsuipa', 'deprecated': False}, + 'wtfpl': {'id': 'WTFPL', 'deprecated': False}, + 'wxwindows': {'id': 'wxWindows', 'deprecated': True}, + 'x11': {'id': 'X11', 'deprecated': False}, + 'x11-distribute-modifications-variant': {'id': 'X11-distribute-modifications-variant', 'deprecated': False}, + 'x11-swapped': {'id': 'X11-swapped', 'deprecated': False}, + 'xdebug-1.03': {'id': 'Xdebug-1.03', 'deprecated': False}, + 'xerox': {'id': 'Xerox', 'deprecated': False}, + 'xfig': {'id': 'Xfig', 'deprecated': False}, + 'xfree86-1.1': {'id': 'XFree86-1.1', 'deprecated': False}, + 'xinetd': {'id': 'xinetd', 'deprecated': False}, + 'xkeyboard-config-zinoviev': {'id': 'xkeyboard-config-Zinoviev', 'deprecated': False}, + 'xlock': {'id': 'xlock', 'deprecated': False}, + 'xnet': {'id': 'Xnet', 'deprecated': False}, + 'xpp': {'id': 'xpp', 'deprecated': False}, + 'xskat': {'id': 'XSkat', 'deprecated': False}, + 'xzoom': {'id': 'xzoom', 'deprecated': False}, + 'ypl-1.0': {'id': 'YPL-1.0', 'deprecated': False}, + 'ypl-1.1': {'id': 'YPL-1.1', 'deprecated': False}, + 'zed': {'id': 'Zed', 'deprecated': False}, + 'zeeff': {'id': 'Zeeff', 'deprecated': False}, + 'zend-2.0': {'id': 'Zend-2.0', 'deprecated': False}, + 'zimbra-1.3': {'id': 'Zimbra-1.3', 'deprecated': False}, + 'zimbra-1.4': {'id': 'Zimbra-1.4', 'deprecated': False}, + 'zlib': {'id': 'Zlib', 'deprecated': False}, + 'zlib-acknowledgement': {'id': 'zlib-acknowledgement', 'deprecated': False}, + 'zpl-1.1': {'id': 'ZPL-1.1', 'deprecated': False}, + 'zpl-2.0': {'id': 'ZPL-2.0', 'deprecated': False}, + 'zpl-2.1': {'id': 'ZPL-2.1', 'deprecated': False}, +} + +EXCEPTIONS: dict[str, SPDXException] = { + '389-exception': {'id': '389-exception', 'deprecated': False}, + 'asterisk-exception': {'id': 'Asterisk-exception', 'deprecated': False}, + 'asterisk-linking-protocols-exception': {'id': 'Asterisk-linking-protocols-exception', 'deprecated': False}, + 'autoconf-exception-2.0': {'id': 'Autoconf-exception-2.0', 'deprecated': False}, + 'autoconf-exception-3.0': {'id': 'Autoconf-exception-3.0', 'deprecated': False}, + 'autoconf-exception-generic': {'id': 'Autoconf-exception-generic', 'deprecated': False}, + 'autoconf-exception-generic-3.0': {'id': 'Autoconf-exception-generic-3.0', 'deprecated': False}, + 'autoconf-exception-macro': {'id': 'Autoconf-exception-macro', 'deprecated': False}, + 'bison-exception-1.24': {'id': 'Bison-exception-1.24', 'deprecated': False}, + 'bison-exception-2.2': {'id': 'Bison-exception-2.2', 'deprecated': False}, + 'bootloader-exception': {'id': 'Bootloader-exception', 'deprecated': False}, + 'classpath-exception-2.0': {'id': 'Classpath-exception-2.0', 'deprecated': False}, + 'clisp-exception-2.0': {'id': 'CLISP-exception-2.0', 'deprecated': False}, + 'cryptsetup-openssl-exception': {'id': 'cryptsetup-OpenSSL-exception', 'deprecated': False}, + 'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception', 'deprecated': False}, + 'ecos-exception-2.0': {'id': 'eCos-exception-2.0', 'deprecated': False}, + 'erlang-otp-linking-exception': {'id': 'erlang-otp-linking-exception', 'deprecated': False}, + 'fawkes-runtime-exception': {'id': 'Fawkes-Runtime-exception', 'deprecated': False}, + 'fltk-exception': {'id': 'FLTK-exception', 'deprecated': False}, + 'fmt-exception': {'id': 'fmt-exception', 'deprecated': False}, + 'font-exception-2.0': {'id': 'Font-exception-2.0', 'deprecated': False}, + 'freertos-exception-2.0': {'id': 'freertos-exception-2.0', 'deprecated': False}, + 'gcc-exception-2.0': {'id': 'GCC-exception-2.0', 'deprecated': False}, + 'gcc-exception-2.0-note': {'id': 'GCC-exception-2.0-note', 'deprecated': False}, + 'gcc-exception-3.1': {'id': 'GCC-exception-3.1', 'deprecated': False}, + 'gmsh-exception': {'id': 'Gmsh-exception', 'deprecated': False}, + 'gnat-exception': {'id': 'GNAT-exception', 'deprecated': False}, + 'gnome-examples-exception': {'id': 'GNOME-examples-exception', 'deprecated': False}, + 'gnu-compiler-exception': {'id': 'GNU-compiler-exception', 'deprecated': False}, + 'gnu-javamail-exception': {'id': 'gnu-javamail-exception', 'deprecated': False}, + 'gpl-3.0-interface-exception': {'id': 'GPL-3.0-interface-exception', 'deprecated': False}, + 'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception', 'deprecated': False}, + 'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception', 'deprecated': False}, + 'gpl-cc-1.0': {'id': 'GPL-CC-1.0', 'deprecated': False}, + 'gstreamer-exception-2005': {'id': 'GStreamer-exception-2005', 'deprecated': False}, + 'gstreamer-exception-2008': {'id': 'GStreamer-exception-2008', 'deprecated': False}, + 'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception', 'deprecated': False}, + 'kicad-libraries-exception': {'id': 'KiCad-libraries-exception', 'deprecated': False}, + 'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception', 'deprecated': False}, + 'libpri-openh323-exception': {'id': 'libpri-OpenH323-exception', 'deprecated': False}, + 'libtool-exception': {'id': 'Libtool-exception', 'deprecated': False}, + 'linux-syscall-note': {'id': 'Linux-syscall-note', 'deprecated': False}, + 'llgpl': {'id': 'LLGPL', 'deprecated': False}, + 'llvm-exception': {'id': 'LLVM-exception', 'deprecated': False}, + 'lzma-exception': {'id': 'LZMA-exception', 'deprecated': False}, + 'mif-exception': {'id': 'mif-exception', 'deprecated': False}, + 'nokia-qt-exception-1.1': {'id': 'Nokia-Qt-exception-1.1', 'deprecated': True}, + 'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception', 'deprecated': False}, + 'occt-exception-1.0': {'id': 'OCCT-exception-1.0', 'deprecated': False}, + 'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0', 'deprecated': False}, + 'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception', 'deprecated': False}, + 'pcre2-exception': {'id': 'PCRE2-exception', 'deprecated': False}, + 'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817', 'deprecated': False}, + 'qpl-1.0-inria-2004-exception': {'id': 'QPL-1.0-INRIA-2004-exception', 'deprecated': False}, + 'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0', 'deprecated': False}, + 'qt-lgpl-exception-1.1': {'id': 'Qt-LGPL-exception-1.1', 'deprecated': False}, + 'qwt-exception-1.0': {'id': 'Qwt-exception-1.0', 'deprecated': False}, + 'romic-exception': {'id': 'romic-exception', 'deprecated': False}, + 'rrdtool-floss-exception-2.0': {'id': 'RRDtool-FLOSS-exception-2.0', 'deprecated': False}, + 'sane-exception': {'id': 'SANE-exception', 'deprecated': False}, + 'shl-2.0': {'id': 'SHL-2.0', 'deprecated': False}, + 'shl-2.1': {'id': 'SHL-2.1', 'deprecated': False}, + 'stunnel-exception': {'id': 'stunnel-exception', 'deprecated': False}, + 'swi-exception': {'id': 'SWI-exception', 'deprecated': False}, + 'swift-exception': {'id': 'Swift-exception', 'deprecated': False}, + 'texinfo-exception': {'id': 'Texinfo-exception', 'deprecated': False}, + 'u-boot-exception-2.0': {'id': 'u-boot-exception-2.0', 'deprecated': False}, + 'ubdl-exception': {'id': 'UBDL-exception', 'deprecated': False}, + 'universal-foss-exception-1.0': {'id': 'Universal-FOSS-exception-1.0', 'deprecated': False}, + 'vsftpd-openssl-exception': {'id': 'vsftpd-openssl-exception', 'deprecated': False}, + 'wxwindows-exception-3.1': {'id': 'WxWindows-exception-3.1', 'deprecated': False}, + 'x11vnc-openssl-exception': {'id': 'x11vnc-openssl-exception', 'deprecated': False}, +} diff --git a/server/libs/packaging/markers.py b/server/libs/packaging/markers.py new file mode 100644 index 0000000..e7cea57 --- /dev/null +++ b/server/libs/packaging/markers.py @@ -0,0 +1,362 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import operator +import os +import platform +import sys +from typing import AbstractSet, Any, Callable, Literal, TypedDict, Union, cast + +from ._parser import MarkerAtom, MarkerList, Op, Value, Variable +from ._parser import parse_marker as _parse_marker +from ._tokenizer import ParserSyntaxError +from .specifiers import InvalidSpecifier, Specifier +from .utils import canonicalize_name + +__all__ = [ + "EvaluateContext", + "InvalidMarker", + "Marker", + "UndefinedComparison", + "UndefinedEnvironmentName", + "default_environment", +] + +Operator = Callable[[str, Union[str, AbstractSet[str]]], bool] +EvaluateContext = Literal["metadata", "lock_file", "requirement"] +MARKERS_ALLOWING_SET = {"extras", "dependency_groups"} + + +class InvalidMarker(ValueError): + """ + An invalid marker was found, users should refer to PEP 508. + """ + + +class UndefinedComparison(ValueError): + """ + An invalid operation was attempted on a value that doesn't support it. + """ + + +class UndefinedEnvironmentName(ValueError): + """ + A name was attempted to be used that does not exist inside of the + environment. + """ + + +class Environment(TypedDict): + implementation_name: str + """The implementation's identifier, e.g. ``'cpython'``.""" + + implementation_version: str + """ + The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or + ``'7.3.13'`` for PyPy3.10 v7.3.13. + """ + + os_name: str + """ + The value of :py:data:`os.name`. The name of the operating system dependent module + imported, e.g. ``'posix'``. + """ + + platform_machine: str + """ + Returns the machine type, e.g. ``'i386'``. + + An empty string if the value cannot be determined. + """ + + platform_release: str + """ + The system's release, e.g. ``'2.2.0'`` or ``'NT'``. + + An empty string if the value cannot be determined. + """ + + platform_system: str + """ + The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``. + + An empty string if the value cannot be determined. + """ + + platform_version: str + """ + The system's release version, e.g. ``'#3 on degas'``. + + An empty string if the value cannot be determined. + """ + + python_full_version: str + """ + The Python version as string ``'major.minor.patchlevel'``. + + Note that unlike the Python :py:data:`sys.version`, this value will always include + the patchlevel (it defaults to 0). + """ + + platform_python_implementation: str + """ + A string identifying the Python implementation, e.g. ``'CPython'``. + """ + + python_version: str + """The Python version as string ``'major.minor'``.""" + + sys_platform: str + """ + This string contains a platform identifier that can be used to append + platform-specific components to :py:data:`sys.path`, for instance. + + For Unix systems, except on Linux and AIX, this is the lowercased OS name as + returned by ``uname -s`` with the first part of the version as returned by + ``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python + was built. + """ + + +def _normalize_extra_values(results: Any) -> Any: + """ + Normalize extra values. + """ + if isinstance(results[0], tuple): + lhs, op, rhs = results[0] + if isinstance(lhs, Variable) and lhs.value == "extra": + normalized_extra = canonicalize_name(rhs.value) + rhs = Value(normalized_extra) + elif isinstance(rhs, Variable) and rhs.value == "extra": + normalized_extra = canonicalize_name(lhs.value) + lhs = Value(normalized_extra) + results[0] = lhs, op, rhs + return results + + +def _format_marker( + marker: list[str] | MarkerAtom | str, first: bool | None = True +) -> str: + assert isinstance(marker, (list, tuple, str)) + + # Sometimes we have a structure like [[...]] which is a single item list + # where the single item is itself it's own list. In that case we want skip + # the rest of this function so that we don't get extraneous () on the + # outside. + if ( + isinstance(marker, list) + and len(marker) == 1 + and isinstance(marker[0], (list, tuple)) + ): + return _format_marker(marker[0]) + + if isinstance(marker, list): + inner = (_format_marker(m, first=False) for m in marker) + if first: + return " ".join(inner) + else: + return "(" + " ".join(inner) + ")" + elif isinstance(marker, tuple): + return " ".join([m.serialize() for m in marker]) + else: + return marker + + +_operators: dict[str, Operator] = { + "in": lambda lhs, rhs: lhs in rhs, + "not in": lambda lhs, rhs: lhs not in rhs, + "<": operator.lt, + "<=": operator.le, + "==": operator.eq, + "!=": operator.ne, + ">=": operator.ge, + ">": operator.gt, +} + + +def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str]) -> bool: + if isinstance(rhs, str): + try: + spec = Specifier("".join([op.serialize(), rhs])) + except InvalidSpecifier: + pass + else: + return spec.contains(lhs, prereleases=True) + + oper: Operator | None = _operators.get(op.serialize()) + if oper is None: + raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.") + + return oper(lhs, rhs) + + +def _normalize( + lhs: str, rhs: str | AbstractSet[str], key: str +) -> tuple[str, str | AbstractSet[str]]: + # PEP 685 – Comparison of extra names for optional distribution dependencies + # https://peps.python.org/pep-0685/ + # > When comparing extra names, tools MUST normalize the names being + # > compared using the semantics outlined in PEP 503 for names + if key == "extra": + assert isinstance(rhs, str), "extra value must be a string" + return (canonicalize_name(lhs), canonicalize_name(rhs)) + if key in MARKERS_ALLOWING_SET: + if isinstance(rhs, str): # pragma: no cover + return (canonicalize_name(lhs), canonicalize_name(rhs)) + else: + return (canonicalize_name(lhs), {canonicalize_name(v) for v in rhs}) + + # other environment markers don't have such standards + return lhs, rhs + + +def _evaluate_markers( + markers: MarkerList, environment: dict[str, str | AbstractSet[str]] +) -> bool: + groups: list[list[bool]] = [[]] + + for marker in markers: + assert isinstance(marker, (list, tuple, str)) + + if isinstance(marker, list): + groups[-1].append(_evaluate_markers(marker, environment)) + elif isinstance(marker, tuple): + lhs, op, rhs = marker + + if isinstance(lhs, Variable): + environment_key = lhs.value + lhs_value = environment[environment_key] + rhs_value = rhs.value + else: + lhs_value = lhs.value + environment_key = rhs.value + rhs_value = environment[environment_key] + assert isinstance(lhs_value, str), "lhs must be a string" + lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key) + groups[-1].append(_eval_op(lhs_value, op, rhs_value)) + else: + assert marker in ["and", "or"] + if marker == "or": + groups.append([]) + + return any(all(item) for item in groups) + + +def format_full_version(info: sys._version_info) -> str: + version = f"{info.major}.{info.minor}.{info.micro}" + kind = info.releaselevel + if kind != "final": + version += kind[0] + str(info.serial) + return version + + +def default_environment() -> Environment: + iver = format_full_version(sys.implementation.version) + implementation_name = sys.implementation.name + return { + "implementation_name": implementation_name, + "implementation_version": iver, + "os_name": os.name, + "platform_machine": platform.machine(), + "platform_release": platform.release(), + "platform_system": platform.system(), + "platform_version": platform.version(), + "python_full_version": platform.python_version(), + "platform_python_implementation": platform.python_implementation(), + "python_version": ".".join(platform.python_version_tuple()[:2]), + "sys_platform": sys.platform, + } + + +class Marker: + def __init__(self, marker: str) -> None: + # Note: We create a Marker object without calling this constructor in + # packaging.requirements.Requirement. If any additional logic is + # added here, make sure to mirror/adapt Requirement. + try: + self._markers = _normalize_extra_values(_parse_marker(marker)) + # The attribute `_markers` can be described in terms of a recursive type: + # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]] + # + # For example, the following expression: + # python_version > "3.6" or (python_version == "3.6" and os_name == "unix") + # + # is parsed into: + # [ + # (, ')>, ), + # 'and', + # [ + # (, , ), + # 'or', + # (, , ) + # ] + # ] + except ParserSyntaxError as e: + raise InvalidMarker(str(e)) from e + + def __str__(self) -> str: + return _format_marker(self._markers) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash((self.__class__.__name__, str(self))) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Marker): + return NotImplemented + + return str(self) == str(other) + + def evaluate( + self, + environment: dict[str, str] | None = None, + context: EvaluateContext = "metadata", + ) -> bool: + """Evaluate a marker. + + Return the boolean from evaluating the given marker against the + environment. environment is an optional argument to override all or + part of the determined environment. The *context* parameter specifies what + context the markers are being evaluated for, which influences what markers + are considered valid. Acceptable values are "metadata" (for core metadata; + default), "lock_file", and "requirement" (i.e. all other situations). + + The environment is determined from the current Python process. + """ + current_environment = cast( + "dict[str, str | AbstractSet[str]]", default_environment() + ) + if context == "lock_file": + current_environment.update( + extras=frozenset(), dependency_groups=frozenset() + ) + elif context == "metadata": + current_environment["extra"] = "" + if environment is not None: + current_environment.update(environment) + # The API used to allow setting extra to None. We need to handle this + # case for backwards compatibility. + if "extra" in current_environment and current_environment["extra"] is None: + current_environment["extra"] = "" + + return _evaluate_markers( + self._markers, _repair_python_full_version(current_environment) + ) + + +def _repair_python_full_version( + env: dict[str, str | AbstractSet[str]], +) -> dict[str, str | AbstractSet[str]]: + """ + Work around platform.python_version() returning something that is not PEP 440 + compliant for non-tagged Python builds. + """ + python_full_version = cast(str, env["python_full_version"]) + if python_full_version.endswith("+"): + env["python_full_version"] = f"{python_full_version}local" + return env diff --git a/server/libs/packaging/metadata.py b/server/libs/packaging/metadata.py new file mode 100644 index 0000000..3bd8602 --- /dev/null +++ b/server/libs/packaging/metadata.py @@ -0,0 +1,862 @@ +from __future__ import annotations + +import email.feedparser +import email.header +import email.message +import email.parser +import email.policy +import pathlib +import sys +import typing +from typing import ( + Any, + Callable, + Generic, + Literal, + TypedDict, + cast, +) + +from . import licenses, requirements, specifiers, utils +from . import version as version_module +from .licenses import NormalizedLicenseExpression + +T = typing.TypeVar("T") + + +if sys.version_info >= (3, 11): # pragma: no cover + ExceptionGroup = ExceptionGroup +else: # pragma: no cover + + class ExceptionGroup(Exception): + """A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11. + + If :external:exc:`ExceptionGroup` is already defined by Python itself, + that version is used instead. + """ + + message: str + exceptions: list[Exception] + + def __init__(self, message: str, exceptions: list[Exception]) -> None: + self.message = message + self.exceptions = exceptions + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})" + + +class InvalidMetadata(ValueError): + """A metadata field contains invalid data.""" + + field: str + """The name of the field that contains invalid data.""" + + def __init__(self, field: str, message: str) -> None: + self.field = field + super().__init__(message) + + +# The RawMetadata class attempts to make as few assumptions about the underlying +# serialization formats as possible. The idea is that as long as a serialization +# formats offer some very basic primitives in *some* way then we can support +# serializing to and from that format. +class RawMetadata(TypedDict, total=False): + """A dictionary of raw core metadata. + + Each field in core metadata maps to a key of this dictionary (when data is + provided). The key is lower-case and underscores are used instead of dashes + compared to the equivalent core metadata field. Any core metadata field that + can be specified multiple times or can hold multiple values in a single + field have a key with a plural name. See :class:`Metadata` whose attributes + match the keys of this dictionary. + + Core metadata fields that can be specified multiple times are stored as a + list or dict depending on which is appropriate for the field. Any fields + which hold multiple values in a single field are stored as a list. + + """ + + # Metadata 1.0 - PEP 241 + metadata_version: str + name: str + version: str + platforms: list[str] + summary: str + description: str + keywords: list[str] + home_page: str + author: str + author_email: str + license: str + + # Metadata 1.1 - PEP 314 + supported_platforms: list[str] + download_url: str + classifiers: list[str] + requires: list[str] + provides: list[str] + obsoletes: list[str] + + # Metadata 1.2 - PEP 345 + maintainer: str + maintainer_email: str + requires_dist: list[str] + provides_dist: list[str] + obsoletes_dist: list[str] + requires_python: str + requires_external: list[str] + project_urls: dict[str, str] + + # Metadata 2.0 + # PEP 426 attempted to completely revamp the metadata format + # but got stuck without ever being able to build consensus on + # it and ultimately ended up withdrawn. + # + # However, a number of tools had started emitting METADATA with + # `2.0` Metadata-Version, so for historical reasons, this version + # was skipped. + + # Metadata 2.1 - PEP 566 + description_content_type: str + provides_extra: list[str] + + # Metadata 2.2 - PEP 643 + dynamic: list[str] + + # Metadata 2.3 - PEP 685 + # No new fields were added in PEP 685, just some edge case were + # tightened up to provide better interoptability. + + # Metadata 2.4 - PEP 639 + license_expression: str + license_files: list[str] + + +_STRING_FIELDS = { + "author", + "author_email", + "description", + "description_content_type", + "download_url", + "home_page", + "license", + "license_expression", + "maintainer", + "maintainer_email", + "metadata_version", + "name", + "requires_python", + "summary", + "version", +} + +_LIST_FIELDS = { + "classifiers", + "dynamic", + "license_files", + "obsoletes", + "obsoletes_dist", + "platforms", + "provides", + "provides_dist", + "provides_extra", + "requires", + "requires_dist", + "requires_external", + "supported_platforms", +} + +_DICT_FIELDS = { + "project_urls", +} + + +def _parse_keywords(data: str) -> list[str]: + """Split a string of comma-separated keywords into a list of keywords.""" + return [k.strip() for k in data.split(",")] + + +def _parse_project_urls(data: list[str]) -> dict[str, str]: + """Parse a list of label/URL string pairings separated by a comma.""" + urls = {} + for pair in data: + # Our logic is slightly tricky here as we want to try and do + # *something* reasonable with malformed data. + # + # The main thing that we have to worry about, is data that does + # not have a ',' at all to split the label from the Value. There + # isn't a singular right answer here, and we will fail validation + # later on (if the caller is validating) so it doesn't *really* + # matter, but since the missing value has to be an empty str + # and our return value is dict[str, str], if we let the key + # be the missing value, then they'd have multiple '' values that + # overwrite each other in a accumulating dict. + # + # The other potentional issue is that it's possible to have the + # same label multiple times in the metadata, with no solid "right" + # answer with what to do in that case. As such, we'll do the only + # thing we can, which is treat the field as unparseable and add it + # to our list of unparsed fields. + parts = [p.strip() for p in pair.split(",", 1)] + parts.extend([""] * (max(0, 2 - len(parts)))) # Ensure 2 items + + # TODO: The spec doesn't say anything about if the keys should be + # considered case sensitive or not... logically they should + # be case-preserving and case-insensitive, but doing that + # would open up more cases where we might have duplicate + # entries. + label, url = parts + if label in urls: + # The label already exists in our set of urls, so this field + # is unparseable, and we can just add the whole thing to our + # unparseable data and stop processing it. + raise KeyError("duplicate labels in project urls") + urls[label] = url + + return urls + + +def _get_payload(msg: email.message.Message, source: bytes | str) -> str: + """Get the body of the message.""" + # If our source is a str, then our caller has managed encodings for us, + # and we don't need to deal with it. + if isinstance(source, str): + payload = msg.get_payload() + assert isinstance(payload, str) + return payload + # If our source is a bytes, then we're managing the encoding and we need + # to deal with it. + else: + bpayload = msg.get_payload(decode=True) + assert isinstance(bpayload, bytes) + try: + return bpayload.decode("utf8", "strict") + except UnicodeDecodeError as exc: + raise ValueError("payload in an invalid encoding") from exc + + +# The various parse_FORMAT functions here are intended to be as lenient as +# possible in their parsing, while still returning a correctly typed +# RawMetadata. +# +# To aid in this, we also generally want to do as little touching of the +# data as possible, except where there are possibly some historic holdovers +# that make valid data awkward to work with. +# +# While this is a lower level, intermediate format than our ``Metadata`` +# class, some light touch ups can make a massive difference in usability. + +# Map METADATA fields to RawMetadata. +_EMAIL_TO_RAW_MAPPING = { + "author": "author", + "author-email": "author_email", + "classifier": "classifiers", + "description": "description", + "description-content-type": "description_content_type", + "download-url": "download_url", + "dynamic": "dynamic", + "home-page": "home_page", + "keywords": "keywords", + "license": "license", + "license-expression": "license_expression", + "license-file": "license_files", + "maintainer": "maintainer", + "maintainer-email": "maintainer_email", + "metadata-version": "metadata_version", + "name": "name", + "obsoletes": "obsoletes", + "obsoletes-dist": "obsoletes_dist", + "platform": "platforms", + "project-url": "project_urls", + "provides": "provides", + "provides-dist": "provides_dist", + "provides-extra": "provides_extra", + "requires": "requires", + "requires-dist": "requires_dist", + "requires-external": "requires_external", + "requires-python": "requires_python", + "summary": "summary", + "supported-platform": "supported_platforms", + "version": "version", +} +_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()} + + +def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]: + """Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``). + + This function returns a two-item tuple of dicts. The first dict is of + recognized fields from the core metadata specification. Fields that can be + parsed and translated into Python's built-in types are converted + appropriately. All other fields are left as-is. Fields that are allowed to + appear multiple times are stored as lists. + + The second dict contains all other fields from the metadata. This includes + any unrecognized fields. It also includes any fields which are expected to + be parsed into a built-in type but were not formatted appropriately. Finally, + any fields that are expected to appear only once but are repeated are + included in this dict. + + """ + raw: dict[str, str | list[str] | dict[str, str]] = {} + unparsed: dict[str, list[str]] = {} + + if isinstance(data, str): + parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data) + else: + parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data) + + # We have to wrap parsed.keys() in a set, because in the case of multiple + # values for a key (a list), the key will appear multiple times in the + # list of keys, but we're avoiding that by using get_all(). + for name in frozenset(parsed.keys()): + # Header names in RFC are case insensitive, so we'll normalize to all + # lower case to make comparisons easier. + name = name.lower() + + # We use get_all() here, even for fields that aren't multiple use, + # because otherwise someone could have e.g. two Name fields, and we + # would just silently ignore it rather than doing something about it. + headers = parsed.get_all(name) or [] + + # The way the email module works when parsing bytes is that it + # unconditionally decodes the bytes as ascii using the surrogateescape + # handler. When you pull that data back out (such as with get_all() ), + # it looks to see if the str has any surrogate escapes, and if it does + # it wraps it in a Header object instead of returning the string. + # + # As such, we'll look for those Header objects, and fix up the encoding. + value = [] + # Flag if we have run into any issues processing the headers, thus + # signalling that the data belongs in 'unparsed'. + valid_encoding = True + for h in headers: + # It's unclear if this can return more types than just a Header or + # a str, so we'll just assert here to make sure. + assert isinstance(h, (email.header.Header, str)) + + # If it's a header object, we need to do our little dance to get + # the real data out of it. In cases where there is invalid data + # we're going to end up with mojibake, but there's no obvious, good + # way around that without reimplementing parts of the Header object + # ourselves. + # + # That should be fine since, if mojibacked happens, this key is + # going into the unparsed dict anyways. + if isinstance(h, email.header.Header): + # The Header object stores it's data as chunks, and each chunk + # can be independently encoded, so we'll need to check each + # of them. + chunks: list[tuple[bytes, str | None]] = [] + for bin, encoding in email.header.decode_header(h): + try: + bin.decode("utf8", "strict") + except UnicodeDecodeError: + # Enable mojibake. + encoding = "latin1" + valid_encoding = False + else: + encoding = "utf8" + chunks.append((bin, encoding)) + + # Turn our chunks back into a Header object, then let that + # Header object do the right thing to turn them into a + # string for us. + value.append(str(email.header.make_header(chunks))) + # This is already a string, so just add it. + else: + value.append(h) + + # We've processed all of our values to get them into a list of str, + # but we may have mojibake data, in which case this is an unparsed + # field. + if not valid_encoding: + unparsed[name] = value + continue + + raw_name = _EMAIL_TO_RAW_MAPPING.get(name) + if raw_name is None: + # This is a bit of a weird situation, we've encountered a key that + # we don't know what it means, so we don't know whether it's meant + # to be a list or not. + # + # Since we can't really tell one way or another, we'll just leave it + # as a list, even though it may be a single item list, because that's + # what makes the most sense for email headers. + unparsed[name] = value + continue + + # If this is one of our string fields, then we'll check to see if our + # value is a list of a single item. If it is then we'll assume that + # it was emitted as a single string, and unwrap the str from inside + # the list. + # + # If it's any other kind of data, then we haven't the faintest clue + # what we should parse it as, and we have to just add it to our list + # of unparsed stuff. + if raw_name in _STRING_FIELDS and len(value) == 1: + raw[raw_name] = value[0] + # If this is one of our list of string fields, then we can just assign + # the value, since email *only* has strings, and our get_all() call + # above ensures that this is a list. + elif raw_name in _LIST_FIELDS: + raw[raw_name] = value + # Special Case: Keywords + # The keywords field is implemented in the metadata spec as a str, + # but it conceptually is a list of strings, and is serialized using + # ", ".join(keywords), so we'll do some light data massaging to turn + # this into what it logically is. + elif raw_name == "keywords" and len(value) == 1: + raw[raw_name] = _parse_keywords(value[0]) + # Special Case: Project-URL + # The project urls is implemented in the metadata spec as a list of + # specially-formatted strings that represent a key and a value, which + # is fundamentally a mapping, however the email format doesn't support + # mappings in a sane way, so it was crammed into a list of strings + # instead. + # + # We will do a little light data massaging to turn this into a map as + # it logically should be. + elif raw_name == "project_urls": + try: + raw[raw_name] = _parse_project_urls(value) + except KeyError: + unparsed[name] = value + # Nothing that we've done has managed to parse this, so it'll just + # throw it in our unparseable data and move on. + else: + unparsed[name] = value + + # We need to support getting the Description from the message payload in + # addition to getting it from the the headers. This does mean, though, there + # is the possibility of it being set both ways, in which case we put both + # in 'unparsed' since we don't know which is right. + try: + payload = _get_payload(parsed, data) + except ValueError: + unparsed.setdefault("description", []).append( + parsed.get_payload(decode=isinstance(data, bytes)) # type: ignore[call-overload] + ) + else: + if payload: + # Check to see if we've already got a description, if so then both + # it, and this body move to unparseable. + if "description" in raw: + description_header = cast(str, raw.pop("description")) + unparsed.setdefault("description", []).extend( + [description_header, payload] + ) + elif "description" in unparsed: + unparsed["description"].append(payload) + else: + raw["description"] = payload + + # We need to cast our `raw` to a metadata, because a TypedDict only support + # literal key names, but we're computing our key names on purpose, but the + # way this function is implemented, our `TypedDict` can only have valid key + # names. + return cast(RawMetadata, raw), unparsed + + +_NOT_FOUND = object() + + +# Keep the two values in sync. +_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"] +_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4"] + +_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"]) + + +class _Validator(Generic[T]): + """Validate a metadata field. + + All _process_*() methods correspond to a core metadata field. The method is + called with the field's raw value. If the raw value is valid it is returned + in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field). + If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause + as appropriate). + """ + + name: str + raw_name: str + added: _MetadataVersion + + def __init__( + self, + *, + added: _MetadataVersion = "1.0", + ) -> None: + self.added = added + + def __set_name__(self, _owner: Metadata, name: str) -> None: + self.name = name + self.raw_name = _RAW_TO_EMAIL_MAPPING[name] + + def __get__(self, instance: Metadata, _owner: type[Metadata]) -> T: + # With Python 3.8, the caching can be replaced with functools.cached_property(). + # No need to check the cache as attribute lookup will resolve into the + # instance's __dict__ before __get__ is called. + cache = instance.__dict__ + value = instance._raw.get(self.name) + + # To make the _process_* methods easier, we'll check if the value is None + # and if this field is NOT a required attribute, and if both of those + # things are true, we'll skip the the converter. This will mean that the + # converters never have to deal with the None union. + if self.name in _REQUIRED_ATTRS or value is not None: + try: + converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}") + except AttributeError: + pass + else: + value = converter(value) + + cache[self.name] = value + try: + del instance._raw[self.name] # type: ignore[misc] + except KeyError: + pass + + return cast(T, value) + + def _invalid_metadata( + self, msg: str, cause: Exception | None = None + ) -> InvalidMetadata: + exc = InvalidMetadata( + self.raw_name, msg.format_map({"field": repr(self.raw_name)}) + ) + exc.__cause__ = cause + return exc + + def _process_metadata_version(self, value: str) -> _MetadataVersion: + # Implicitly makes Metadata-Version required. + if value not in _VALID_METADATA_VERSIONS: + raise self._invalid_metadata(f"{value!r} is not a valid metadata version") + return cast(_MetadataVersion, value) + + def _process_name(self, value: str) -> str: + if not value: + raise self._invalid_metadata("{field} is a required field") + # Validate the name as a side-effect. + try: + utils.canonicalize_name(value, validate=True) + except utils.InvalidName as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) from exc + else: + return value + + def _process_version(self, value: str) -> version_module.Version: + if not value: + raise self._invalid_metadata("{field} is a required field") + try: + return version_module.parse(value) + except version_module.InvalidVersion as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) from exc + + def _process_summary(self, value: str) -> str: + """Check the field contains no newlines.""" + if "\n" in value: + raise self._invalid_metadata("{field} must be a single line") + return value + + def _process_description_content_type(self, value: str) -> str: + content_types = {"text/plain", "text/x-rst", "text/markdown"} + message = email.message.EmailMessage() + message["content-type"] = value + + content_type, parameters = ( + # Defaults to `text/plain` if parsing failed. + message.get_content_type().lower(), + message["content-type"].params, + ) + # Check if content-type is valid or defaulted to `text/plain` and thus was + # not parseable. + if content_type not in content_types or content_type not in value.lower(): + raise self._invalid_metadata( + f"{{field}} must be one of {list(content_types)}, not {value!r}" + ) + + charset = parameters.get("charset", "UTF-8") + if charset != "UTF-8": + raise self._invalid_metadata( + f"{{field}} can only specify the UTF-8 charset, not {list(charset)}" + ) + + markdown_variants = {"GFM", "CommonMark"} + variant = parameters.get("variant", "GFM") # Use an acceptable default. + if content_type == "text/markdown" and variant not in markdown_variants: + raise self._invalid_metadata( + f"valid Markdown variants for {{field}} are {list(markdown_variants)}, " + f"not {variant!r}", + ) + return value + + def _process_dynamic(self, value: list[str]) -> list[str]: + for dynamic_field in map(str.lower, value): + if dynamic_field in {"name", "version", "metadata-version"}: + raise self._invalid_metadata( + f"{dynamic_field!r} is not allowed as a dynamic field" + ) + elif dynamic_field not in _EMAIL_TO_RAW_MAPPING: + raise self._invalid_metadata( + f"{dynamic_field!r} is not a valid dynamic field" + ) + return list(map(str.lower, value)) + + def _process_provides_extra( + self, + value: list[str], + ) -> list[utils.NormalizedName]: + normalized_names = [] + try: + for name in value: + normalized_names.append(utils.canonicalize_name(name, validate=True)) + except utils.InvalidName as exc: + raise self._invalid_metadata( + f"{name!r} is invalid for {{field}}", cause=exc + ) from exc + else: + return normalized_names + + def _process_requires_python(self, value: str) -> specifiers.SpecifierSet: + try: + return specifiers.SpecifierSet(value) + except specifiers.InvalidSpecifier as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) from exc + + def _process_requires_dist( + self, + value: list[str], + ) -> list[requirements.Requirement]: + reqs = [] + try: + for req in value: + reqs.append(requirements.Requirement(req)) + except requirements.InvalidRequirement as exc: + raise self._invalid_metadata( + f"{req!r} is invalid for {{field}}", cause=exc + ) from exc + else: + return reqs + + def _process_license_expression( + self, value: str + ) -> NormalizedLicenseExpression | None: + try: + return licenses.canonicalize_license_expression(value) + except ValueError as exc: + raise self._invalid_metadata( + f"{value!r} is invalid for {{field}}", cause=exc + ) from exc + + def _process_license_files(self, value: list[str]) -> list[str]: + paths = [] + for path in value: + if ".." in path: + raise self._invalid_metadata( + f"{path!r} is invalid for {{field}}, " + "parent directory indicators are not allowed" + ) + if "*" in path: + raise self._invalid_metadata( + f"{path!r} is invalid for {{field}}, paths must be resolved" + ) + if ( + pathlib.PurePosixPath(path).is_absolute() + or pathlib.PureWindowsPath(path).is_absolute() + ): + raise self._invalid_metadata( + f"{path!r} is invalid for {{field}}, paths must be relative" + ) + if pathlib.PureWindowsPath(path).as_posix() != path: + raise self._invalid_metadata( + f"{path!r} is invalid for {{field}}, paths must use '/' delimiter" + ) + paths.append(path) + return paths + + +class Metadata: + """Representation of distribution metadata. + + Compared to :class:`RawMetadata`, this class provides objects representing + metadata fields instead of only using built-in types. Any invalid metadata + will cause :exc:`InvalidMetadata` to be raised (with a + :py:attr:`~BaseException.__cause__` attribute as appropriate). + """ + + _raw: RawMetadata + + @classmethod + def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> Metadata: + """Create an instance from :class:`RawMetadata`. + + If *validate* is true, all metadata will be validated. All exceptions + related to validation will be gathered and raised as an :class:`ExceptionGroup`. + """ + ins = cls() + ins._raw = data.copy() # Mutations occur due to caching enriched values. + + if validate: + exceptions: list[Exception] = [] + try: + metadata_version = ins.metadata_version + metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version) + except InvalidMetadata as metadata_version_exc: + exceptions.append(metadata_version_exc) + metadata_version = None + + # Make sure to check for the fields that are present, the required + # fields (so their absence can be reported). + fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS + # Remove fields that have already been checked. + fields_to_check -= {"metadata_version"} + + for key in fields_to_check: + try: + if metadata_version: + # Can't use getattr() as that triggers descriptor protocol which + # will fail due to no value for the instance argument. + try: + field_metadata_version = cls.__dict__[key].added + except KeyError: + exc = InvalidMetadata(key, f"unrecognized field: {key!r}") + exceptions.append(exc) + continue + field_age = _VALID_METADATA_VERSIONS.index( + field_metadata_version + ) + if field_age > metadata_age: + field = _RAW_TO_EMAIL_MAPPING[key] + exc = InvalidMetadata( + field, + f"{field} introduced in metadata version " + f"{field_metadata_version}, not {metadata_version}", + ) + exceptions.append(exc) + continue + getattr(ins, key) + except InvalidMetadata as exc: + exceptions.append(exc) + + if exceptions: + raise ExceptionGroup("invalid metadata", exceptions) + + return ins + + @classmethod + def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata: + """Parse metadata from email headers. + + If *validate* is true, the metadata will be validated. All exceptions + related to validation will be gathered and raised as an :class:`ExceptionGroup`. + """ + raw, unparsed = parse_email(data) + + if validate: + exceptions: list[Exception] = [] + for unparsed_key in unparsed: + if unparsed_key in _EMAIL_TO_RAW_MAPPING: + message = f"{unparsed_key!r} has invalid data" + else: + message = f"unrecognized field: {unparsed_key!r}" + exceptions.append(InvalidMetadata(unparsed_key, message)) + + if exceptions: + raise ExceptionGroup("unparsed", exceptions) + + try: + return cls.from_raw(raw, validate=validate) + except ExceptionGroup as exc_group: + raise ExceptionGroup( + "invalid or unparsed metadata", exc_group.exceptions + ) from None + + metadata_version: _Validator[_MetadataVersion] = _Validator() + """:external:ref:`core-metadata-metadata-version` + (required; validated to be a valid metadata version)""" + # `name` is not normalized/typed to NormalizedName so as to provide access to + # the original/raw name. + name: _Validator[str] = _Validator() + """:external:ref:`core-metadata-name` + (required; validated using :func:`~packaging.utils.canonicalize_name` and its + *validate* parameter)""" + version: _Validator[version_module.Version] = _Validator() + """:external:ref:`core-metadata-version` (required)""" + dynamic: _Validator[list[str] | None] = _Validator( + added="2.2", + ) + """:external:ref:`core-metadata-dynamic` + (validated against core metadata field names and lowercased)""" + platforms: _Validator[list[str] | None] = _Validator() + """:external:ref:`core-metadata-platform`""" + supported_platforms: _Validator[list[str] | None] = _Validator(added="1.1") + """:external:ref:`core-metadata-supported-platform`""" + summary: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-summary` (validated to contain no newlines)""" + description: _Validator[str | None] = _Validator() # TODO 2.1: can be in body + """:external:ref:`core-metadata-description`""" + description_content_type: _Validator[str | None] = _Validator(added="2.1") + """:external:ref:`core-metadata-description-content-type` (validated)""" + keywords: _Validator[list[str] | None] = _Validator() + """:external:ref:`core-metadata-keywords`""" + home_page: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-home-page`""" + download_url: _Validator[str | None] = _Validator(added="1.1") + """:external:ref:`core-metadata-download-url`""" + author: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-author`""" + author_email: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-author-email`""" + maintainer: _Validator[str | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-maintainer`""" + maintainer_email: _Validator[str | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-maintainer-email`""" + license: _Validator[str | None] = _Validator() + """:external:ref:`core-metadata-license`""" + license_expression: _Validator[NormalizedLicenseExpression | None] = _Validator( + added="2.4" + ) + """:external:ref:`core-metadata-license-expression`""" + license_files: _Validator[list[str] | None] = _Validator(added="2.4") + """:external:ref:`core-metadata-license-file`""" + classifiers: _Validator[list[str] | None] = _Validator(added="1.1") + """:external:ref:`core-metadata-classifier`""" + requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator( + added="1.2" + ) + """:external:ref:`core-metadata-requires-dist`""" + requires_python: _Validator[specifiers.SpecifierSet | None] = _Validator( + added="1.2" + ) + """:external:ref:`core-metadata-requires-python`""" + # Because `Requires-External` allows for non-PEP 440 version specifiers, we + # don't do any processing on the values. + requires_external: _Validator[list[str] | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-requires-external`""" + project_urls: _Validator[dict[str, str] | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-project-url`""" + # PEP 685 lets us raise an error if an extra doesn't pass `Name` validation + # regardless of metadata version. + provides_extra: _Validator[list[utils.NormalizedName] | None] = _Validator( + added="2.1", + ) + """:external:ref:`core-metadata-provides-extra`""" + provides_dist: _Validator[list[str] | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-provides-dist`""" + obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2") + """:external:ref:`core-metadata-obsoletes-dist`""" + requires: _Validator[list[str] | None] = _Validator(added="1.1") + """``Requires`` (deprecated)""" + provides: _Validator[list[str] | None] = _Validator(added="1.1") + """``Provides`` (deprecated)""" + obsoletes: _Validator[list[str] | None] = _Validator(added="1.1") + """``Obsoletes`` (deprecated)""" diff --git a/server/libs/packaging/py.typed b/server/libs/packaging/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/server/libs/packaging/requirements.py b/server/libs/packaging/requirements.py new file mode 100644 index 0000000..4e068c9 --- /dev/null +++ b/server/libs/packaging/requirements.py @@ -0,0 +1,91 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +from __future__ import annotations + +from typing import Any, Iterator + +from ._parser import parse_requirement as _parse_requirement +from ._tokenizer import ParserSyntaxError +from .markers import Marker, _normalize_extra_values +from .specifiers import SpecifierSet +from .utils import canonicalize_name + + +class InvalidRequirement(ValueError): + """ + An invalid requirement was found, users should refer to PEP 508. + """ + + +class Requirement: + """Parse a requirement. + + Parse a given requirement string into its parts, such as name, specifier, + URL, and extras. Raises InvalidRequirement on a badly-formed requirement + string. + """ + + # TODO: Can we test whether something is contained within a requirement? + # If so how do we do that? Do we need to test against the _name_ of + # the thing as well as the version? What about the markers? + # TODO: Can we normalize the name and extra name? + + def __init__(self, requirement_string: str) -> None: + try: + parsed = _parse_requirement(requirement_string) + except ParserSyntaxError as e: + raise InvalidRequirement(str(e)) from e + + self.name: str = parsed.name + self.url: str | None = parsed.url or None + self.extras: set[str] = set(parsed.extras or []) + self.specifier: SpecifierSet = SpecifierSet(parsed.specifier) + self.marker: Marker | None = None + if parsed.marker is not None: + self.marker = Marker.__new__(Marker) + self.marker._markers = _normalize_extra_values(parsed.marker) + + def _iter_parts(self, name: str) -> Iterator[str]: + yield name + + if self.extras: + formatted_extras = ",".join(sorted(self.extras)) + yield f"[{formatted_extras}]" + + if self.specifier: + yield str(self.specifier) + + if self.url: + yield f"@ {self.url}" + if self.marker: + yield " " + + if self.marker: + yield f"; {self.marker}" + + def __str__(self) -> str: + return "".join(self._iter_parts(self.name)) + + def __repr__(self) -> str: + return f"" + + def __hash__(self) -> int: + return hash( + ( + self.__class__.__name__, + *self._iter_parts(canonicalize_name(self.name)), + ) + ) + + def __eq__(self, other: Any) -> bool: + if not isinstance(other, Requirement): + return NotImplemented + + return ( + canonicalize_name(self.name) == canonicalize_name(other.name) + and self.extras == other.extras + and self.specifier == other.specifier + and self.url == other.url + and self.marker == other.marker + ) diff --git a/server/libs/packaging/specifiers.py b/server/libs/packaging/specifiers.py new file mode 100644 index 0000000..c844804 --- /dev/null +++ b/server/libs/packaging/specifiers.py @@ -0,0 +1,1019 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +""" +.. testsetup:: + + from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier + from packaging.version import Version +""" + +from __future__ import annotations + +import abc +import itertools +import re +from typing import Callable, Iterable, Iterator, TypeVar, Union + +from .utils import canonicalize_version +from .version import Version + +UnparsedVersion = Union[Version, str] +UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion) +CallableOperator = Callable[[Version, str], bool] + + +def _coerce_version(version: UnparsedVersion) -> Version: + if not isinstance(version, Version): + version = Version(version) + return version + + +class InvalidSpecifier(ValueError): + """ + Raised when attempting to create a :class:`Specifier` with a specifier + string that is invalid. + + >>> Specifier("lolwat") + Traceback (most recent call last): + ... + packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat' + """ + + +class BaseSpecifier(metaclass=abc.ABCMeta): + @abc.abstractmethod + def __str__(self) -> str: + """ + Returns the str representation of this Specifier-like object. This + should be representative of the Specifier itself. + """ + + @abc.abstractmethod + def __hash__(self) -> int: + """ + Returns a hash value for this Specifier-like object. + """ + + @abc.abstractmethod + def __eq__(self, other: object) -> bool: + """ + Returns a boolean representing whether or not the two Specifier-like + objects are equal. + + :param other: The other object to check against. + """ + + @property + @abc.abstractmethod + def prereleases(self) -> bool | None: + """Whether or not pre-releases as a whole are allowed. + + This can be set to either ``True`` or ``False`` to explicitly enable or disable + prereleases or it can be set to ``None`` (the default) to use default semantics. + """ + + @prereleases.setter + def prereleases(self, value: bool) -> None: + """Setter for :attr:`prereleases`. + + :param value: The value to set. + """ + + @abc.abstractmethod + def contains(self, item: str, prereleases: bool | None = None) -> bool: + """ + Determines if the given item is contained within this specifier. + """ + + @abc.abstractmethod + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None + ) -> Iterator[UnparsedVersionVar]: + """ + Takes an iterable of items and filters them so that only items which + are contained within this specifier are allowed in it. + """ + + +class Specifier(BaseSpecifier): + """This class abstracts handling of version specifiers. + + .. tip:: + + It is generally not required to instantiate this manually. You should instead + prefer to work with :class:`SpecifierSet` instead, which can parse + comma-separated version specifiers (which is what package metadata contains). + """ + + _operator_regex_str = r""" + (?P(~=|==|!=|<=|>=|<|>|===)) + """ + _version_regex_str = r""" + (?P + (?: + # The identity operators allow for an escape hatch that will + # do an exact string match of the version you wish to install. + # This will not be parsed by PEP 440 and we cannot determine + # any semantic meaning from it. This operator is discouraged + # but included entirely as an escape hatch. + (?<====) # Only match for the identity operator + \s* + [^\s;)]* # The arbitrary version can be just about anything, + # we match everything except for whitespace, a + # semi-colon for marker support, and a closing paren + # since versions can be enclosed in them. + ) + | + (?: + # The (non)equality operators allow for wild card and local + # versions to be specified so we have to define these two + # operators separately to enable that. + (?<===|!=) # Only match for equals and not equals + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)* # release + + # You cannot use a wild card and a pre-release, post-release, a dev or + # local version together so group them with a | and make them optional. + (?: + \.\* # Wild card syntax of .* + | + (?: # pre release + [-_\.]? + (alpha|beta|preview|pre|a|b|c|rc) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + (?:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local + )? + ) + | + (?: + # The compatible operator requires at least two digits in the + # release segment. + (?<=~=) # Only match for the compatible operator + + \s* + v? + (?:[0-9]+!)? # epoch + [0-9]+(?:\.[0-9]+)+ # release (We have a + instead of a *) + (?: # pre release + [-_\.]? + (alpha|beta|preview|pre|a|b|c|rc) + [-_\.]? + [0-9]* + )? + (?: # post release + (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*) + )? + (?:[-_\.]?dev[-_\.]?[0-9]*)? # dev release + ) + | + (?: + # All other operators only allow a sub set of what the + # (non)equality operators do. Specifically they do not allow + # local versions to be specified nor do they allow the prefix + # matching wild cards. + (?=": "greater_than_equal", + "<": "less_than", + ">": "greater_than", + "===": "arbitrary", + } + + def __init__(self, spec: str = "", prereleases: bool | None = None) -> None: + """Initialize a Specifier instance. + + :param spec: + The string representation of a specifier which will be parsed and + normalized before use. + :param prereleases: + This tells the specifier if it should accept prerelease versions if + applicable or not. The default of ``None`` will autodetect it from the + given specifiers. + :raises InvalidSpecifier: + If the given specifier is invalid (i.e. bad syntax). + """ + match = self._regex.search(spec) + if not match: + raise InvalidSpecifier(f"Invalid specifier: {spec!r}") + + self._spec: tuple[str, str] = ( + match.group("operator").strip(), + match.group("version").strip(), + ) + + # Store whether or not this Specifier should accept prereleases + self._prereleases = prereleases + + # https://github.com/python/mypy/pull/13475#pullrequestreview-1079784515 + @property # type: ignore[override] + def prereleases(self) -> bool: + # If there is an explicit prereleases set for this, then we'll just + # blindly use that. + if self._prereleases is not None: + return self._prereleases + + # Look at all of our specifiers and determine if they are inclusive + # operators, and if they are if they are including an explicit + # prerelease. + operator, version = self._spec + if operator in ["==", ">=", "<=", "~=", "===", ">", "<"]: + # The == specifier can include a trailing .*, if it does we + # want to remove before parsing. + if operator == "==" and version.endswith(".*"): + version = version[:-2] + + # Parse the version, and if it is a pre-release than this + # specifier allows pre-releases. + if Version(version).is_prerelease: + return True + + return False + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + @property + def operator(self) -> str: + """The operator of this specifier. + + >>> Specifier("==1.2.3").operator + '==' + """ + return self._spec[0] + + @property + def version(self) -> str: + """The version of this specifier. + + >>> Specifier("==1.2.3").version + '1.2.3' + """ + return self._spec[1] + + def __repr__(self) -> str: + """A representation of the Specifier that shows all internal state. + + >>> Specifier('>=1.0.0') + =1.0.0')> + >>> Specifier('>=1.0.0', prereleases=False) + =1.0.0', prereleases=False)> + >>> Specifier('>=1.0.0', prereleases=True) + =1.0.0', prereleases=True)> + """ + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return f"<{self.__class__.__name__}({str(self)!r}{pre})>" + + def __str__(self) -> str: + """A string representation of the Specifier that can be round-tripped. + + >>> str(Specifier('>=1.0.0')) + '>=1.0.0' + >>> str(Specifier('>=1.0.0', prereleases=False)) + '>=1.0.0' + """ + return "{}{}".format(*self._spec) + + @property + def _canonical_spec(self) -> tuple[str, str]: + canonical_version = canonicalize_version( + self._spec[1], + strip_trailing_zero=(self._spec[0] != "~="), + ) + return self._spec[0], canonical_version + + def __hash__(self) -> int: + return hash(self._canonical_spec) + + def __eq__(self, other: object) -> bool: + """Whether or not the two Specifier-like objects are equal. + + :param other: The other object to check against. + + The value of :attr:`prereleases` is ignored. + + >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0") + True + >>> (Specifier("==1.2.3", prereleases=False) == + ... Specifier("==1.2.3", prereleases=True)) + True + >>> Specifier("==1.2.3") == "==1.2.3" + True + >>> Specifier("==1.2.3") == Specifier("==1.2.4") + False + >>> Specifier("==1.2.3") == Specifier("~=1.2.3") + False + """ + if isinstance(other, str): + try: + other = self.__class__(str(other)) + except InvalidSpecifier: + return NotImplemented + elif not isinstance(other, self.__class__): + return NotImplemented + + return self._canonical_spec == other._canonical_spec + + def _get_operator(self, op: str) -> CallableOperator: + operator_callable: CallableOperator = getattr( + self, f"_compare_{self._operators[op]}" + ) + return operator_callable + + def _compare_compatible(self, prospective: Version, spec: str) -> bool: + # Compatible releases have an equivalent combination of >= and ==. That + # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to + # implement this in terms of the other specifiers instead of + # implementing it ourselves. The only thing we need to do is construct + # the other specifiers. + + # We want everything but the last item in the version, but we want to + # ignore suffix segments. + prefix = _version_join( + list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1] + ) + + # Add the prefix notation to the end of our string + prefix += ".*" + + return self._get_operator(">=")(prospective, spec) and self._get_operator("==")( + prospective, prefix + ) + + def _compare_equal(self, prospective: Version, spec: str) -> bool: + # We need special logic to handle prefix matching + if spec.endswith(".*"): + # In the case of prefix matching we want to ignore local segment. + normalized_prospective = canonicalize_version( + prospective.public, strip_trailing_zero=False + ) + # Get the normalized version string ignoring the trailing .* + normalized_spec = canonicalize_version(spec[:-2], strip_trailing_zero=False) + # Split the spec out by bangs and dots, and pretend that there is + # an implicit dot in between a release segment and a pre-release segment. + split_spec = _version_split(normalized_spec) + + # Split the prospective version out by bangs and dots, and pretend + # that there is an implicit dot in between a release segment and + # a pre-release segment. + split_prospective = _version_split(normalized_prospective) + + # 0-pad the prospective version before shortening it to get the correct + # shortened version. + padded_prospective, _ = _pad_version(split_prospective, split_spec) + + # Shorten the prospective version to be the same length as the spec + # so that we can determine if the specifier is a prefix of the + # prospective version or not. + shortened_prospective = padded_prospective[: len(split_spec)] + + return shortened_prospective == split_spec + else: + # Convert our spec string into a Version + spec_version = Version(spec) + + # If the specifier does not have a local segment, then we want to + # act as if the prospective version also does not have a local + # segment. + if not spec_version.local: + prospective = Version(prospective.public) + + return prospective == spec_version + + def _compare_not_equal(self, prospective: Version, spec: str) -> bool: + return not self._compare_equal(prospective, spec) + + def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool: + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) <= Version(spec) + + def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool: + # NB: Local version identifiers are NOT permitted in the version + # specifier, so local version labels can be universally removed from + # the prospective version. + return Version(prospective.public) >= Version(spec) + + def _compare_less_than(self, prospective: Version, spec_str: str) -> bool: + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is less than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective < spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a pre-release version, that we do not accept pre-release + # versions for the version mentioned in the specifier (e.g. <3.1 should + # not match 3.1.dev0, but should match 3.0.dev0). + if not spec.is_prerelease and prospective.is_prerelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # less than the spec version *and* it's not a pre-release of the same + # version in the spec. + return True + + def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool: + # Convert our spec to a Version instance, since we'll want to work with + # it as a version. + spec = Version(spec_str) + + # Check to see if the prospective version is greater than the spec + # version. If it's not we can short circuit and just return False now + # instead of doing extra unneeded work. + if not prospective > spec: + return False + + # This special case is here so that, unless the specifier itself + # includes is a post-release version, that we do not accept + # post-release versions for the version mentioned in the specifier + # (e.g. >3.1 should not match 3.0.post0, but should match 3.2.post0). + if not spec.is_postrelease and prospective.is_postrelease: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # Ensure that we do not allow a local version of the version mentioned + # in the specifier, which is technically greater than, to match. + if prospective.local is not None: + if Version(prospective.base_version) == Version(spec.base_version): + return False + + # If we've gotten to here, it means that prospective version is both + # greater than the spec version *and* it's not a pre-release of the + # same version in the spec. + return True + + def _compare_arbitrary(self, prospective: Version, spec: str) -> bool: + return str(prospective).lower() == str(spec).lower() + + def __contains__(self, item: str | Version) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: The item to check for. + + This is used for the ``in`` operator and behaves the same as + :meth:`contains` with no ``prereleases`` argument passed. + + >>> "1.2.3" in Specifier(">=1.2.3") + True + >>> Version("1.2.3") in Specifier(">=1.2.3") + True + >>> "1.0.0" in Specifier(">=1.2.3") + False + >>> "1.3.0a1" in Specifier(">=1.2.3") + False + >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True) + True + """ + return self.contains(item) + + def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: + The item to check for, which can be a version string or a + :class:`Version` instance. + :param prereleases: + Whether or not to match prereleases with this Specifier. If set to + ``None`` (the default), it uses :attr:`prereleases` to determine + whether or not prereleases are allowed. + + >>> Specifier(">=1.2.3").contains("1.2.3") + True + >>> Specifier(">=1.2.3").contains(Version("1.2.3")) + True + >>> Specifier(">=1.2.3").contains("1.0.0") + False + >>> Specifier(">=1.2.3").contains("1.3.0a1") + False + >>> Specifier(">=1.2.3", prereleases=True).contains("1.3.0a1") + True + >>> Specifier(">=1.2.3").contains("1.3.0a1", prereleases=True) + True + """ + + # Determine if prereleases are to be allowed or not. + if prereleases is None: + prereleases = self.prereleases + + # Normalize item to a Version, this allows us to have a shortcut for + # "2.0" in Specifier(">=2") + normalized_item = _coerce_version(item) + + # Determine if we should be supporting prereleases in this specifier + # or not, if we do not support prereleases than we can short circuit + # logic if this version is a prereleases. + if normalized_item.is_prerelease and not prereleases: + return False + + # Actually do the comparison to determine if this item is contained + # within this Specifier or not. + operator_callable: CallableOperator = self._get_operator(self.operator) + return operator_callable(normalized_item, self.version) + + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None + ) -> Iterator[UnparsedVersionVar]: + """Filter items in the given iterable, that match the specifier. + + :param iterable: + An iterable that can contain version strings and :class:`Version` instances. + The items in the iterable will be filtered according to the specifier. + :param prereleases: + Whether or not to allow prereleases in the returned iterator. If set to + ``None`` (the default), it will be intelligently decide whether to allow + prereleases or not (based on the :attr:`prereleases` attribute, and + whether the only versions matching are prereleases). + + This method is smarter than just ``filter(Specifier().contains, [...])`` + because it implements the rule from :pep:`440` that a prerelease item + SHOULD be accepted if no other versions match the given specifier. + + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) + ['1.3'] + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")])) + ['1.2.3', '1.3', ] + >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"])) + ['1.5a1'] + >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + """ + + yielded = False + found_prereleases = [] + + kw = {"prereleases": prereleases if prereleases is not None else True} + + # Attempt to iterate over all the values in the iterable and if any of + # them match, yield them. + for version in iterable: + parsed_version = _coerce_version(version) + + if self.contains(parsed_version, **kw): + # If our version is a prerelease, and we were not set to allow + # prereleases, then we'll store it for later in case nothing + # else matches this specifier. + if parsed_version.is_prerelease and not ( + prereleases or self.prereleases + ): + found_prereleases.append(version) + # Either this is not a prerelease, or we should have been + # accepting prereleases from the beginning. + else: + yielded = True + yield version + + # Now that we've iterated over everything, determine if we've yielded + # any values, and if we have not and we have any prereleases stored up + # then we will go ahead and yield the prereleases. + if not yielded and found_prereleases: + for version in found_prereleases: + yield version + + +_prefix_regex = re.compile(r"^([0-9]+)((?:a|b|c|rc)[0-9]+)$") + + +def _version_split(version: str) -> list[str]: + """Split version into components. + + The split components are intended for version comparison. The logic does + not attempt to retain the original version string, so joining the + components back with :func:`_version_join` may not produce the original + version string. + """ + result: list[str] = [] + + epoch, _, rest = version.rpartition("!") + result.append(epoch or "0") + + for item in rest.split("."): + match = _prefix_regex.search(item) + if match: + result.extend(match.groups()) + else: + result.append(item) + return result + + +def _version_join(components: list[str]) -> str: + """Join split version components into a version string. + + This function assumes the input came from :func:`_version_split`, where the + first component must be the epoch (either empty or numeric), and all other + components numeric. + """ + epoch, *rest = components + return f"{epoch}!{'.'.join(rest)}" + + +def _is_not_suffix(segment: str) -> bool: + return not any( + segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post") + ) + + +def _pad_version(left: list[str], right: list[str]) -> tuple[list[str], list[str]]: + left_split, right_split = [], [] + + # Get the release segment of our versions + left_split.append(list(itertools.takewhile(lambda x: x.isdigit(), left))) + right_split.append(list(itertools.takewhile(lambda x: x.isdigit(), right))) + + # Get the rest of our versions + left_split.append(left[len(left_split[0]) :]) + right_split.append(right[len(right_split[0]) :]) + + # Insert our padding + left_split.insert(1, ["0"] * max(0, len(right_split[0]) - len(left_split[0]))) + right_split.insert(1, ["0"] * max(0, len(left_split[0]) - len(right_split[0]))) + + return ( + list(itertools.chain.from_iterable(left_split)), + list(itertools.chain.from_iterable(right_split)), + ) + + +class SpecifierSet(BaseSpecifier): + """This class abstracts handling of a set of version specifiers. + + It can be passed a single specifier (``>=3.0``), a comma-separated list of + specifiers (``>=3.0,!=3.1``), or no specifier at all. + """ + + def __init__( + self, + specifiers: str | Iterable[Specifier] = "", + prereleases: bool | None = None, + ) -> None: + """Initialize a SpecifierSet instance. + + :param specifiers: + The string representation of a specifier or a comma-separated list of + specifiers which will be parsed and normalized before use. + May also be an iterable of ``Specifier`` instances, which will be used + as is. + :param prereleases: + This tells the SpecifierSet if it should accept prerelease versions if + applicable or not. The default of ``None`` will autodetect it from the + given specifiers. + + :raises InvalidSpecifier: + If the given ``specifiers`` are not parseable than this exception will be + raised. + """ + + if isinstance(specifiers, str): + # Split on `,` to break each individual specifier into its own item, and + # strip each item to remove leading/trailing whitespace. + split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()] + + # Make each individual specifier a Specifier and save in a frozen set + # for later. + self._specs = frozenset(map(Specifier, split_specifiers)) + else: + # Save the supplied specifiers in a frozen set. + self._specs = frozenset(specifiers) + + # Store our prereleases value so we can use it later to determine if + # we accept prereleases or not. + self._prereleases = prereleases + + @property + def prereleases(self) -> bool | None: + # If we have been given an explicit prerelease modifier, then we'll + # pass that through here. + if self._prereleases is not None: + return self._prereleases + + # If we don't have any specifiers, and we don't have a forced value, + # then we'll just return None since we don't know if this should have + # pre-releases or not. + if not self._specs: + return None + + # Otherwise we'll see if any of the given specifiers accept + # prereleases, if any of them do we'll return True, otherwise False. + return any(s.prereleases for s in self._specs) + + @prereleases.setter + def prereleases(self, value: bool) -> None: + self._prereleases = value + + def __repr__(self) -> str: + """A representation of the specifier set that shows all internal state. + + Note that the ordering of the individual specifiers within the set may not + match the input string. + + >>> SpecifierSet('>=1.0.0,!=2.0.0') + =1.0.0')> + >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False) + =1.0.0', prereleases=False)> + >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True) + =1.0.0', prereleases=True)> + """ + pre = ( + f", prereleases={self.prereleases!r}" + if self._prereleases is not None + else "" + ) + + return f"" + + def __str__(self) -> str: + """A string representation of the specifier set that can be round-tripped. + + Note that the ordering of the individual specifiers within the set may not + match the input string. + + >>> str(SpecifierSet(">=1.0.0,!=1.0.1")) + '!=1.0.1,>=1.0.0' + >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False)) + '!=1.0.1,>=1.0.0' + """ + return ",".join(sorted(str(s) for s in self._specs)) + + def __hash__(self) -> int: + return hash(self._specs) + + def __and__(self, other: SpecifierSet | str) -> SpecifierSet: + """Return a SpecifierSet which is a combination of the two sets. + + :param other: The other object to combine with. + + >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1' + =1.0.0')> + >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1') + =1.0.0')> + """ + if isinstance(other, str): + other = SpecifierSet(other) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + specifier = SpecifierSet() + specifier._specs = frozenset(self._specs | other._specs) + + if self._prereleases is None and other._prereleases is not None: + specifier._prereleases = other._prereleases + elif self._prereleases is not None and other._prereleases is None: + specifier._prereleases = self._prereleases + elif self._prereleases == other._prereleases: + specifier._prereleases = self._prereleases + else: + raise ValueError( + "Cannot combine SpecifierSets with True and False prerelease overrides." + ) + + return specifier + + def __eq__(self, other: object) -> bool: + """Whether or not the two SpecifierSet-like objects are equal. + + :param other: The other object to check against. + + The value of :attr:`prereleases` is ignored. + + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) == + ... SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)) + True + >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1" + True + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2") + False + """ + if isinstance(other, (str, Specifier)): + other = SpecifierSet(str(other)) + elif not isinstance(other, SpecifierSet): + return NotImplemented + + return self._specs == other._specs + + def __len__(self) -> int: + """Returns the number of specifiers in this specifier set.""" + return len(self._specs) + + def __iter__(self) -> Iterator[Specifier]: + """ + Returns an iterator over all the underlying :class:`Specifier` instances + in this specifier set. + + >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str) + [, =1.0.0')>] + """ + return iter(self._specs) + + def __contains__(self, item: UnparsedVersion) -> bool: + """Return whether or not the item is contained in this specifier. + + :param item: The item to check for. + + This is used for the ``in`` operator and behaves the same as + :meth:`contains` with no ``prereleases`` argument passed. + + >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1") + True + >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1") + False + >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1") + False + >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True) + True + """ + return self.contains(item) + + def contains( + self, + item: UnparsedVersion, + prereleases: bool | None = None, + installed: bool | None = None, + ) -> bool: + """Return whether or not the item is contained in this SpecifierSet. + + :param item: + The item to check for, which can be a version string or a + :class:`Version` instance. + :param prereleases: + Whether or not to match prereleases with this SpecifierSet. If set to + ``None`` (the default), it uses :attr:`prereleases` to determine + whether or not prereleases are allowed. + + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3") + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3")) + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1") + False + >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True).contains("1.3.0a1") + True + >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True) + True + """ + # Ensure that our item is a Version instance. + if not isinstance(item, Version): + item = Version(item) + + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # We can determine if we're going to allow pre-releases by looking to + # see if any of the underlying items supports them. If none of them do + # and this item is a pre-release then we do not allow it and we can + # short circuit that here. + # Note: This means that 1.0.dev1 would not be contained in something + # like >=1.0.devabc however it would be in >=1.0.debabc,>0.0.dev0 + if not prereleases and item.is_prerelease: + return False + + if installed and item.is_prerelease: + item = Version(item.base_version) + + # We simply dispatch to the underlying specs here to make sure that the + # given version is contained within all of them. + # Note: This use of all() here means that an empty set of specifiers + # will always return True, this is an explicit design decision. + return all(s.contains(item, prereleases=prereleases) for s in self._specs) + + def filter( + self, iterable: Iterable[UnparsedVersionVar], prereleases: bool | None = None + ) -> Iterator[UnparsedVersionVar]: + """Filter items in the given iterable, that match the specifiers in this set. + + :param iterable: + An iterable that can contain version strings and :class:`Version` instances. + The items in the iterable will be filtered according to the specifier. + :param prereleases: + Whether or not to allow prereleases in the returned iterator. If set to + ``None`` (the default), it will be intelligently decide whether to allow + prereleases or not (based on the :attr:`prereleases` attribute, and + whether the only versions matching are prereleases). + + This method is smarter than just ``filter(SpecifierSet(...).contains, [...])`` + because it implements the rule from :pep:`440` that a prerelease item + SHOULD be accepted if no other versions match the given specifier. + + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"])) + ['1.3'] + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")])) + ['1.3', ] + >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"])) + [] + >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + + An "empty" SpecifierSet will filter items based on the presence of prerelease + versions in the set. + + >>> list(SpecifierSet("").filter(["1.3", "1.5a1"])) + ['1.3'] + >>> list(SpecifierSet("").filter(["1.5a1"])) + ['1.5a1'] + >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"])) + ['1.3', '1.5a1'] + >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True)) + ['1.3', '1.5a1'] + """ + # Determine if we're forcing a prerelease or not, if we're not forcing + # one for this particular filter call, then we'll use whatever the + # SpecifierSet thinks for whether or not we should support prereleases. + if prereleases is None: + prereleases = self.prereleases + + # If we have any specifiers, then we want to wrap our iterable in the + # filter method for each one, this will act as a logical AND amongst + # each specifier. + if self._specs: + for spec in self._specs: + iterable = spec.filter(iterable, prereleases=bool(prereleases)) + return iter(iterable) + # If we do not have any specifiers, then we need to have a rough filter + # which will filter out any pre-releases, unless there are no final + # releases. + else: + filtered: list[UnparsedVersionVar] = [] + found_prereleases: list[UnparsedVersionVar] = [] + + for item in iterable: + parsed_version = _coerce_version(item) + + # Store any item which is a pre-release for later unless we've + # already found a final version or we are accepting prereleases + if parsed_version.is_prerelease and not prereleases: + if not filtered: + found_prereleases.append(item) + else: + filtered.append(item) + + # If we've found no items except for pre-releases, then we'll go + # ahead and use the pre-releases + if not filtered and found_prereleases and prereleases is None: + return iter(found_prereleases) + + return iter(filtered) diff --git a/server/libs/packaging/tags.py b/server/libs/packaging/tags.py new file mode 100644 index 0000000..8522f59 --- /dev/null +++ b/server/libs/packaging/tags.py @@ -0,0 +1,656 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import logging +import platform +import re +import struct +import subprocess +import sys +import sysconfig +from importlib.machinery import EXTENSION_SUFFIXES +from typing import ( + Iterable, + Iterator, + Sequence, + Tuple, + cast, +) + +from . import _manylinux, _musllinux + +logger = logging.getLogger(__name__) + +PythonVersion = Sequence[int] +AppleVersion = Tuple[int, int] + +INTERPRETER_SHORT_NAMES: dict[str, str] = { + "python": "py", # Generic. + "cpython": "cp", + "pypy": "pp", + "ironpython": "ip", + "jython": "jy", +} + + +_32_BIT_INTERPRETER = struct.calcsize("P") == 4 + + +class Tag: + """ + A representation of the tag triple for a wheel. + + Instances are considered immutable and thus are hashable. Equality checking + is also supported. + """ + + __slots__ = ["_abi", "_hash", "_interpreter", "_platform"] + + def __init__(self, interpreter: str, abi: str, platform: str) -> None: + self._interpreter = interpreter.lower() + self._abi = abi.lower() + self._platform = platform.lower() + # The __hash__ of every single element in a Set[Tag] will be evaluated each time + # that a set calls its `.disjoint()` method, which may be called hundreds of + # times when scanning a page of links for packages with tags matching that + # Set[Tag]. Pre-computing the value here produces significant speedups for + # downstream consumers. + self._hash = hash((self._interpreter, self._abi, self._platform)) + + @property + def interpreter(self) -> str: + return self._interpreter + + @property + def abi(self) -> str: + return self._abi + + @property + def platform(self) -> str: + return self._platform + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Tag): + return NotImplemented + + return ( + (self._hash == other._hash) # Short-circuit ASAP for perf reasons. + and (self._platform == other._platform) + and (self._abi == other._abi) + and (self._interpreter == other._interpreter) + ) + + def __hash__(self) -> int: + return self._hash + + def __str__(self) -> str: + return f"{self._interpreter}-{self._abi}-{self._platform}" + + def __repr__(self) -> str: + return f"<{self} @ {id(self)}>" + + +def parse_tag(tag: str) -> frozenset[Tag]: + """ + Parses the provided tag (e.g. `py3-none-any`) into a frozenset of Tag instances. + + Returning a set is required due to the possibility that the tag is a + compressed tag set. + """ + tags = set() + interpreters, abis, platforms = tag.split("-") + for interpreter in interpreters.split("."): + for abi in abis.split("."): + for platform_ in platforms.split("."): + tags.add(Tag(interpreter, abi, platform_)) + return frozenset(tags) + + +def _get_config_var(name: str, warn: bool = False) -> int | str | None: + value: int | str | None = sysconfig.get_config_var(name) + if value is None and warn: + logger.debug( + "Config variable '%s' is unset, Python ABI tag may be incorrect", name + ) + return value + + +def _normalize_string(string: str) -> str: + return string.replace(".", "_").replace("-", "_").replace(" ", "_") + + +def _is_threaded_cpython(abis: list[str]) -> bool: + """ + Determine if the ABI corresponds to a threaded (`--disable-gil`) build. + + The threaded builds are indicated by a "t" in the abiflags. + """ + if len(abis) == 0: + return False + # expect e.g., cp313 + m = re.match(r"cp\d+(.*)", abis[0]) + if not m: + return False + abiflags = m.group(1) + return "t" in abiflags + + +def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool: + """ + Determine if the Python version supports abi3. + + PEP 384 was first implemented in Python 3.2. The threaded (`--disable-gil`) + builds do not support abi3. + """ + return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading + + +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]: + py_version = tuple(py_version) # To allow for version comparison. + abis = [] + version = _version_nodot(py_version[:2]) + threading = debug = pymalloc = ucs4 = "" + with_debug = _get_config_var("Py_DEBUG", warn) + has_refcount = hasattr(sys, "gettotalrefcount") + # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled + # extension modules is the best option. + # https://github.com/pypa/pip/issues/3383#issuecomment-173267692 + has_ext = "_d.pyd" in EXTENSION_SUFFIXES + if with_debug or (with_debug is None and (has_refcount or has_ext)): + debug = "d" + if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn): + threading = "t" + if py_version < (3, 8): + with_pymalloc = _get_config_var("WITH_PYMALLOC", warn) + if with_pymalloc or with_pymalloc is None: + pymalloc = "m" + if py_version < (3, 3): + unicode_size = _get_config_var("Py_UNICODE_SIZE", warn) + if unicode_size == 4 or ( + unicode_size is None and sys.maxunicode == 0x10FFFF + ): + ucs4 = "u" + elif debug: + # Debug builds can also load "normal" extension modules. + # We can also assume no UCS-4 or pymalloc requirement. + abis.append(f"cp{version}{threading}") + abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}") + return abis + + +def cpython_tags( + python_version: PythonVersion | None = None, + abis: Iterable[str] | None = None, + platforms: Iterable[str] | None = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a CPython interpreter. + + The tags consist of: + - cp-- + - cp-abi3- + - cp-none- + - cp-abi3- # Older Python versions down to 3.2. + + If python_version only specifies a major version then user-provided ABIs and + the 'none' ABItag will be used. + + If 'abi3' or 'none' are specified in 'abis' then they will be yielded at + their normal position and not at the beginning. + """ + if not python_version: + python_version = sys.version_info[:2] + + interpreter = f"cp{_version_nodot(python_version[:2])}" + + if abis is None: + if len(python_version) > 1: + abis = _cpython_abis(python_version, warn) + else: + abis = [] + abis = list(abis) + # 'abi3' and 'none' are explicitly handled later. + for explicit_abi in ("abi3", "none"): + try: + abis.remove(explicit_abi) + except ValueError: + pass + + platforms = list(platforms or platform_tags()) + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + threading = _is_threaded_cpython(abis) + use_abi3 = _abi3_applies(python_version, threading) + if use_abi3: + yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms) + yield from (Tag(interpreter, "none", platform_) for platform_ in platforms) + + if use_abi3: + for minor_version in range(python_version[1] - 1, 1, -1): + for platform_ in platforms: + version = _version_nodot((python_version[0], minor_version)) + interpreter = f"cp{version}" + yield Tag(interpreter, "abi3", platform_) + + +def _generic_abi() -> list[str]: + """ + Return the ABI tag based on EXT_SUFFIX. + """ + # The following are examples of `EXT_SUFFIX`. + # We want to keep the parts which are related to the ABI and remove the + # parts which are related to the platform: + # - linux: '.cpython-310-x86_64-linux-gnu.so' => cp310 + # - mac: '.cpython-310-darwin.so' => cp310 + # - win: '.cp310-win_amd64.pyd' => cp310 + # - win: '.pyd' => cp37 (uses _cpython_abis()) + # - pypy: '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73 + # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib' + # => graalpy_38_native + + ext_suffix = _get_config_var("EXT_SUFFIX", warn=True) + if not isinstance(ext_suffix, str) or ext_suffix[0] != ".": + raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')") + parts = ext_suffix.split(".") + if len(parts) < 3: + # CPython3.7 and earlier uses ".pyd" on Windows. + return _cpython_abis(sys.version_info[:2]) + soabi = parts[1] + if soabi.startswith("cpython"): + # non-windows + abi = "cp" + soabi.split("-")[1] + elif soabi.startswith("cp"): + # windows + abi = soabi.split("-")[0] + elif soabi.startswith("pypy"): + abi = "-".join(soabi.split("-")[:2]) + elif soabi.startswith("graalpy"): + abi = "-".join(soabi.split("-")[:3]) + elif soabi: + # pyston, ironpython, others? + abi = soabi + else: + return [] + return [_normalize_string(abi)] + + +def generic_tags( + interpreter: str | None = None, + abis: Iterable[str] | None = None, + platforms: Iterable[str] | None = None, + *, + warn: bool = False, +) -> Iterator[Tag]: + """ + Yields the tags for a generic interpreter. + + The tags consist of: + - -- + + The "none" ABI will be added if it was not explicitly provided. + """ + if not interpreter: + interp_name = interpreter_name() + interp_version = interpreter_version(warn=warn) + interpreter = "".join([interp_name, interp_version]) + if abis is None: + abis = _generic_abi() + else: + abis = list(abis) + platforms = list(platforms or platform_tags()) + if "none" not in abis: + abis.append("none") + for abi in abis: + for platform_ in platforms: + yield Tag(interpreter, abi, platform_) + + +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]: + """ + Yields Python versions in descending order. + + After the latest version, the major-only version will be yielded, and then + all previous versions of that major version. + """ + if len(py_version) > 1: + yield f"py{_version_nodot(py_version[:2])}" + yield f"py{py_version[0]}" + if len(py_version) > 1: + for minor in range(py_version[1] - 1, -1, -1): + yield f"py{_version_nodot((py_version[0], minor))}" + + +def compatible_tags( + python_version: PythonVersion | None = None, + interpreter: str | None = None, + platforms: Iterable[str] | None = None, +) -> Iterator[Tag]: + """ + Yields the sequence of tags that are compatible with a specific version of Python. + + The tags consist of: + - py*-none- + - -none-any # ... if `interpreter` is provided. + - py*-none-any + """ + if not python_version: + python_version = sys.version_info[:2] + platforms = list(platforms or platform_tags()) + for version in _py_interpreter_range(python_version): + for platform_ in platforms: + yield Tag(version, "none", platform_) + if interpreter: + yield Tag(interpreter, "none", "any") + for version in _py_interpreter_range(python_version): + yield Tag(version, "none", "any") + + +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str: + if not is_32bit: + return arch + + if arch.startswith("ppc"): + return "ppc" + + return "i386" + + +def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]: + formats = [cpu_arch] + if cpu_arch == "x86_64": + if version < (10, 4): + return [] + formats.extend(["intel", "fat64", "fat32"]) + + elif cpu_arch == "i386": + if version < (10, 4): + return [] + formats.extend(["intel", "fat32", "fat"]) + + elif cpu_arch == "ppc64": + # TODO: Need to care about 32-bit PPC for ppc64 through 10.2? + if version > (10, 5) or version < (10, 4): + return [] + formats.append("fat64") + + elif cpu_arch == "ppc": + if version > (10, 6): + return [] + formats.extend(["fat32", "fat"]) + + if cpu_arch in {"arm64", "x86_64"}: + formats.append("universal2") + + if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}: + formats.append("universal") + + return formats + + +def mac_platforms( + version: AppleVersion | None = None, arch: str | None = None +) -> Iterator[str]: + """ + Yields the platform tags for a macOS system. + + The `version` parameter is a two-item tuple specifying the macOS version to + generate platform tags for. The `arch` parameter is the CPU architecture to + generate platform tags for. Both parameters default to the appropriate value + for the current system. + """ + version_str, _, cpu_arch = platform.mac_ver() + if version is None: + version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2]))) + if version == (10, 16): + # When built against an older macOS SDK, Python will report macOS 10.16 + # instead of the real version. + version_str = subprocess.run( + [ + sys.executable, + "-sS", + "-c", + "import platform; print(platform.mac_ver()[0])", + ], + check=True, + env={"SYSTEM_VERSION_COMPAT": "0"}, + stdout=subprocess.PIPE, + text=True, + ).stdout + version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2]))) + else: + version = version + if arch is None: + arch = _mac_arch(cpu_arch) + else: + arch = arch + + if (10, 0) <= version and version < (11, 0): + # Prior to Mac OS 11, each yearly release of Mac OS bumped the + # "minor" version number. The major version was always 10. + major_version = 10 + for minor_version in range(version[1], -1, -1): + compat_version = major_version, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield f"macosx_{major_version}_{minor_version}_{binary_format}" + + if version >= (11, 0): + # Starting with Mac OS 11, each yearly release bumps the major version + # number. The minor versions are now the midyear updates. + minor_version = 0 + for major_version in range(version[0], 10, -1): + compat_version = major_version, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield f"macosx_{major_version}_{minor_version}_{binary_format}" + + if version >= (11, 0): + # Mac OS 11 on x86_64 is compatible with binaries from previous releases. + # Arm64 support was introduced in 11.0, so no Arm binaries from previous + # releases exist. + # + # However, the "universal2" binary format can have a + # macOS version earlier than 11.0 when the x86_64 part of the binary supports + # that version of macOS. + major_version = 10 + if arch == "x86_64": + for minor_version in range(16, 3, -1): + compat_version = major_version, minor_version + binary_formats = _mac_binary_formats(compat_version, arch) + for binary_format in binary_formats: + yield f"macosx_{major_version}_{minor_version}_{binary_format}" + else: + for minor_version in range(16, 3, -1): + compat_version = major_version, minor_version + binary_format = "universal2" + yield f"macosx_{major_version}_{minor_version}_{binary_format}" + + +def ios_platforms( + version: AppleVersion | None = None, multiarch: str | None = None +) -> Iterator[str]: + """ + Yields the platform tags for an iOS system. + + :param version: A two-item tuple specifying the iOS version to generate + platform tags for. Defaults to the current iOS version. + :param multiarch: The CPU architecture+ABI to generate platform tags for - + (the value used by `sys.implementation._multiarch` e.g., + `arm64_iphoneos` or `x84_64_iphonesimulator`). Defaults to the current + multiarch value. + """ + if version is None: + # if iOS is the current platform, ios_ver *must* be defined. However, + # it won't exist for CPython versions before 3.13, which causes a mypy + # error. + _, release, _, _ = platform.ios_ver() # type: ignore[attr-defined, unused-ignore] + version = cast("AppleVersion", tuple(map(int, release.split(".")[:2]))) + + if multiarch is None: + multiarch = sys.implementation._multiarch + multiarch = multiarch.replace("-", "_") + + ios_platform_template = "ios_{major}_{minor}_{multiarch}" + + # Consider any iOS major.minor version from the version requested, down to + # 12.0. 12.0 is the first iOS version that is known to have enough features + # to support CPython. Consider every possible minor release up to X.9. There + # highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra + # candidates that won't ever match doesn't really hurt, and it saves us from + # having to keep an explicit list of known iOS versions in the code. Return + # the results descending order of version number. + + # If the requested major version is less than 12, there won't be any matches. + if version[0] < 12: + return + + # Consider the actual X.Y version that was requested. + yield ios_platform_template.format( + major=version[0], minor=version[1], multiarch=multiarch + ) + + # Consider every minor version from X.0 to the minor version prior to the + # version requested by the platform. + for minor in range(version[1] - 1, -1, -1): + yield ios_platform_template.format( + major=version[0], minor=minor, multiarch=multiarch + ) + + for major in range(version[0] - 1, 11, -1): + for minor in range(9, -1, -1): + yield ios_platform_template.format( + major=major, minor=minor, multiarch=multiarch + ) + + +def android_platforms( + api_level: int | None = None, abi: str | None = None +) -> Iterator[str]: + """ + Yields the :attr:`~Tag.platform` tags for Android. If this function is invoked on + non-Android platforms, the ``api_level`` and ``abi`` arguments are required. + + :param int api_level: The maximum `API level + `__ to return. Defaults + to the current system's version, as returned by ``platform.android_ver``. + :param str abi: The `Android ABI `__, + e.g. ``arm64_v8a``. Defaults to the current system's ABI , as returned by + ``sysconfig.get_platform``. Hyphens and periods will be replaced with + underscores. + """ + if platform.system() != "Android" and (api_level is None or abi is None): + raise TypeError( + "on non-Android platforms, the api_level and abi arguments are required" + ) + + if api_level is None: + # Python 3.13 was the first version to return platform.system() == "Android", + # and also the first version to define platform.android_ver(). + api_level = platform.android_ver().api_level # type: ignore[attr-defined] + + if abi is None: + abi = sysconfig.get_platform().split("-")[-1] + abi = _normalize_string(abi) + + # 16 is the minimum API level known to have enough features to support CPython + # without major patching. Yield every API level from the maximum down to the + # minimum, inclusive. + min_api_level = 16 + for ver in range(api_level, min_api_level - 1, -1): + yield f"android_{ver}_{abi}" + + +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]: + linux = _normalize_string(sysconfig.get_platform()) + if not linux.startswith("linux_"): + # we should never be here, just yield the sysconfig one and return + yield linux + return + if is_32bit: + if linux == "linux_x86_64": + linux = "linux_i686" + elif linux == "linux_aarch64": + linux = "linux_armv8l" + _, arch = linux.split("_", 1) + archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch]) + yield from _manylinux.platform_tags(archs) + yield from _musllinux.platform_tags(archs) + for arch in archs: + yield f"linux_{arch}" + + +def _generic_platforms() -> Iterator[str]: + yield _normalize_string(sysconfig.get_platform()) + + +def platform_tags() -> Iterator[str]: + """ + Provides the platform tags for this installation. + """ + if platform.system() == "Darwin": + return mac_platforms() + elif platform.system() == "iOS": + return ios_platforms() + elif platform.system() == "Android": + return android_platforms() + elif platform.system() == "Linux": + return _linux_platforms() + else: + return _generic_platforms() + + +def interpreter_name() -> str: + """ + Returns the name of the running interpreter. + + Some implementations have a reserved, two-letter abbreviation which will + be returned when appropriate. + """ + name = sys.implementation.name + return INTERPRETER_SHORT_NAMES.get(name) or name + + +def interpreter_version(*, warn: bool = False) -> str: + """ + Returns the version of the running interpreter. + """ + version = _get_config_var("py_version_nodot", warn=warn) + if version: + version = str(version) + else: + version = _version_nodot(sys.version_info[:2]) + return version + + +def _version_nodot(version: PythonVersion) -> str: + return "".join(map(str, version)) + + +def sys_tags(*, warn: bool = False) -> Iterator[Tag]: + """ + Returns the sequence of tag triples for the running interpreter. + + The order of the sequence corresponds to priority order for the + interpreter, from most to least important. + """ + + interp_name = interpreter_name() + if interp_name == "cp": + yield from cpython_tags(warn=warn) + else: + yield from generic_tags() + + if interp_name == "pp": + interp = "pp3" + elif interp_name == "cp": + interp = "cp" + interpreter_version(warn=warn) + else: + interp = None + yield from compatible_tags(interpreter=interp) diff --git a/server/libs/packaging/utils.py b/server/libs/packaging/utils.py new file mode 100644 index 0000000..2345095 --- /dev/null +++ b/server/libs/packaging/utils.py @@ -0,0 +1,163 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import annotations + +import functools +import re +from typing import NewType, Tuple, Union, cast + +from .tags import Tag, parse_tag +from .version import InvalidVersion, Version, _TrimmedRelease + +BuildTag = Union[Tuple[()], Tuple[int, str]] +NormalizedName = NewType("NormalizedName", str) + + +class InvalidName(ValueError): + """ + An invalid distribution name; users should refer to the packaging user guide. + """ + + +class InvalidWheelFilename(ValueError): + """ + An invalid wheel filename was found, users should refer to PEP 427. + """ + + +class InvalidSdistFilename(ValueError): + """ + An invalid sdist filename was found, users should refer to the packaging user guide. + """ + + +# Core metadata spec for `Name` +_validate_regex = re.compile( + r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", re.IGNORECASE +) +_canonicalize_regex = re.compile(r"[-_.]+") +_normalized_regex = re.compile(r"^([a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9])$") +# PEP 427: The build number must start with a digit. +_build_tag_regex = re.compile(r"(\d+)(.*)") + + +def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName: + if validate and not _validate_regex.match(name): + raise InvalidName(f"name is invalid: {name!r}") + # This is taken from PEP 503. + value = _canonicalize_regex.sub("-", name).lower() + return cast(NormalizedName, value) + + +def is_normalized_name(name: str) -> bool: + return _normalized_regex.match(name) is not None + + +@functools.singledispatch +def canonicalize_version( + version: Version | str, *, strip_trailing_zero: bool = True +) -> str: + """ + Return a canonical form of a version as a string. + + >>> canonicalize_version('1.0.1') + '1.0.1' + + Per PEP 625, versions may have multiple canonical forms, differing + only by trailing zeros. + + >>> canonicalize_version('1.0.0') + '1' + >>> canonicalize_version('1.0.0', strip_trailing_zero=False) + '1.0.0' + + Invalid versions are returned unaltered. + + >>> canonicalize_version('foo bar baz') + 'foo bar baz' + """ + return str(_TrimmedRelease(str(version)) if strip_trailing_zero else version) + + +@canonicalize_version.register +def _(version: str, *, strip_trailing_zero: bool = True) -> str: + try: + parsed = Version(version) + except InvalidVersion: + # Legacy versions cannot be normalized + return version + return canonicalize_version(parsed, strip_trailing_zero=strip_trailing_zero) + + +def parse_wheel_filename( + filename: str, +) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]: + if not filename.endswith(".whl"): + raise InvalidWheelFilename( + f"Invalid wheel filename (extension must be '.whl'): {filename!r}" + ) + + filename = filename[:-4] + dashes = filename.count("-") + if dashes not in (4, 5): + raise InvalidWheelFilename( + f"Invalid wheel filename (wrong number of parts): {filename!r}" + ) + + parts = filename.split("-", dashes - 2) + name_part = parts[0] + # See PEP 427 for the rules on escaping the project name. + if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None: + raise InvalidWheelFilename(f"Invalid project name: {filename!r}") + name = canonicalize_name(name_part) + + try: + version = Version(parts[1]) + except InvalidVersion as e: + raise InvalidWheelFilename( + f"Invalid wheel filename (invalid version): {filename!r}" + ) from e + + if dashes == 5: + build_part = parts[2] + build_match = _build_tag_regex.match(build_part) + if build_match is None: + raise InvalidWheelFilename( + f"Invalid build number: {build_part} in {filename!r}" + ) + build = cast(BuildTag, (int(build_match.group(1)), build_match.group(2))) + else: + build = () + tags = parse_tag(parts[-1]) + return (name, version, build, tags) + + +def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]: + if filename.endswith(".tar.gz"): + file_stem = filename[: -len(".tar.gz")] + elif filename.endswith(".zip"): + file_stem = filename[: -len(".zip")] + else: + raise InvalidSdistFilename( + f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):" + f" {filename!r}" + ) + + # We are requiring a PEP 440 version, which cannot contain dashes, + # so we split on the last dash. + name_part, sep, version_part = file_stem.rpartition("-") + if not sep: + raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}") + + name = canonicalize_name(name_part) + + try: + version = Version(version_part) + except InvalidVersion as e: + raise InvalidSdistFilename( + f"Invalid sdist filename (invalid version): {filename!r}" + ) from e + + return (name, version) diff --git a/server/libs/packaging/version.py b/server/libs/packaging/version.py new file mode 100644 index 0000000..c9bbda2 --- /dev/null +++ b/server/libs/packaging/version.py @@ -0,0 +1,582 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +""" +.. testsetup:: + + from packaging.version import parse, Version +""" + +from __future__ import annotations + +import itertools +import re +from typing import Any, Callable, NamedTuple, SupportsInt, Tuple, Union + +from ._structures import Infinity, InfinityType, NegativeInfinity, NegativeInfinityType + +__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "parse"] + +LocalType = Tuple[Union[int, str], ...] + +CmpPrePostDevType = Union[InfinityType, NegativeInfinityType, Tuple[str, int]] +CmpLocalType = Union[ + NegativeInfinityType, + Tuple[Union[Tuple[int, str], Tuple[NegativeInfinityType, Union[int, str]]], ...], +] +CmpKey = Tuple[ + int, + Tuple[int, ...], + CmpPrePostDevType, + CmpPrePostDevType, + CmpPrePostDevType, + CmpLocalType, +] +VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool] + + +class _Version(NamedTuple): + epoch: int + release: tuple[int, ...] + dev: tuple[str, int] | None + pre: tuple[str, int] | None + post: tuple[str, int] | None + local: LocalType | None + + +def parse(version: str) -> Version: + """Parse the given version string. + + >>> parse('1.0.dev1') + + + :param version: The version string to parse. + :raises InvalidVersion: When the version string is not a valid version. + """ + return Version(version) + + +class InvalidVersion(ValueError): + """Raised when a version string is not a valid version. + + >>> Version("invalid") + Traceback (most recent call last): + ... + packaging.version.InvalidVersion: Invalid version: 'invalid' + """ + + +class _BaseVersion: + _key: tuple[Any, ...] + + def __hash__(self) -> int: + return hash(self._key) + + # Please keep the duplicated `isinstance` check + # in the six comparisons hereunder + # unless you find a way to avoid adding overhead function calls. + def __lt__(self, other: _BaseVersion) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key < other._key + + def __le__(self, other: _BaseVersion) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key <= other._key + + def __eq__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key == other._key + + def __ge__(self, other: _BaseVersion) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key >= other._key + + def __gt__(self, other: _BaseVersion) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key > other._key + + def __ne__(self, other: object) -> bool: + if not isinstance(other, _BaseVersion): + return NotImplemented + + return self._key != other._key + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +_VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                          # pre-release
+            [-_\.]?
+            (?Palpha|a|beta|b|preview|pre|c|rc)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+        (?P                                         # post release
+            (?:-(?P[0-9]+))
+            |
+            (?:
+                [-_\.]?
+                (?Ppost|rev|r)
+                [-_\.]?
+                (?P[0-9]+)?
+            )
+        )?
+        (?P                                          # dev release
+            [-_\.]?
+            (?Pdev)
+            [-_\.]?
+            (?P[0-9]+)?
+        )?
+    )
+    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
+"""
+
+VERSION_PATTERN = _VERSION_PATTERN
+"""
+A string containing the regular expression used to match a valid version.
+
+The pattern is not anchored at either end, and is intended for embedding in larger
+expressions (for example, matching a version number as part of a file name). The
+regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
+flags set.
+
+:meta hide-value:
+"""
+
+
+class Version(_BaseVersion):
+    """This class abstracts handling of a project's versions.
+
+    A :class:`Version` instance is comparison aware and can be compared and
+    sorted using the standard Python interfaces.
+
+    >>> v1 = Version("1.0a5")
+    >>> v2 = Version("1.0")
+    >>> v1
+    
+    >>> v2
+    
+    >>> v1 < v2
+    True
+    >>> v1 == v2
+    False
+    >>> v1 > v2
+    False
+    >>> v1 >= v2
+    False
+    >>> v1 <= v2
+    True
+    """
+
+    _regex = re.compile(r"^\s*" + VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE)
+    _key: CmpKey
+
+    def __init__(self, version: str) -> None:
+        """Initialize a Version object.
+
+        :param version:
+            The string representation of a version which will be parsed and normalized
+            before use.
+        :raises InvalidVersion:
+            If the ``version`` does not conform to PEP 440 in any way then this
+            exception will be raised.
+        """
+
+        # Validate the version and parse it into pieces
+        match = self._regex.search(version)
+        if not match:
+            raise InvalidVersion(f"Invalid version: {version!r}")
+
+        # Store the parsed out pieces of the version
+        self._version = _Version(
+            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
+            release=tuple(int(i) for i in match.group("release").split(".")),
+            pre=_parse_letter_version(match.group("pre_l"), match.group("pre_n")),
+            post=_parse_letter_version(
+                match.group("post_l"), match.group("post_n1") or match.group("post_n2")
+            ),
+            dev=_parse_letter_version(match.group("dev_l"), match.group("dev_n")),
+            local=_parse_local_version(match.group("local")),
+        )
+
+        # Generate a key which will be used for sorting
+        self._key = _cmpkey(
+            self._version.epoch,
+            self._version.release,
+            self._version.pre,
+            self._version.post,
+            self._version.dev,
+            self._version.local,
+        )
+
+    def __repr__(self) -> str:
+        """A representation of the Version that shows all internal state.
+
+        >>> Version('1.0.0')
+        
+        """
+        return f""
+
+    def __str__(self) -> str:
+        """A string representation of the version that can be round-tripped.
+
+        >>> str(Version("1.0a5"))
+        '1.0a5'
+        """
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append(f"{self.epoch}!")
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        # Pre-release
+        if self.pre is not None:
+            parts.append("".join(str(x) for x in self.pre))
+
+        # Post-release
+        if self.post is not None:
+            parts.append(f".post{self.post}")
+
+        # Development release
+        if self.dev is not None:
+            parts.append(f".dev{self.dev}")
+
+        # Local version segment
+        if self.local is not None:
+            parts.append(f"+{self.local}")
+
+        return "".join(parts)
+
+    @property
+    def epoch(self) -> int:
+        """The epoch of the version.
+
+        >>> Version("2.0.0").epoch
+        0
+        >>> Version("1!2.0.0").epoch
+        1
+        """
+        return self._version.epoch
+
+    @property
+    def release(self) -> tuple[int, ...]:
+        """The components of the "release" segment of the version.
+
+        >>> Version("1.2.3").release
+        (1, 2, 3)
+        >>> Version("2.0.0").release
+        (2, 0, 0)
+        >>> Version("1!2.0.0.post0").release
+        (2, 0, 0)
+
+        Includes trailing zeroes but not the epoch or any pre-release / development /
+        post-release suffixes.
+        """
+        return self._version.release
+
+    @property
+    def pre(self) -> tuple[str, int] | None:
+        """The pre-release segment of the version.
+
+        >>> print(Version("1.2.3").pre)
+        None
+        >>> Version("1.2.3a1").pre
+        ('a', 1)
+        >>> Version("1.2.3b1").pre
+        ('b', 1)
+        >>> Version("1.2.3rc1").pre
+        ('rc', 1)
+        """
+        return self._version.pre
+
+    @property
+    def post(self) -> int | None:
+        """The post-release number of the version.
+
+        >>> print(Version("1.2.3").post)
+        None
+        >>> Version("1.2.3.post1").post
+        1
+        """
+        return self._version.post[1] if self._version.post else None
+
+    @property
+    def dev(self) -> int | None:
+        """The development number of the version.
+
+        >>> print(Version("1.2.3").dev)
+        None
+        >>> Version("1.2.3.dev1").dev
+        1
+        """
+        return self._version.dev[1] if self._version.dev else None
+
+    @property
+    def local(self) -> str | None:
+        """The local version segment of the version.
+
+        >>> print(Version("1.2.3").local)
+        None
+        >>> Version("1.2.3+abc").local
+        'abc'
+        """
+        if self._version.local:
+            return ".".join(str(x) for x in self._version.local)
+        else:
+            return None
+
+    @property
+    def public(self) -> str:
+        """The public portion of the version.
+
+        >>> Version("1.2.3").public
+        '1.2.3'
+        >>> Version("1.2.3+abc").public
+        '1.2.3'
+        >>> Version("1!1.2.3dev1+abc").public
+        '1!1.2.3.dev1'
+        """
+        return str(self).split("+", 1)[0]
+
+    @property
+    def base_version(self) -> str:
+        """The "base version" of the version.
+
+        >>> Version("1.2.3").base_version
+        '1.2.3'
+        >>> Version("1.2.3+abc").base_version
+        '1.2.3'
+        >>> Version("1!1.2.3dev1+abc").base_version
+        '1!1.2.3'
+
+        The "base version" is the public version of the project without any pre or post
+        release markers.
+        """
+        parts = []
+
+        # Epoch
+        if self.epoch != 0:
+            parts.append(f"{self.epoch}!")
+
+        # Release segment
+        parts.append(".".join(str(x) for x in self.release))
+
+        return "".join(parts)
+
+    @property
+    def is_prerelease(self) -> bool:
+        """Whether this version is a pre-release.
+
+        >>> Version("1.2.3").is_prerelease
+        False
+        >>> Version("1.2.3a1").is_prerelease
+        True
+        >>> Version("1.2.3b1").is_prerelease
+        True
+        >>> Version("1.2.3rc1").is_prerelease
+        True
+        >>> Version("1.2.3dev1").is_prerelease
+        True
+        """
+        return self.dev is not None or self.pre is not None
+
+    @property
+    def is_postrelease(self) -> bool:
+        """Whether this version is a post-release.
+
+        >>> Version("1.2.3").is_postrelease
+        False
+        >>> Version("1.2.3.post1").is_postrelease
+        True
+        """
+        return self.post is not None
+
+    @property
+    def is_devrelease(self) -> bool:
+        """Whether this version is a development release.
+
+        >>> Version("1.2.3").is_devrelease
+        False
+        >>> Version("1.2.3.dev1").is_devrelease
+        True
+        """
+        return self.dev is not None
+
+    @property
+    def major(self) -> int:
+        """The first item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").major
+        1
+        """
+        return self.release[0] if len(self.release) >= 1 else 0
+
+    @property
+    def minor(self) -> int:
+        """The second item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").minor
+        2
+        >>> Version("1").minor
+        0
+        """
+        return self.release[1] if len(self.release) >= 2 else 0
+
+    @property
+    def micro(self) -> int:
+        """The third item of :attr:`release` or ``0`` if unavailable.
+
+        >>> Version("1.2.3").micro
+        3
+        >>> Version("1").micro
+        0
+        """
+        return self.release[2] if len(self.release) >= 3 else 0
+
+
+class _TrimmedRelease(Version):
+    @property
+    def release(self) -> tuple[int, ...]:
+        """
+        Release segment without any trailing zeros.
+
+        >>> _TrimmedRelease('1.0.0').release
+        (1,)
+        >>> _TrimmedRelease('0.0').release
+        (0,)
+        """
+        rel = super().release
+        nonzeros = (index for index, val in enumerate(rel) if val)
+        last_nonzero = max(nonzeros, default=0)
+        return rel[: last_nonzero + 1]
+
+
+def _parse_letter_version(
+    letter: str | None, number: str | bytes | SupportsInt | None
+) -> tuple[str, int] | None:
+    if letter:
+        # We consider there to be an implicit 0 in a pre-release if there is
+        # not a numeral associated with it.
+        if number is None:
+            number = 0
+
+        # We normalize any letters to their lower case form
+        letter = letter.lower()
+
+        # We consider some words to be alternate spellings of other words and
+        # in those cases we want to normalize the spellings to our preferred
+        # spelling.
+        if letter == "alpha":
+            letter = "a"
+        elif letter == "beta":
+            letter = "b"
+        elif letter in ["c", "pre", "preview"]:
+            letter = "rc"
+        elif letter in ["rev", "r"]:
+            letter = "post"
+
+        return letter, int(number)
+
+    assert not letter
+    if number:
+        # We assume if we are given a number, but we are not given a letter
+        # then this is using the implicit post release syntax (e.g. 1.0-1)
+        letter = "post"
+
+        return letter, int(number)
+
+    return None
+
+
+_local_version_separators = re.compile(r"[\._-]")
+
+
+def _parse_local_version(local: str | None) -> LocalType | None:
+    """
+    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
+    """
+    if local is not None:
+        return tuple(
+            part.lower() if not part.isdigit() else int(part)
+            for part in _local_version_separators.split(local)
+        )
+    return None
+
+
+def _cmpkey(
+    epoch: int,
+    release: tuple[int, ...],
+    pre: tuple[str, int] | None,
+    post: tuple[str, int] | None,
+    dev: tuple[str, int] | None,
+    local: LocalType | None,
+) -> CmpKey:
+    # When we compare a release version, we want to compare it with all of the
+    # trailing zeros removed. So we'll use a reverse the list, drop all the now
+    # leading zeros until we come to something non zero, then take the rest
+    # re-reverse it back into the correct order and make it a tuple and use
+    # that for our sorting key.
+    _release = tuple(
+        reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release))))
+    )
+
+    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
+    # We'll do this by abusing the pre segment, but we _only_ want to do this
+    # if there is not a pre or a post segment. If we have one of those then
+    # the normal sorting rules will handle this case correctly.
+    if pre is None and post is None and dev is not None:
+        _pre: CmpPrePostDevType = NegativeInfinity
+    # Versions without a pre-release (except as noted above) should sort after
+    # those with one.
+    elif pre is None:
+        _pre = Infinity
+    else:
+        _pre = pre
+
+    # Versions without a post segment should sort before those with one.
+    if post is None:
+        _post: CmpPrePostDevType = NegativeInfinity
+
+    else:
+        _post = post
+
+    # Versions without a development segment should sort after those with one.
+    if dev is None:
+        _dev: CmpPrePostDevType = Infinity
+
+    else:
+        _dev = dev
+
+    if local is None:
+        # Versions without a local segment should sort before those with one.
+        _local: CmpLocalType = NegativeInfinity
+    else:
+        # Versions with a local segment need that segment parsed to implement
+        # the sorting rules in PEP440.
+        # - Alpha numeric segments sort before numeric segments
+        # - Alpha numeric segments sort lexicographically
+        # - Numeric segments sort numerically
+        # - Shorter versions sort before longer versions when the prefixes
+        #   match exactly
+        _local = tuple(
+            (i, "") if isinstance(i, int) else (NegativeInfinity, i) for i in local
+        )
+
+    return epoch, _release, _pre, _post, _dev, _local
diff --git a/server/libs/ply-3.11.dist-info/DESCRIPTION.rst b/server/libs/ply-3.11.dist-info/DESCRIPTION.rst
new file mode 100644
index 0000000..5e826bd
--- /dev/null
+++ b/server/libs/ply-3.11.dist-info/DESCRIPTION.rst
@@ -0,0 +1,12 @@
+
+PLY is yet another implementation of lex and yacc for Python. Some notable
+features include the fact that its implemented entirely in Python and it
+uses LALR(1) parsing which is efficient and well suited for larger grammars.
+
+PLY provides most of the standard lex/yacc features including support for empty 
+productions, precedence rules, error recovery, and support for ambiguous grammars. 
+
+PLY is extremely easy to use and provides very extensive error checking. 
+It is compatible with both Python 2 and Python 3.
+
+
diff --git a/server/libs/ply-3.11.dist-info/INSTALLER b/server/libs/ply-3.11.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/server/libs/ply-3.11.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/server/libs/ply-3.11.dist-info/METADATA b/server/libs/ply-3.11.dist-info/METADATA
new file mode 100644
index 0000000..25ed5ab
--- /dev/null
+++ b/server/libs/ply-3.11.dist-info/METADATA
@@ -0,0 +1,25 @@
+Metadata-Version: 2.0
+Name: ply
+Version: 3.11
+Summary: Python Lex & Yacc
+Home-page: http://www.dabeaz.com/ply/
+Author: David Beazley
+Author-email: dave@dabeaz.com
+License: BSD
+Description-Content-Type: UNKNOWN
+Platform: UNKNOWN
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 2
+
+
+PLY is yet another implementation of lex and yacc for Python. Some notable
+features include the fact that its implemented entirely in Python and it
+uses LALR(1) parsing which is efficient and well suited for larger grammars.
+
+PLY provides most of the standard lex/yacc features including support for empty 
+productions, precedence rules, error recovery, and support for ambiguous grammars. 
+
+PLY is extremely easy to use and provides very extensive error checking. 
+It is compatible with both Python 2 and Python 3.
+
+
diff --git a/server/libs/ply-3.11.dist-info/RECORD b/server/libs/ply-3.11.dist-info/RECORD
new file mode 100644
index 0000000..5a76af5
--- /dev/null
+++ b/server/libs/ply-3.11.dist-info/RECORD
@@ -0,0 +1,20 @@
+ply-3.11.dist-info/DESCRIPTION.rst,sha256=nnBY1Nj_GhIsOFck7R2yGHobQVosxi2CPQkHgeSZ0Hg,519
+ply-3.11.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+ply-3.11.dist-info/METADATA,sha256=pYZ9p1TsWGQ8Kxp9yEJVyvs25PkR5h3gIDuTOCsvJGg,844
+ply-3.11.dist-info/RECORD,,
+ply-3.11.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+ply-3.11.dist-info/WHEEL,sha256=kdsN-5OJAZIiHN-iO4Rhl82KyS0bDWf4uBwMbkNafr8,110
+ply-3.11.dist-info/metadata.json,sha256=s7M7va9E25_7TRpzHfNCfN73Ieiy5iKogF0PzXtMxMI,515
+ply-3.11.dist-info/top_level.txt,sha256=gDYBHRQ7Vy0tY0AjXyJtadvU2LDaOsHqhhV70AGsisc,4
+ply/__init__.py,sha256=sx6iBIF__WKIeU0iw2WSoSqBhclHF5EhBTc0wDigTV8,103
+ply/__pycache__/__init__.cpython-311.pyc,,
+ply/__pycache__/cpp.cpython-311.pyc,,
+ply/__pycache__/ctokens.cpython-311.pyc,,
+ply/__pycache__/lex.cpython-311.pyc,,
+ply/__pycache__/yacc.cpython-311.pyc,,
+ply/__pycache__/ygen.cpython-311.pyc,,
+ply/cpp.py,sha256=KTg13R5SKeicwZm7bIPL44KcQBRcHsmeEGOwIBVvLko,33639
+ply/ctokens.py,sha256=GmyWYDY9nl6F1WJQ9rmcQFgh1FnADFlnp_TBjTcEsqU,3155
+ply/lex.py,sha256=babRISnIAfzHo7WqLYF2qGCSaH0btM8d3ztgHaK3SA0,42905
+ply/yacc.py,sha256=EF043rIHrXJYG6jcb15TI2SLwdCoNOQZXCN_1M3-I4k,137736
+ply/ygen.py,sha256=TRnkZgx5BBB43Qspu2J4gVtpeBut8xrTEZoLbNN0b6M,2246
diff --git a/server/libs/ply-3.11.dist-info/REQUESTED b/server/libs/ply-3.11.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/server/libs/ply-3.11.dist-info/WHEEL b/server/libs/ply-3.11.dist-info/WHEEL
new file mode 100644
index 0000000..7332a41
--- /dev/null
+++ b/server/libs/ply-3.11.dist-info/WHEEL
@@ -0,0 +1,6 @@
+Wheel-Version: 1.0
+Generator: bdist_wheel (0.30.0)
+Root-Is-Purelib: true
+Tag: py2-none-any
+Tag: py3-none-any
+
diff --git a/server/libs/ply-3.11.dist-info/metadata.json b/server/libs/ply-3.11.dist-info/metadata.json
new file mode 100644
index 0000000..9f472a3
--- /dev/null
+++ b/server/libs/ply-3.11.dist-info/metadata.json
@@ -0,0 +1 @@
+{"classifiers": ["Programming Language :: Python :: 3", "Programming Language :: Python :: 2"], "description_content_type": "UNKNOWN", "extensions": {"python.details": {"contacts": [{"email": "dave@dabeaz.com", "name": "David Beazley", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://www.dabeaz.com/ply/"}}}, "generator": "bdist_wheel (0.30.0)", "license": "BSD", "metadata_version": "2.0", "name": "ply", "summary": "Python Lex & Yacc", "version": "3.11"}
\ No newline at end of file
diff --git a/server/libs/ply-3.11.dist-info/top_level.txt b/server/libs/ply-3.11.dist-info/top_level.txt
new file mode 100644
index 0000000..90412f0
--- /dev/null
+++ b/server/libs/ply-3.11.dist-info/top_level.txt
@@ -0,0 +1 @@
+ply
diff --git a/server/libs/ply/__init__.py b/server/libs/ply/__init__.py
new file mode 100644
index 0000000..23707c6
--- /dev/null
+++ b/server/libs/ply/__init__.py
@@ -0,0 +1,5 @@
+# PLY package
+# Author: David Beazley (dave@dabeaz.com)
+
+__version__ = '3.11'
+__all__ = ['lex','yacc']
diff --git a/server/libs/ply/cpp.py b/server/libs/ply/cpp.py
new file mode 100644
index 0000000..2422916
--- /dev/null
+++ b/server/libs/ply/cpp.py
@@ -0,0 +1,914 @@
+# -----------------------------------------------------------------------------
+# cpp.py
+#
+# Author:  David Beazley (http://www.dabeaz.com)
+# Copyright (C) 2007
+# All rights reserved
+#
+# This module implements an ANSI-C style lexical preprocessor for PLY.
+# -----------------------------------------------------------------------------
+from __future__ import generators
+
+import sys
+
+# Some Python 3 compatibility shims
+if sys.version_info.major < 3:
+    STRING_TYPES = (str, unicode)
+else:
+    STRING_TYPES = str
+    xrange = range
+
+# -----------------------------------------------------------------------------
+# Default preprocessor lexer definitions.   These tokens are enough to get
+# a basic preprocessor working.   Other modules may import these if they want
+# -----------------------------------------------------------------------------
+
+tokens = (
+   'CPP_ID','CPP_INTEGER', 'CPP_FLOAT', 'CPP_STRING', 'CPP_CHAR', 'CPP_WS', 'CPP_COMMENT1', 'CPP_COMMENT2', 'CPP_POUND','CPP_DPOUND'
+)
+
+literals = "+-*/%|&~^<>=!?()[]{}.,;:\\\'\""
+
+# Whitespace
+def t_CPP_WS(t):
+    r'\s+'
+    t.lexer.lineno += t.value.count("\n")
+    return t
+
+t_CPP_POUND = r'\#'
+t_CPP_DPOUND = r'\#\#'
+
+# Identifier
+t_CPP_ID = r'[A-Za-z_][\w_]*'
+
+# Integer literal
+def CPP_INTEGER(t):
+    r'(((((0x)|(0X))[0-9a-fA-F]+)|(\d+))([uU][lL]|[lL][uU]|[uU]|[lL])?)'
+    return t
+
+t_CPP_INTEGER = CPP_INTEGER
+
+# Floating literal
+t_CPP_FLOAT = r'((\d+)(\.\d+)(e(\+|-)?(\d+))? | (\d+)e(\+|-)?(\d+))([lL]|[fF])?'
+
+# String literal
+def t_CPP_STRING(t):
+    r'\"([^\\\n]|(\\(.|\n)))*?\"'
+    t.lexer.lineno += t.value.count("\n")
+    return t
+
+# Character constant 'c' or L'c'
+def t_CPP_CHAR(t):
+    r'(L)?\'([^\\\n]|(\\(.|\n)))*?\''
+    t.lexer.lineno += t.value.count("\n")
+    return t
+
+# Comment
+def t_CPP_COMMENT1(t):
+    r'(/\*(.|\n)*?\*/)'
+    ncr = t.value.count("\n")
+    t.lexer.lineno += ncr
+    # replace with one space or a number of '\n'
+    t.type = 'CPP_WS'; t.value = '\n' * ncr if ncr else ' '
+    return t
+
+# Line comment
+def t_CPP_COMMENT2(t):
+    r'(//.*?(\n|$))'
+    # replace with '/n'
+    t.type = 'CPP_WS'; t.value = '\n'
+    return t
+
+def t_error(t):
+    t.type = t.value[0]
+    t.value = t.value[0]
+    t.lexer.skip(1)
+    return t
+
+import re
+import copy
+import time
+import os.path
+
+# -----------------------------------------------------------------------------
+# trigraph()
+#
+# Given an input string, this function replaces all trigraph sequences.
+# The following mapping is used:
+#
+#     ??=    #
+#     ??/    \
+#     ??'    ^
+#     ??(    [
+#     ??)    ]
+#     ??!    |
+#     ??<    {
+#     ??>    }
+#     ??-    ~
+# -----------------------------------------------------------------------------
+
+_trigraph_pat = re.compile(r'''\?\?[=/\'\(\)\!<>\-]''')
+_trigraph_rep = {
+    '=':'#',
+    '/':'\\',
+    "'":'^',
+    '(':'[',
+    ')':']',
+    '!':'|',
+    '<':'{',
+    '>':'}',
+    '-':'~'
+}
+
+def trigraph(input):
+    return _trigraph_pat.sub(lambda g: _trigraph_rep[g.group()[-1]],input)
+
+# ------------------------------------------------------------------
+# Macro object
+#
+# This object holds information about preprocessor macros
+#
+#    .name      - Macro name (string)
+#    .value     - Macro value (a list of tokens)
+#    .arglist   - List of argument names
+#    .variadic  - Boolean indicating whether or not variadic macro
+#    .vararg    - Name of the variadic parameter
+#
+# When a macro is created, the macro replacement token sequence is
+# pre-scanned and used to create patch lists that are later used
+# during macro expansion
+# ------------------------------------------------------------------
+
+class Macro(object):
+    def __init__(self,name,value,arglist=None,variadic=False):
+        self.name = name
+        self.value = value
+        self.arglist = arglist
+        self.variadic = variadic
+        if variadic:
+            self.vararg = arglist[-1]
+        self.source = None
+
+# ------------------------------------------------------------------
+# Preprocessor object
+#
+# Object representing a preprocessor.  Contains macro definitions,
+# include directories, and other information
+# ------------------------------------------------------------------
+
+class Preprocessor(object):
+    def __init__(self,lexer=None):
+        if lexer is None:
+            lexer = lex.lexer
+        self.lexer = lexer
+        self.macros = { }
+        self.path = []
+        self.temp_path = []
+
+        # Probe the lexer for selected tokens
+        self.lexprobe()
+
+        tm = time.localtime()
+        self.define("__DATE__ \"%s\"" % time.strftime("%b %d %Y",tm))
+        self.define("__TIME__ \"%s\"" % time.strftime("%H:%M:%S",tm))
+        self.parser = None
+
+    # -----------------------------------------------------------------------------
+    # tokenize()
+    #
+    # Utility function. Given a string of text, tokenize into a list of tokens
+    # -----------------------------------------------------------------------------
+
+    def tokenize(self,text):
+        tokens = []
+        self.lexer.input(text)
+        while True:
+            tok = self.lexer.token()
+            if not tok: break
+            tokens.append(tok)
+        return tokens
+
+    # ---------------------------------------------------------------------
+    # error()
+    #
+    # Report a preprocessor error/warning of some kind
+    # ----------------------------------------------------------------------
+
+    def error(self,file,line,msg):
+        print("%s:%d %s" % (file,line,msg))
+
+    # ----------------------------------------------------------------------
+    # lexprobe()
+    #
+    # This method probes the preprocessor lexer object to discover
+    # the token types of symbols that are important to the preprocessor.
+    # If this works right, the preprocessor will simply "work"
+    # with any suitable lexer regardless of how tokens have been named.
+    # ----------------------------------------------------------------------
+
+    def lexprobe(self):
+
+        # Determine the token type for identifiers
+        self.lexer.input("identifier")
+        tok = self.lexer.token()
+        if not tok or tok.value != "identifier":
+            print("Couldn't determine identifier type")
+        else:
+            self.t_ID = tok.type
+
+        # Determine the token type for integers
+        self.lexer.input("12345")
+        tok = self.lexer.token()
+        if not tok or int(tok.value) != 12345:
+            print("Couldn't determine integer type")
+        else:
+            self.t_INTEGER = tok.type
+            self.t_INTEGER_TYPE = type(tok.value)
+
+        # Determine the token type for strings enclosed in double quotes
+        self.lexer.input("\"filename\"")
+        tok = self.lexer.token()
+        if not tok or tok.value != "\"filename\"":
+            print("Couldn't determine string type")
+        else:
+            self.t_STRING = tok.type
+
+        # Determine the token type for whitespace--if any
+        self.lexer.input("  ")
+        tok = self.lexer.token()
+        if not tok or tok.value != "  ":
+            self.t_SPACE = None
+        else:
+            self.t_SPACE = tok.type
+
+        # Determine the token type for newlines
+        self.lexer.input("\n")
+        tok = self.lexer.token()
+        if not tok or tok.value != "\n":
+            self.t_NEWLINE = None
+            print("Couldn't determine token for newlines")
+        else:
+            self.t_NEWLINE = tok.type
+
+        self.t_WS = (self.t_SPACE, self.t_NEWLINE)
+
+        # Check for other characters used by the preprocessor
+        chars = [ '<','>','#','##','\\','(',')',',','.']
+        for c in chars:
+            self.lexer.input(c)
+            tok = self.lexer.token()
+            if not tok or tok.value != c:
+                print("Unable to lex '%s' required for preprocessor" % c)
+
+    # ----------------------------------------------------------------------
+    # add_path()
+    #
+    # Adds a search path to the preprocessor.
+    # ----------------------------------------------------------------------
+
+    def add_path(self,path):
+        self.path.append(path)
+
+    # ----------------------------------------------------------------------
+    # group_lines()
+    #
+    # Given an input string, this function splits it into lines.  Trailing whitespace
+    # is removed.   Any line ending with \ is grouped with the next line.  This
+    # function forms the lowest level of the preprocessor---grouping into text into
+    # a line-by-line format.
+    # ----------------------------------------------------------------------
+
+    def group_lines(self,input):
+        lex = self.lexer.clone()
+        lines = [x.rstrip() for x in input.splitlines()]
+        for i in xrange(len(lines)):
+            j = i+1
+            while lines[i].endswith('\\') and (j < len(lines)):
+                lines[i] = lines[i][:-1]+lines[j]
+                lines[j] = ""
+                j += 1
+
+        input = "\n".join(lines)
+        lex.input(input)
+        lex.lineno = 1
+
+        current_line = []
+        while True:
+            tok = lex.token()
+            if not tok:
+                break
+            current_line.append(tok)
+            if tok.type in self.t_WS and '\n' in tok.value:
+                yield current_line
+                current_line = []
+
+        if current_line:
+            yield current_line
+
+    # ----------------------------------------------------------------------
+    # tokenstrip()
+    #
+    # Remove leading/trailing whitespace tokens from a token list
+    # ----------------------------------------------------------------------
+
+    def tokenstrip(self,tokens):
+        i = 0
+        while i < len(tokens) and tokens[i].type in self.t_WS:
+            i += 1
+        del tokens[:i]
+        i = len(tokens)-1
+        while i >= 0 and tokens[i].type in self.t_WS:
+            i -= 1
+        del tokens[i+1:]
+        return tokens
+
+
+    # ----------------------------------------------------------------------
+    # collect_args()
+    #
+    # Collects comma separated arguments from a list of tokens.   The arguments
+    # must be enclosed in parenthesis.  Returns a tuple (tokencount,args,positions)
+    # where tokencount is the number of tokens consumed, args is a list of arguments,
+    # and positions is a list of integers containing the starting index of each
+    # argument.  Each argument is represented by a list of tokens.
+    #
+    # When collecting arguments, leading and trailing whitespace is removed
+    # from each argument.
+    #
+    # This function properly handles nested parenthesis and commas---these do not
+    # define new arguments.
+    # ----------------------------------------------------------------------
+
+    def collect_args(self,tokenlist):
+        args = []
+        positions = []
+        current_arg = []
+        nesting = 1
+        tokenlen = len(tokenlist)
+
+        # Search for the opening '('.
+        i = 0
+        while (i < tokenlen) and (tokenlist[i].type in self.t_WS):
+            i += 1
+
+        if (i < tokenlen) and (tokenlist[i].value == '('):
+            positions.append(i+1)
+        else:
+            self.error(self.source,tokenlist[0].lineno,"Missing '(' in macro arguments")
+            return 0, [], []
+
+        i += 1
+
+        while i < tokenlen:
+            t = tokenlist[i]
+            if t.value == '(':
+                current_arg.append(t)
+                nesting += 1
+            elif t.value == ')':
+                nesting -= 1
+                if nesting == 0:
+                    if current_arg:
+                        args.append(self.tokenstrip(current_arg))
+                        positions.append(i)
+                    return i+1,args,positions
+                current_arg.append(t)
+            elif t.value == ',' and nesting == 1:
+                args.append(self.tokenstrip(current_arg))
+                positions.append(i+1)
+                current_arg = []
+            else:
+                current_arg.append(t)
+            i += 1
+
+        # Missing end argument
+        self.error(self.source,tokenlist[-1].lineno,"Missing ')' in macro arguments")
+        return 0, [],[]
+
+    # ----------------------------------------------------------------------
+    # macro_prescan()
+    #
+    # Examine the macro value (token sequence) and identify patch points
+    # This is used to speed up macro expansion later on---we'll know
+    # right away where to apply patches to the value to form the expansion
+    # ----------------------------------------------------------------------
+
+    def macro_prescan(self,macro):
+        macro.patch     = []             # Standard macro arguments
+        macro.str_patch = []             # String conversion expansion
+        macro.var_comma_patch = []       # Variadic macro comma patch
+        i = 0
+        while i < len(macro.value):
+            if macro.value[i].type == self.t_ID and macro.value[i].value in macro.arglist:
+                argnum = macro.arglist.index(macro.value[i].value)
+                # Conversion of argument to a string
+                if i > 0 and macro.value[i-1].value == '#':
+                    macro.value[i] = copy.copy(macro.value[i])
+                    macro.value[i].type = self.t_STRING
+                    del macro.value[i-1]
+                    macro.str_patch.append((argnum,i-1))
+                    continue
+                # Concatenation
+                elif (i > 0 and macro.value[i-1].value == '##'):
+                    macro.patch.append(('c',argnum,i-1))
+                    del macro.value[i-1]
+                    i -= 1
+                    continue
+                elif ((i+1) < len(macro.value) and macro.value[i+1].value == '##'):
+                    macro.patch.append(('c',argnum,i))
+                    del macro.value[i + 1]
+                    continue
+                # Standard expansion
+                else:
+                    macro.patch.append(('e',argnum,i))
+            elif macro.value[i].value == '##':
+                if macro.variadic and (i > 0) and (macro.value[i-1].value == ',') and \
+                        ((i+1) < len(macro.value)) and (macro.value[i+1].type == self.t_ID) and \
+                        (macro.value[i+1].value == macro.vararg):
+                    macro.var_comma_patch.append(i-1)
+            i += 1
+        macro.patch.sort(key=lambda x: x[2],reverse=True)
+
+    # ----------------------------------------------------------------------
+    # macro_expand_args()
+    #
+    # Given a Macro and list of arguments (each a token list), this method
+    # returns an expanded version of a macro.  The return value is a token sequence
+    # representing the replacement macro tokens
+    # ----------------------------------------------------------------------
+
+    def macro_expand_args(self,macro,args):
+        # Make a copy of the macro token sequence
+        rep = [copy.copy(_x) for _x in macro.value]
+
+        # Make string expansion patches.  These do not alter the length of the replacement sequence
+
+        str_expansion = {}
+        for argnum, i in macro.str_patch:
+            if argnum not in str_expansion:
+                str_expansion[argnum] = ('"%s"' % "".join([x.value for x in args[argnum]])).replace("\\","\\\\")
+            rep[i] = copy.copy(rep[i])
+            rep[i].value = str_expansion[argnum]
+
+        # Make the variadic macro comma patch.  If the variadic macro argument is empty, we get rid
+        comma_patch = False
+        if macro.variadic and not args[-1]:
+            for i in macro.var_comma_patch:
+                rep[i] = None
+                comma_patch = True
+
+        # Make all other patches.   The order of these matters.  It is assumed that the patch list
+        # has been sorted in reverse order of patch location since replacements will cause the
+        # size of the replacement sequence to expand from the patch point.
+
+        expanded = { }
+        for ptype, argnum, i in macro.patch:
+            # Concatenation.   Argument is left unexpanded
+            if ptype == 'c':
+                rep[i:i+1] = args[argnum]
+            # Normal expansion.  Argument is macro expanded first
+            elif ptype == 'e':
+                if argnum not in expanded:
+                    expanded[argnum] = self.expand_macros(args[argnum])
+                rep[i:i+1] = expanded[argnum]
+
+        # Get rid of removed comma if necessary
+        if comma_patch:
+            rep = [_i for _i in rep if _i]
+
+        return rep
+
+
+    # ----------------------------------------------------------------------
+    # expand_macros()
+    #
+    # Given a list of tokens, this function performs macro expansion.
+    # The expanded argument is a dictionary that contains macros already
+    # expanded.  This is used to prevent infinite recursion.
+    # ----------------------------------------------------------------------
+
+    def expand_macros(self,tokens,expanded=None):
+        if expanded is None:
+            expanded = {}
+        i = 0
+        while i < len(tokens):
+            t = tokens[i]
+            if t.type == self.t_ID:
+                if t.value in self.macros and t.value not in expanded:
+                    # Yes, we found a macro match
+                    expanded[t.value] = True
+
+                    m = self.macros[t.value]
+                    if not m.arglist:
+                        # A simple macro
+                        ex = self.expand_macros([copy.copy(_x) for _x in m.value],expanded)
+                        for e in ex:
+                            e.lineno = t.lineno
+                        tokens[i:i+1] = ex
+                        i += len(ex)
+                    else:
+                        # A macro with arguments
+                        j = i + 1
+                        while j < len(tokens) and tokens[j].type in self.t_WS:
+                            j += 1
+                        if j < len(tokens) and tokens[j].value == '(':
+                            tokcount,args,positions = self.collect_args(tokens[j:])
+                            if not m.variadic and len(args) !=  len(m.arglist):
+                                self.error(self.source,t.lineno,"Macro %s requires %d arguments" % (t.value,len(m.arglist)))
+                                i = j + tokcount
+                            elif m.variadic and len(args) < len(m.arglist)-1:
+                                if len(m.arglist) > 2:
+                                    self.error(self.source,t.lineno,"Macro %s must have at least %d arguments" % (t.value, len(m.arglist)-1))
+                                else:
+                                    self.error(self.source,t.lineno,"Macro %s must have at least %d argument" % (t.value, len(m.arglist)-1))
+                                i = j + tokcount
+                            else:
+                                if m.variadic:
+                                    if len(args) == len(m.arglist)-1:
+                                        args.append([])
+                                    else:
+                                        args[len(m.arglist)-1] = tokens[j+positions[len(m.arglist)-1]:j+tokcount-1]
+                                        del args[len(m.arglist):]
+
+                                # Get macro replacement text
+                                rep = self.macro_expand_args(m,args)
+                                rep = self.expand_macros(rep,expanded)
+                                for r in rep:
+                                    r.lineno = t.lineno
+                                tokens[i:j+tokcount] = rep
+                                i += len(rep)
+                        else:
+                            # This is not a macro. It is just a word which
+                            # equals to name of the macro. Hence, go to the
+                            # next token.
+                            i += 1
+
+                    del expanded[t.value]
+                    continue
+                elif t.value == '__LINE__':
+                    t.type = self.t_INTEGER
+                    t.value = self.t_INTEGER_TYPE(t.lineno)
+
+            i += 1
+        return tokens
+
+    # ----------------------------------------------------------------------
+    # evalexpr()
+    #
+    # Evaluate an expression token sequence for the purposes of evaluating
+    # integral expressions.
+    # ----------------------------------------------------------------------
+
+    def evalexpr(self,tokens):
+        # tokens = tokenize(line)
+        # Search for defined macros
+        i = 0
+        while i < len(tokens):
+            if tokens[i].type == self.t_ID and tokens[i].value == 'defined':
+                j = i + 1
+                needparen = False
+                result = "0L"
+                while j < len(tokens):
+                    if tokens[j].type in self.t_WS:
+                        j += 1
+                        continue
+                    elif tokens[j].type == self.t_ID:
+                        if tokens[j].value in self.macros:
+                            result = "1L"
+                        else:
+                            result = "0L"
+                        if not needparen: break
+                    elif tokens[j].value == '(':
+                        needparen = True
+                    elif tokens[j].value == ')':
+                        break
+                    else:
+                        self.error(self.source,tokens[i].lineno,"Malformed defined()")
+                    j += 1
+                tokens[i].type = self.t_INTEGER
+                tokens[i].value = self.t_INTEGER_TYPE(result)
+                del tokens[i+1:j+1]
+            i += 1
+        tokens = self.expand_macros(tokens)
+        for i,t in enumerate(tokens):
+            if t.type == self.t_ID:
+                tokens[i] = copy.copy(t)
+                tokens[i].type = self.t_INTEGER
+                tokens[i].value = self.t_INTEGER_TYPE("0L")
+            elif t.type == self.t_INTEGER:
+                tokens[i] = copy.copy(t)
+                # Strip off any trailing suffixes
+                tokens[i].value = str(tokens[i].value)
+                while tokens[i].value[-1] not in "0123456789abcdefABCDEF":
+                    tokens[i].value = tokens[i].value[:-1]
+
+        expr = "".join([str(x.value) for x in tokens])
+        expr = expr.replace("&&"," and ")
+        expr = expr.replace("||"," or ")
+        expr = expr.replace("!"," not ")
+        try:
+            result = eval(expr)
+        except Exception:
+            self.error(self.source,tokens[0].lineno,"Couldn't evaluate expression")
+            result = 0
+        return result
+
+    # ----------------------------------------------------------------------
+    # parsegen()
+    #
+    # Parse an input string/
+    # ----------------------------------------------------------------------
+    def parsegen(self,input,source=None):
+
+        # Replace trigraph sequences
+        t = trigraph(input)
+        lines = self.group_lines(t)
+
+        if not source:
+            source = ""
+
+        self.define("__FILE__ \"%s\"" % source)
+
+        self.source = source
+        chunk = []
+        enable = True
+        iftrigger = False
+        ifstack = []
+
+        for x in lines:
+            for i,tok in enumerate(x):
+                if tok.type not in self.t_WS: break
+            if tok.value == '#':
+                # Preprocessor directive
+
+                # insert necessary whitespace instead of eaten tokens
+                for tok in x:
+                    if tok.type in self.t_WS and '\n' in tok.value:
+                        chunk.append(tok)
+
+                dirtokens = self.tokenstrip(x[i+1:])
+                if dirtokens:
+                    name = dirtokens[0].value
+                    args = self.tokenstrip(dirtokens[1:])
+                else:
+                    name = ""
+                    args = []
+
+                if name == 'define':
+                    if enable:
+                        for tok in self.expand_macros(chunk):
+                            yield tok
+                        chunk = []
+                        self.define(args)
+                elif name == 'include':
+                    if enable:
+                        for tok in self.expand_macros(chunk):
+                            yield tok
+                        chunk = []
+                        oldfile = self.macros['__FILE__']
+                        for tok in self.include(args):
+                            yield tok
+                        self.macros['__FILE__'] = oldfile
+                        self.source = source
+                elif name == 'undef':
+                    if enable:
+                        for tok in self.expand_macros(chunk):
+                            yield tok
+                        chunk = []
+                        self.undef(args)
+                elif name == 'ifdef':
+                    ifstack.append((enable,iftrigger))
+                    if enable:
+                        if not args[0].value in self.macros:
+                            enable = False
+                            iftrigger = False
+                        else:
+                            iftrigger = True
+                elif name == 'ifndef':
+                    ifstack.append((enable,iftrigger))
+                    if enable:
+                        if args[0].value in self.macros:
+                            enable = False
+                            iftrigger = False
+                        else:
+                            iftrigger = True
+                elif name == 'if':
+                    ifstack.append((enable,iftrigger))
+                    if enable:
+                        result = self.evalexpr(args)
+                        if not result:
+                            enable = False
+                            iftrigger = False
+                        else:
+                            iftrigger = True
+                elif name == 'elif':
+                    if ifstack:
+                        if ifstack[-1][0]:     # We only pay attention if outer "if" allows this
+                            if enable:         # If already true, we flip enable False
+                                enable = False
+                            elif not iftrigger:   # If False, but not triggered yet, we'll check expression
+                                result = self.evalexpr(args)
+                                if result:
+                                    enable  = True
+                                    iftrigger = True
+                    else:
+                        self.error(self.source,dirtokens[0].lineno,"Misplaced #elif")
+
+                elif name == 'else':
+                    if ifstack:
+                        if ifstack[-1][0]:
+                            if enable:
+                                enable = False
+                            elif not iftrigger:
+                                enable = True
+                                iftrigger = True
+                    else:
+                        self.error(self.source,dirtokens[0].lineno,"Misplaced #else")
+
+                elif name == 'endif':
+                    if ifstack:
+                        enable,iftrigger = ifstack.pop()
+                    else:
+                        self.error(self.source,dirtokens[0].lineno,"Misplaced #endif")
+                else:
+                    # Unknown preprocessor directive
+                    pass
+
+            else:
+                # Normal text
+                if enable:
+                    chunk.extend(x)
+
+        for tok in self.expand_macros(chunk):
+            yield tok
+        chunk = []
+
+    # ----------------------------------------------------------------------
+    # include()
+    #
+    # Implementation of file-inclusion
+    # ----------------------------------------------------------------------
+
+    def include(self,tokens):
+        # Try to extract the filename and then process an include file
+        if not tokens:
+            return
+        if tokens:
+            if tokens[0].value != '<' and tokens[0].type != self.t_STRING:
+                tokens = self.expand_macros(tokens)
+
+            if tokens[0].value == '<':
+                # Include <...>
+                i = 1
+                while i < len(tokens):
+                    if tokens[i].value == '>':
+                        break
+                    i += 1
+                else:
+                    print("Malformed #include <...>")
+                    return
+                filename = "".join([x.value for x in tokens[1:i]])
+                path = self.path + [""] + self.temp_path
+            elif tokens[0].type == self.t_STRING:
+                filename = tokens[0].value[1:-1]
+                path = self.temp_path + [""] + self.path
+            else:
+                print("Malformed #include statement")
+                return
+        for p in path:
+            iname = os.path.join(p,filename)
+            try:
+                data = open(iname,"r").read()
+                dname = os.path.dirname(iname)
+                if dname:
+                    self.temp_path.insert(0,dname)
+                for tok in self.parsegen(data,filename):
+                    yield tok
+                if dname:
+                    del self.temp_path[0]
+                break
+            except IOError:
+                pass
+        else:
+            print("Couldn't find '%s'" % filename)
+
+    # ----------------------------------------------------------------------
+    # define()
+    #
+    # Define a new macro
+    # ----------------------------------------------------------------------
+
+    def define(self,tokens):
+        if isinstance(tokens,STRING_TYPES):
+            tokens = self.tokenize(tokens)
+
+        linetok = tokens
+        try:
+            name = linetok[0]
+            if len(linetok) > 1:
+                mtype = linetok[1]
+            else:
+                mtype = None
+            if not mtype:
+                m = Macro(name.value,[])
+                self.macros[name.value] = m
+            elif mtype.type in self.t_WS:
+                # A normal macro
+                m = Macro(name.value,self.tokenstrip(linetok[2:]))
+                self.macros[name.value] = m
+            elif mtype.value == '(':
+                # A macro with arguments
+                tokcount, args, positions = self.collect_args(linetok[1:])
+                variadic = False
+                for a in args:
+                    if variadic:
+                        print("No more arguments may follow a variadic argument")
+                        break
+                    astr = "".join([str(_i.value) for _i in a])
+                    if astr == "...":
+                        variadic = True
+                        a[0].type = self.t_ID
+                        a[0].value = '__VA_ARGS__'
+                        variadic = True
+                        del a[1:]
+                        continue
+                    elif astr[-3:] == "..." and a[0].type == self.t_ID:
+                        variadic = True
+                        del a[1:]
+                        # If, for some reason, "." is part of the identifier, strip off the name for the purposes
+                        # of macro expansion
+                        if a[0].value[-3:] == '...':
+                            a[0].value = a[0].value[:-3]
+                        continue
+                    if len(a) > 1 or a[0].type != self.t_ID:
+                        print("Invalid macro argument")
+                        break
+                else:
+                    mvalue = self.tokenstrip(linetok[1+tokcount:])
+                    i = 0
+                    while i < len(mvalue):
+                        if i+1 < len(mvalue):
+                            if mvalue[i].type in self.t_WS and mvalue[i+1].value == '##':
+                                del mvalue[i]
+                                continue
+                            elif mvalue[i].value == '##' and mvalue[i+1].type in self.t_WS:
+                                del mvalue[i+1]
+                        i += 1
+                    m = Macro(name.value,mvalue,[x[0].value for x in args],variadic)
+                    self.macro_prescan(m)
+                    self.macros[name.value] = m
+            else:
+                print("Bad macro definition")
+        except LookupError:
+            print("Bad macro definition")
+
+    # ----------------------------------------------------------------------
+    # undef()
+    #
+    # Undefine a macro
+    # ----------------------------------------------------------------------
+
+    def undef(self,tokens):
+        id = tokens[0].value
+        try:
+            del self.macros[id]
+        except LookupError:
+            pass
+
+    # ----------------------------------------------------------------------
+    # parse()
+    #
+    # Parse input text.
+    # ----------------------------------------------------------------------
+    def parse(self,input,source=None,ignore={}):
+        self.ignore = ignore
+        self.parser = self.parsegen(input,source)
+
+    # ----------------------------------------------------------------------
+    # token()
+    #
+    # Method to return individual tokens
+    # ----------------------------------------------------------------------
+    def token(self):
+        try:
+            while True:
+                tok = next(self.parser)
+                if tok.type not in self.ignore: return tok
+        except StopIteration:
+            self.parser = None
+            return None
+
+if __name__ == '__main__':
+    import ply.lex as lex
+    lexer = lex.lex()
+
+    # Run a preprocessor
+    import sys
+    f = open(sys.argv[1])
+    input = f.read()
+
+    p = Preprocessor(lexer)
+    p.parse(input,sys.argv[1])
+    while True:
+        tok = p.token()
+        if not tok: break
+        print(p.source, tok)
diff --git a/server/libs/ply/ctokens.py b/server/libs/ply/ctokens.py
new file mode 100644
index 0000000..b265e59
--- /dev/null
+++ b/server/libs/ply/ctokens.py
@@ -0,0 +1,127 @@
+# ----------------------------------------------------------------------
+# ctokens.py
+#
+# Token specifications for symbols in ANSI C and C++.  This file is
+# meant to be used as a library in other tokenizers.
+# ----------------------------------------------------------------------
+
+# Reserved words
+
+tokens = [
+    # Literals (identifier, integer constant, float constant, string constant, char const)
+    'ID', 'TYPEID', 'INTEGER', 'FLOAT', 'STRING', 'CHARACTER',
+
+    # Operators (+,-,*,/,%,|,&,~,^,<<,>>, ||, &&, !, <, <=, >, >=, ==, !=)
+    'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'MODULO',
+    'OR', 'AND', 'NOT', 'XOR', 'LSHIFT', 'RSHIFT',
+    'LOR', 'LAND', 'LNOT',
+    'LT', 'LE', 'GT', 'GE', 'EQ', 'NE',
+
+    # Assignment (=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=)
+    'EQUALS', 'TIMESEQUAL', 'DIVEQUAL', 'MODEQUAL', 'PLUSEQUAL', 'MINUSEQUAL',
+    'LSHIFTEQUAL','RSHIFTEQUAL', 'ANDEQUAL', 'XOREQUAL', 'OREQUAL',
+
+    # Increment/decrement (++,--)
+    'INCREMENT', 'DECREMENT',
+
+    # Structure dereference (->)
+    'ARROW',
+
+    # Ternary operator (?)
+    'TERNARY',
+
+    # Delimeters ( ) [ ] { } , . ; :
+    'LPAREN', 'RPAREN',
+    'LBRACKET', 'RBRACKET',
+    'LBRACE', 'RBRACE',
+    'COMMA', 'PERIOD', 'SEMI', 'COLON',
+
+    # Ellipsis (...)
+    'ELLIPSIS',
+]
+
+# Operators
+t_PLUS             = r'\+'
+t_MINUS            = r'-'
+t_TIMES            = r'\*'
+t_DIVIDE           = r'/'
+t_MODULO           = r'%'
+t_OR               = r'\|'
+t_AND              = r'&'
+t_NOT              = r'~'
+t_XOR              = r'\^'
+t_LSHIFT           = r'<<'
+t_RSHIFT           = r'>>'
+t_LOR              = r'\|\|'
+t_LAND             = r'&&'
+t_LNOT             = r'!'
+t_LT               = r'<'
+t_GT               = r'>'
+t_LE               = r'<='
+t_GE               = r'>='
+t_EQ               = r'=='
+t_NE               = r'!='
+
+# Assignment operators
+
+t_EQUALS           = r'='
+t_TIMESEQUAL       = r'\*='
+t_DIVEQUAL         = r'/='
+t_MODEQUAL         = r'%='
+t_PLUSEQUAL        = r'\+='
+t_MINUSEQUAL       = r'-='
+t_LSHIFTEQUAL      = r'<<='
+t_RSHIFTEQUAL      = r'>>='
+t_ANDEQUAL         = r'&='
+t_OREQUAL          = r'\|='
+t_XOREQUAL         = r'\^='
+
+# Increment/decrement
+t_INCREMENT        = r'\+\+'
+t_DECREMENT        = r'--'
+
+# ->
+t_ARROW            = r'->'
+
+# ?
+t_TERNARY          = r'\?'
+
+# Delimeters
+t_LPAREN           = r'\('
+t_RPAREN           = r'\)'
+t_LBRACKET         = r'\['
+t_RBRACKET         = r'\]'
+t_LBRACE           = r'\{'
+t_RBRACE           = r'\}'
+t_COMMA            = r','
+t_PERIOD           = r'\.'
+t_SEMI             = r';'
+t_COLON            = r':'
+t_ELLIPSIS         = r'\.\.\.'
+
+# Identifiers
+t_ID = r'[A-Za-z_][A-Za-z0-9_]*'
+
+# Integer literal
+t_INTEGER = r'\d+([uU]|[lL]|[uU][lL]|[lL][uU])?'
+
+# Floating literal
+t_FLOAT = r'((\d+)(\.\d+)(e(\+|-)?(\d+))? | (\d+)e(\+|-)?(\d+))([lL]|[fF])?'
+
+# String literal
+t_STRING = r'\"([^\\\n]|(\\.))*?\"'
+
+# Character constant 'c' or L'c'
+t_CHARACTER = r'(L)?\'([^\\\n]|(\\.))*?\''
+
+# Comment (C-Style)
+def t_COMMENT(t):
+    r'/\*(.|\n)*?\*/'
+    t.lexer.lineno += t.value.count('\n')
+    return t
+
+# Comment (C++-Style)
+def t_CPPCOMMENT(t):
+    r'//.*\n'
+    t.lexer.lineno += 1
+    return t
diff --git a/server/libs/ply/lex.py b/server/libs/ply/lex.py
new file mode 100644
index 0000000..f95bcdb
--- /dev/null
+++ b/server/libs/ply/lex.py
@@ -0,0 +1,1098 @@
+# -----------------------------------------------------------------------------
+# ply: lex.py
+#
+# Copyright (C) 2001-2018
+# David M. Beazley (Dabeaz LLC)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# * Neither the name of the David Beazley or Dabeaz LLC may be used to
+#   endorse or promote products derived from this software without
+#  specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------------------------
+
+__version__    = '3.11'
+__tabversion__ = '3.10'
+
+import re
+import sys
+import types
+import copy
+import os
+import inspect
+
+# This tuple contains known string types
+try:
+    # Python 2.6
+    StringTypes = (types.StringType, types.UnicodeType)
+except AttributeError:
+    # Python 3.0
+    StringTypes = (str, bytes)
+
+# This regular expression is used to match valid token names
+_is_identifier = re.compile(r'^[a-zA-Z0-9_]+$')
+
+# Exception thrown when invalid token encountered and no default error
+# handler is defined.
+class LexError(Exception):
+    def __init__(self, message, s):
+        self.args = (message,)
+        self.text = s
+
+
+# Token class.  This class is used to represent the tokens produced.
+class LexToken(object):
+    def __str__(self):
+        return 'LexToken(%s,%r,%d,%d)' % (self.type, self.value, self.lineno, self.lexpos)
+
+    def __repr__(self):
+        return str(self)
+
+
+# This object is a stand-in for a logging object created by the
+# logging module.
+
+class PlyLogger(object):
+    def __init__(self, f):
+        self.f = f
+
+    def critical(self, msg, *args, **kwargs):
+        self.f.write((msg % args) + '\n')
+
+    def warning(self, msg, *args, **kwargs):
+        self.f.write('WARNING: ' + (msg % args) + '\n')
+
+    def error(self, msg, *args, **kwargs):
+        self.f.write('ERROR: ' + (msg % args) + '\n')
+
+    info = critical
+    debug = critical
+
+
+# Null logger is used when no output is generated. Does nothing.
+class NullLogger(object):
+    def __getattribute__(self, name):
+        return self
+
+    def __call__(self, *args, **kwargs):
+        return self
+
+
+# -----------------------------------------------------------------------------
+#                        === Lexing Engine ===
+#
+# The following Lexer class implements the lexer runtime.   There are only
+# a few public methods and attributes:
+#
+#    input()          -  Store a new string in the lexer
+#    token()          -  Get the next token
+#    clone()          -  Clone the lexer
+#
+#    lineno           -  Current line number
+#    lexpos           -  Current position in the input string
+# -----------------------------------------------------------------------------
+
+class Lexer:
+    def __init__(self):
+        self.lexre = None             # Master regular expression. This is a list of
+                                      # tuples (re, findex) where re is a compiled
+                                      # regular expression and findex is a list
+                                      # mapping regex group numbers to rules
+        self.lexretext = None         # Current regular expression strings
+        self.lexstatere = {}          # Dictionary mapping lexer states to master regexs
+        self.lexstateretext = {}      # Dictionary mapping lexer states to regex strings
+        self.lexstaterenames = {}     # Dictionary mapping lexer states to symbol names
+        self.lexstate = 'INITIAL'     # Current lexer state
+        self.lexstatestack = []       # Stack of lexer states
+        self.lexstateinfo = None      # State information
+        self.lexstateignore = {}      # Dictionary of ignored characters for each state
+        self.lexstateerrorf = {}      # Dictionary of error functions for each state
+        self.lexstateeoff = {}        # Dictionary of eof functions for each state
+        self.lexreflags = 0           # Optional re compile flags
+        self.lexdata = None           # Actual input data (as a string)
+        self.lexpos = 0               # Current position in input text
+        self.lexlen = 0               # Length of the input text
+        self.lexerrorf = None         # Error rule (if any)
+        self.lexeoff = None           # EOF rule (if any)
+        self.lextokens = None         # List of valid tokens
+        self.lexignore = ''           # Ignored characters
+        self.lexliterals = ''         # Literal characters that can be passed through
+        self.lexmodule = None         # Module
+        self.lineno = 1               # Current line number
+        self.lexoptimize = False      # Optimized mode
+
+    def clone(self, object=None):
+        c = copy.copy(self)
+
+        # If the object parameter has been supplied, it means we are attaching the
+        # lexer to a new object.  In this case, we have to rebind all methods in
+        # the lexstatere and lexstateerrorf tables.
+
+        if object:
+            newtab = {}
+            for key, ritem in self.lexstatere.items():
+                newre = []
+                for cre, findex in ritem:
+                    newfindex = []
+                    for f in findex:
+                        if not f or not f[0]:
+                            newfindex.append(f)
+                            continue
+                        newfindex.append((getattr(object, f[0].__name__), f[1]))
+                newre.append((cre, newfindex))
+                newtab[key] = newre
+            c.lexstatere = newtab
+            c.lexstateerrorf = {}
+            for key, ef in self.lexstateerrorf.items():
+                c.lexstateerrorf[key] = getattr(object, ef.__name__)
+            c.lexmodule = object
+        return c
+
+    # ------------------------------------------------------------
+    # writetab() - Write lexer information to a table file
+    # ------------------------------------------------------------
+    def writetab(self, lextab, outputdir=''):
+        if isinstance(lextab, types.ModuleType):
+            raise IOError("Won't overwrite existing lextab module")
+        basetabmodule = lextab.split('.')[-1]
+        filename = os.path.join(outputdir, basetabmodule) + '.py'
+        with open(filename, 'w') as tf:
+            tf.write('# %s.py. This file automatically created by PLY (version %s). Don\'t edit!\n' % (basetabmodule, __version__))
+            tf.write('_tabversion   = %s\n' % repr(__tabversion__))
+            tf.write('_lextokens    = set(%s)\n' % repr(tuple(sorted(self.lextokens))))
+            tf.write('_lexreflags   = %s\n' % repr(int(self.lexreflags)))
+            tf.write('_lexliterals  = %s\n' % repr(self.lexliterals))
+            tf.write('_lexstateinfo = %s\n' % repr(self.lexstateinfo))
+
+            # Rewrite the lexstatere table, replacing function objects with function names
+            tabre = {}
+            for statename, lre in self.lexstatere.items():
+                titem = []
+                for (pat, func), retext, renames in zip(lre, self.lexstateretext[statename], self.lexstaterenames[statename]):
+                    titem.append((retext, _funcs_to_names(func, renames)))
+                tabre[statename] = titem
+
+            tf.write('_lexstatere   = %s\n' % repr(tabre))
+            tf.write('_lexstateignore = %s\n' % repr(self.lexstateignore))
+
+            taberr = {}
+            for statename, ef in self.lexstateerrorf.items():
+                taberr[statename] = ef.__name__ if ef else None
+            tf.write('_lexstateerrorf = %s\n' % repr(taberr))
+
+            tabeof = {}
+            for statename, ef in self.lexstateeoff.items():
+                tabeof[statename] = ef.__name__ if ef else None
+            tf.write('_lexstateeoff = %s\n' % repr(tabeof))
+
+    # ------------------------------------------------------------
+    # readtab() - Read lexer information from a tab file
+    # ------------------------------------------------------------
+    def readtab(self, tabfile, fdict):
+        if isinstance(tabfile, types.ModuleType):
+            lextab = tabfile
+        else:
+            exec('import %s' % tabfile)
+            lextab = sys.modules[tabfile]
+
+        if getattr(lextab, '_tabversion', '0.0') != __tabversion__:
+            raise ImportError('Inconsistent PLY version')
+
+        self.lextokens      = lextab._lextokens
+        self.lexreflags     = lextab._lexreflags
+        self.lexliterals    = lextab._lexliterals
+        self.lextokens_all  = self.lextokens | set(self.lexliterals)
+        self.lexstateinfo   = lextab._lexstateinfo
+        self.lexstateignore = lextab._lexstateignore
+        self.lexstatere     = {}
+        self.lexstateretext = {}
+        for statename, lre in lextab._lexstatere.items():
+            titem = []
+            txtitem = []
+            for pat, func_name in lre:
+                titem.append((re.compile(pat, lextab._lexreflags), _names_to_funcs(func_name, fdict)))
+
+            self.lexstatere[statename] = titem
+            self.lexstateretext[statename] = txtitem
+
+        self.lexstateerrorf = {}
+        for statename, ef in lextab._lexstateerrorf.items():
+            self.lexstateerrorf[statename] = fdict[ef]
+
+        self.lexstateeoff = {}
+        for statename, ef in lextab._lexstateeoff.items():
+            self.lexstateeoff[statename] = fdict[ef]
+
+        self.begin('INITIAL')
+
+    # ------------------------------------------------------------
+    # input() - Push a new string into the lexer
+    # ------------------------------------------------------------
+    def input(self, s):
+        # Pull off the first character to see if s looks like a string
+        c = s[:1]
+        if not isinstance(c, StringTypes):
+            raise ValueError('Expected a string')
+        self.lexdata = s
+        self.lexpos = 0
+        self.lexlen = len(s)
+
+    # ------------------------------------------------------------
+    # begin() - Changes the lexing state
+    # ------------------------------------------------------------
+    def begin(self, state):
+        if state not in self.lexstatere:
+            raise ValueError('Undefined state')
+        self.lexre = self.lexstatere[state]
+        self.lexretext = self.lexstateretext[state]
+        self.lexignore = self.lexstateignore.get(state, '')
+        self.lexerrorf = self.lexstateerrorf.get(state, None)
+        self.lexeoff = self.lexstateeoff.get(state, None)
+        self.lexstate = state
+
+    # ------------------------------------------------------------
+    # push_state() - Changes the lexing state and saves old on stack
+    # ------------------------------------------------------------
+    def push_state(self, state):
+        self.lexstatestack.append(self.lexstate)
+        self.begin(state)
+
+    # ------------------------------------------------------------
+    # pop_state() - Restores the previous state
+    # ------------------------------------------------------------
+    def pop_state(self):
+        self.begin(self.lexstatestack.pop())
+
+    # ------------------------------------------------------------
+    # current_state() - Returns the current lexing state
+    # ------------------------------------------------------------
+    def current_state(self):
+        return self.lexstate
+
+    # ------------------------------------------------------------
+    # skip() - Skip ahead n characters
+    # ------------------------------------------------------------
+    def skip(self, n):
+        self.lexpos += n
+
+    # ------------------------------------------------------------
+    # opttoken() - Return the next token from the Lexer
+    #
+    # Note: This function has been carefully implemented to be as fast
+    # as possible.  Don't make changes unless you really know what
+    # you are doing
+    # ------------------------------------------------------------
+    def token(self):
+        # Make local copies of frequently referenced attributes
+        lexpos    = self.lexpos
+        lexlen    = self.lexlen
+        lexignore = self.lexignore
+        lexdata   = self.lexdata
+
+        while lexpos < lexlen:
+            # This code provides some short-circuit code for whitespace, tabs, and other ignored characters
+            if lexdata[lexpos] in lexignore:
+                lexpos += 1
+                continue
+
+            # Look for a regular expression match
+            for lexre, lexindexfunc in self.lexre:
+                m = lexre.match(lexdata, lexpos)
+                if not m:
+                    continue
+
+                # Create a token for return
+                tok = LexToken()
+                tok.value = m.group()
+                tok.lineno = self.lineno
+                tok.lexpos = lexpos
+
+                i = m.lastindex
+                func, tok.type = lexindexfunc[i]
+
+                if not func:
+                    # If no token type was set, it's an ignored token
+                    if tok.type:
+                        self.lexpos = m.end()
+                        return tok
+                    else:
+                        lexpos = m.end()
+                        break
+
+                lexpos = m.end()
+
+                # If token is processed by a function, call it
+
+                tok.lexer = self      # Set additional attributes useful in token rules
+                self.lexmatch = m
+                self.lexpos = lexpos
+
+                newtok = func(tok)
+
+                # Every function must return a token, if nothing, we just move to next token
+                if not newtok:
+                    lexpos    = self.lexpos         # This is here in case user has updated lexpos.
+                    lexignore = self.lexignore      # This is here in case there was a state change
+                    break
+
+                # Verify type of the token.  If not in the token map, raise an error
+                if not self.lexoptimize:
+                    if newtok.type not in self.lextokens_all:
+                        raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % (
+                            func.__code__.co_filename, func.__code__.co_firstlineno,
+                            func.__name__, newtok.type), lexdata[lexpos:])
+
+                return newtok
+            else:
+                # No match, see if in literals
+                if lexdata[lexpos] in self.lexliterals:
+                    tok = LexToken()
+                    tok.value = lexdata[lexpos]
+                    tok.lineno = self.lineno
+                    tok.type = tok.value
+                    tok.lexpos = lexpos
+                    self.lexpos = lexpos + 1
+                    return tok
+
+                # No match. Call t_error() if defined.
+                if self.lexerrorf:
+                    tok = LexToken()
+                    tok.value = self.lexdata[lexpos:]
+                    tok.lineno = self.lineno
+                    tok.type = 'error'
+                    tok.lexer = self
+                    tok.lexpos = lexpos
+                    self.lexpos = lexpos
+                    newtok = self.lexerrorf(tok)
+                    if lexpos == self.lexpos:
+                        # Error method didn't change text position at all. This is an error.
+                        raise LexError("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:])
+                    lexpos = self.lexpos
+                    if not newtok:
+                        continue
+                    return newtok
+
+                self.lexpos = lexpos
+                raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos], lexpos), lexdata[lexpos:])
+
+        if self.lexeoff:
+            tok = LexToken()
+            tok.type = 'eof'
+            tok.value = ''
+            tok.lineno = self.lineno
+            tok.lexpos = lexpos
+            tok.lexer = self
+            self.lexpos = lexpos
+            newtok = self.lexeoff(tok)
+            return newtok
+
+        self.lexpos = lexpos + 1
+        if self.lexdata is None:
+            raise RuntimeError('No input string given with input()')
+        return None
+
+    # Iterator interface
+    def __iter__(self):
+        return self
+
+    def next(self):
+        t = self.token()
+        if t is None:
+            raise StopIteration
+        return t
+
+    __next__ = next
+
+# -----------------------------------------------------------------------------
+#                           ==== Lex Builder ===
+#
+# The functions and classes below are used to collect lexing information
+# and build a Lexer object from it.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# _get_regex(func)
+#
+# Returns the regular expression assigned to a function either as a doc string
+# or as a .regex attribute attached by the @TOKEN decorator.
+# -----------------------------------------------------------------------------
+def _get_regex(func):
+    return getattr(func, 'regex', func.__doc__)
+
+# -----------------------------------------------------------------------------
+# get_caller_module_dict()
+#
+# This function returns a dictionary containing all of the symbols defined within
+# a caller further down the call stack.  This is used to get the environment
+# associated with the yacc() call if none was provided.
+# -----------------------------------------------------------------------------
+def get_caller_module_dict(levels):
+    f = sys._getframe(levels)
+    ldict = f.f_globals.copy()
+    if f.f_globals != f.f_locals:
+        ldict.update(f.f_locals)
+    return ldict
+
+# -----------------------------------------------------------------------------
+# _funcs_to_names()
+#
+# Given a list of regular expression functions, this converts it to a list
+# suitable for output to a table file
+# -----------------------------------------------------------------------------
+def _funcs_to_names(funclist, namelist):
+    result = []
+    for f, name in zip(funclist, namelist):
+        if f and f[0]:
+            result.append((name, f[1]))
+        else:
+            result.append(f)
+    return result
+
+# -----------------------------------------------------------------------------
+# _names_to_funcs()
+#
+# Given a list of regular expression function names, this converts it back to
+# functions.
+# -----------------------------------------------------------------------------
+def _names_to_funcs(namelist, fdict):
+    result = []
+    for n in namelist:
+        if n and n[0]:
+            result.append((fdict[n[0]], n[1]))
+        else:
+            result.append(n)
+    return result
+
+# -----------------------------------------------------------------------------
+# _form_master_re()
+#
+# This function takes a list of all of the regex components and attempts to
+# form the master regular expression.  Given limitations in the Python re
+# module, it may be necessary to break the master regex into separate expressions.
+# -----------------------------------------------------------------------------
+def _form_master_re(relist, reflags, ldict, toknames):
+    if not relist:
+        return []
+    regex = '|'.join(relist)
+    try:
+        lexre = re.compile(regex, reflags)
+
+        # Build the index to function map for the matching engine
+        lexindexfunc = [None] * (max(lexre.groupindex.values()) + 1)
+        lexindexnames = lexindexfunc[:]
+
+        for f, i in lexre.groupindex.items():
+            handle = ldict.get(f, None)
+            if type(handle) in (types.FunctionType, types.MethodType):
+                lexindexfunc[i] = (handle, toknames[f])
+                lexindexnames[i] = f
+            elif handle is not None:
+                lexindexnames[i] = f
+                if f.find('ignore_') > 0:
+                    lexindexfunc[i] = (None, None)
+                else:
+                    lexindexfunc[i] = (None, toknames[f])
+
+        return [(lexre, lexindexfunc)], [regex], [lexindexnames]
+    except Exception:
+        m = int(len(relist)/2)
+        if m == 0:
+            m = 1
+        llist, lre, lnames = _form_master_re(relist[:m], reflags, ldict, toknames)
+        rlist, rre, rnames = _form_master_re(relist[m:], reflags, ldict, toknames)
+        return (llist+rlist), (lre+rre), (lnames+rnames)
+
+# -----------------------------------------------------------------------------
+# def _statetoken(s,names)
+#
+# Given a declaration name s of the form "t_" and a dictionary whose keys are
+# state names, this function returns a tuple (states,tokenname) where states
+# is a tuple of state names and tokenname is the name of the token.  For example,
+# calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM')
+# -----------------------------------------------------------------------------
+def _statetoken(s, names):
+    parts = s.split('_')
+    for i, part in enumerate(parts[1:], 1):
+        if part not in names and part != 'ANY':
+            break
+
+    if i > 1:
+        states = tuple(parts[1:i])
+    else:
+        states = ('INITIAL',)
+
+    if 'ANY' in states:
+        states = tuple(names)
+
+    tokenname = '_'.join(parts[i:])
+    return (states, tokenname)
+
+
+# -----------------------------------------------------------------------------
+# LexerReflect()
+#
+# This class represents information needed to build a lexer as extracted from a
+# user's input file.
+# -----------------------------------------------------------------------------
+class LexerReflect(object):
+    def __init__(self, ldict, log=None, reflags=0):
+        self.ldict      = ldict
+        self.error_func = None
+        self.tokens     = []
+        self.reflags    = reflags
+        self.stateinfo  = {'INITIAL': 'inclusive'}
+        self.modules    = set()
+        self.error      = False
+        self.log        = PlyLogger(sys.stderr) if log is None else log
+
+    # Get all of the basic information
+    def get_all(self):
+        self.get_tokens()
+        self.get_literals()
+        self.get_states()
+        self.get_rules()
+
+    # Validate all of the information
+    def validate_all(self):
+        self.validate_tokens()
+        self.validate_literals()
+        self.validate_rules()
+        return self.error
+
+    # Get the tokens map
+    def get_tokens(self):
+        tokens = self.ldict.get('tokens', None)
+        if not tokens:
+            self.log.error('No token list is defined')
+            self.error = True
+            return
+
+        if not isinstance(tokens, (list, tuple)):
+            self.log.error('tokens must be a list or tuple')
+            self.error = True
+            return
+
+        if not tokens:
+            self.log.error('tokens is empty')
+            self.error = True
+            return
+
+        self.tokens = tokens
+
+    # Validate the tokens
+    def validate_tokens(self):
+        terminals = {}
+        for n in self.tokens:
+            if not _is_identifier.match(n):
+                self.log.error("Bad token name '%s'", n)
+                self.error = True
+            if n in terminals:
+                self.log.warning("Token '%s' multiply defined", n)
+            terminals[n] = 1
+
+    # Get the literals specifier
+    def get_literals(self):
+        self.literals = self.ldict.get('literals', '')
+        if not self.literals:
+            self.literals = ''
+
+    # Validate literals
+    def validate_literals(self):
+        try:
+            for c in self.literals:
+                if not isinstance(c, StringTypes) or len(c) > 1:
+                    self.log.error('Invalid literal %s. Must be a single character', repr(c))
+                    self.error = True
+
+        except TypeError:
+            self.log.error('Invalid literals specification. literals must be a sequence of characters')
+            self.error = True
+
+    def get_states(self):
+        self.states = self.ldict.get('states', None)
+        # Build statemap
+        if self.states:
+            if not isinstance(self.states, (tuple, list)):
+                self.log.error('states must be defined as a tuple or list')
+                self.error = True
+            else:
+                for s in self.states:
+                    if not isinstance(s, tuple) or len(s) != 2:
+                        self.log.error("Invalid state specifier %s. Must be a tuple (statename,'exclusive|inclusive')", repr(s))
+                        self.error = True
+                        continue
+                    name, statetype = s
+                    if not isinstance(name, StringTypes):
+                        self.log.error('State name %s must be a string', repr(name))
+                        self.error = True
+                        continue
+                    if not (statetype == 'inclusive' or statetype == 'exclusive'):
+                        self.log.error("State type for state %s must be 'inclusive' or 'exclusive'", name)
+                        self.error = True
+                        continue
+                    if name in self.stateinfo:
+                        self.log.error("State '%s' already defined", name)
+                        self.error = True
+                        continue
+                    self.stateinfo[name] = statetype
+
+    # Get all of the symbols with a t_ prefix and sort them into various
+    # categories (functions, strings, error functions, and ignore characters)
+
+    def get_rules(self):
+        tsymbols = [f for f in self.ldict if f[:2] == 't_']
+
+        # Now build up a list of functions and a list of strings
+        self.toknames = {}        # Mapping of symbols to token names
+        self.funcsym  = {}        # Symbols defined as functions
+        self.strsym   = {}        # Symbols defined as strings
+        self.ignore   = {}        # Ignore strings by state
+        self.errorf   = {}        # Error functions by state
+        self.eoff     = {}        # EOF functions by state
+
+        for s in self.stateinfo:
+            self.funcsym[s] = []
+            self.strsym[s] = []
+
+        if len(tsymbols) == 0:
+            self.log.error('No rules of the form t_rulename are defined')
+            self.error = True
+            return
+
+        for f in tsymbols:
+            t = self.ldict[f]
+            states, tokname = _statetoken(f, self.stateinfo)
+            self.toknames[f] = tokname
+
+            if hasattr(t, '__call__'):
+                if tokname == 'error':
+                    for s in states:
+                        self.errorf[s] = t
+                elif tokname == 'eof':
+                    for s in states:
+                        self.eoff[s] = t
+                elif tokname == 'ignore':
+                    line = t.__code__.co_firstlineno
+                    file = t.__code__.co_filename
+                    self.log.error("%s:%d: Rule '%s' must be defined as a string", file, line, t.__name__)
+                    self.error = True
+                else:
+                    for s in states:
+                        self.funcsym[s].append((f, t))
+            elif isinstance(t, StringTypes):
+                if tokname == 'ignore':
+                    for s in states:
+                        self.ignore[s] = t
+                    if '\\' in t:
+                        self.log.warning("%s contains a literal backslash '\\'", f)
+
+                elif tokname == 'error':
+                    self.log.error("Rule '%s' must be defined as a function", f)
+                    self.error = True
+                else:
+                    for s in states:
+                        self.strsym[s].append((f, t))
+            else:
+                self.log.error('%s not defined as a function or string', f)
+                self.error = True
+
+        # Sort the functions by line number
+        for f in self.funcsym.values():
+            f.sort(key=lambda x: x[1].__code__.co_firstlineno)
+
+        # Sort the strings by regular expression length
+        for s in self.strsym.values():
+            s.sort(key=lambda x: len(x[1]), reverse=True)
+
+    # Validate all of the t_rules collected
+    def validate_rules(self):
+        for state in self.stateinfo:
+            # Validate all rules defined by functions
+
+            for fname, f in self.funcsym[state]:
+                line = f.__code__.co_firstlineno
+                file = f.__code__.co_filename
+                module = inspect.getmodule(f)
+                self.modules.add(module)
+
+                tokname = self.toknames[fname]
+                if isinstance(f, types.MethodType):
+                    reqargs = 2
+                else:
+                    reqargs = 1
+                nargs = f.__code__.co_argcount
+                if nargs > reqargs:
+                    self.log.error("%s:%d: Rule '%s' has too many arguments", file, line, f.__name__)
+                    self.error = True
+                    continue
+
+                if nargs < reqargs:
+                    self.log.error("%s:%d: Rule '%s' requires an argument", file, line, f.__name__)
+                    self.error = True
+                    continue
+
+                if not _get_regex(f):
+                    self.log.error("%s:%d: No regular expression defined for rule '%s'", file, line, f.__name__)
+                    self.error = True
+                    continue
+
+                try:
+                    c = re.compile('(?P<%s>%s)' % (fname, _get_regex(f)), self.reflags)
+                    if c.match(''):
+                        self.log.error("%s:%d: Regular expression for rule '%s' matches empty string", file, line, f.__name__)
+                        self.error = True
+                except re.error as e:
+                    self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file, line, f.__name__, e)
+                    if '#' in _get_regex(f):
+                        self.log.error("%s:%d. Make sure '#' in rule '%s' is escaped with '\\#'", file, line, f.__name__)
+                    self.error = True
+
+            # Validate all rules defined by strings
+            for name, r in self.strsym[state]:
+                tokname = self.toknames[name]
+                if tokname == 'error':
+                    self.log.error("Rule '%s' must be defined as a function", name)
+                    self.error = True
+                    continue
+
+                if tokname not in self.tokens and tokname.find('ignore_') < 0:
+                    self.log.error("Rule '%s' defined for an unspecified token %s", name, tokname)
+                    self.error = True
+                    continue
+
+                try:
+                    c = re.compile('(?P<%s>%s)' % (name, r), self.reflags)
+                    if (c.match('')):
+                        self.log.error("Regular expression for rule '%s' matches empty string", name)
+                        self.error = True
+                except re.error as e:
+                    self.log.error("Invalid regular expression for rule '%s'. %s", name, e)
+                    if '#' in r:
+                        self.log.error("Make sure '#' in rule '%s' is escaped with '\\#'", name)
+                    self.error = True
+
+            if not self.funcsym[state] and not self.strsym[state]:
+                self.log.error("No rules defined for state '%s'", state)
+                self.error = True
+
+            # Validate the error function
+            efunc = self.errorf.get(state, None)
+            if efunc:
+                f = efunc
+                line = f.__code__.co_firstlineno
+                file = f.__code__.co_filename
+                module = inspect.getmodule(f)
+                self.modules.add(module)
+
+                if isinstance(f, types.MethodType):
+                    reqargs = 2
+                else:
+                    reqargs = 1
+                nargs = f.__code__.co_argcount
+                if nargs > reqargs:
+                    self.log.error("%s:%d: Rule '%s' has too many arguments", file, line, f.__name__)
+                    self.error = True
+
+                if nargs < reqargs:
+                    self.log.error("%s:%d: Rule '%s' requires an argument", file, line, f.__name__)
+                    self.error = True
+
+        for module in self.modules:
+            self.validate_module(module)
+
+    # -----------------------------------------------------------------------------
+    # validate_module()
+    #
+    # This checks to see if there are duplicated t_rulename() functions or strings
+    # in the parser input file.  This is done using a simple regular expression
+    # match on each line in the source code of the given module.
+    # -----------------------------------------------------------------------------
+
+    def validate_module(self, module):
+        try:
+            lines, linen = inspect.getsourcelines(module)
+        except IOError:
+            return
+
+        fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(')
+        sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=')
+
+        counthash = {}
+        linen += 1
+        for line in lines:
+            m = fre.match(line)
+            if not m:
+                m = sre.match(line)
+            if m:
+                name = m.group(1)
+                prev = counthash.get(name)
+                if not prev:
+                    counthash[name] = linen
+                else:
+                    filename = inspect.getsourcefile(module)
+                    self.log.error('%s:%d: Rule %s redefined. Previously defined on line %d', filename, linen, name, prev)
+                    self.error = True
+            linen += 1
+
+# -----------------------------------------------------------------------------
+# lex(module)
+#
+# Build all of the regular expression rules from definitions in the supplied module
+# -----------------------------------------------------------------------------
+def lex(module=None, object=None, debug=False, optimize=False, lextab='lextab',
+        reflags=int(re.VERBOSE), nowarn=False, outputdir=None, debuglog=None, errorlog=None):
+
+    if lextab is None:
+        lextab = 'lextab'
+
+    global lexer
+
+    ldict = None
+    stateinfo  = {'INITIAL': 'inclusive'}
+    lexobj = Lexer()
+    lexobj.lexoptimize = optimize
+    global token, input
+
+    if errorlog is None:
+        errorlog = PlyLogger(sys.stderr)
+
+    if debug:
+        if debuglog is None:
+            debuglog = PlyLogger(sys.stderr)
+
+    # Get the module dictionary used for the lexer
+    if object:
+        module = object
+
+    # Get the module dictionary used for the parser
+    if module:
+        _items = [(k, getattr(module, k)) for k in dir(module)]
+        ldict = dict(_items)
+        # If no __file__ attribute is available, try to obtain it from the __module__ instead
+        if '__file__' not in ldict:
+            ldict['__file__'] = sys.modules[ldict['__module__']].__file__
+    else:
+        ldict = get_caller_module_dict(2)
+
+    # Determine if the module is package of a package or not.
+    # If so, fix the tabmodule setting so that tables load correctly
+    pkg = ldict.get('__package__')
+    if pkg and isinstance(lextab, str):
+        if '.' not in lextab:
+            lextab = pkg + '.' + lextab
+
+    # Collect parser information from the dictionary
+    linfo = LexerReflect(ldict, log=errorlog, reflags=reflags)
+    linfo.get_all()
+    if not optimize:
+        if linfo.validate_all():
+            raise SyntaxError("Can't build lexer")
+
+    if optimize and lextab:
+        try:
+            lexobj.readtab(lextab, ldict)
+            token = lexobj.token
+            input = lexobj.input
+            lexer = lexobj
+            return lexobj
+
+        except ImportError:
+            pass
+
+    # Dump some basic debugging information
+    if debug:
+        debuglog.info('lex: tokens   = %r', linfo.tokens)
+        debuglog.info('lex: literals = %r', linfo.literals)
+        debuglog.info('lex: states   = %r', linfo.stateinfo)
+
+    # Build a dictionary of valid token names
+    lexobj.lextokens = set()
+    for n in linfo.tokens:
+        lexobj.lextokens.add(n)
+
+    # Get literals specification
+    if isinstance(linfo.literals, (list, tuple)):
+        lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals)
+    else:
+        lexobj.lexliterals = linfo.literals
+
+    lexobj.lextokens_all = lexobj.lextokens | set(lexobj.lexliterals)
+
+    # Get the stateinfo dictionary
+    stateinfo = linfo.stateinfo
+
+    regexs = {}
+    # Build the master regular expressions
+    for state in stateinfo:
+        regex_list = []
+
+        # Add rules defined by functions first
+        for fname, f in linfo.funcsym[state]:
+            regex_list.append('(?P<%s>%s)' % (fname, _get_regex(f)))
+            if debug:
+                debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", fname, _get_regex(f), state)
+
+        # Now add all of the simple rules
+        for name, r in linfo.strsym[state]:
+            regex_list.append('(?P<%s>%s)' % (name, r))
+            if debug:
+                debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", name, r, state)
+
+        regexs[state] = regex_list
+
+    # Build the master regular expressions
+
+    if debug:
+        debuglog.info('lex: ==== MASTER REGEXS FOLLOW ====')
+
+    for state in regexs:
+        lexre, re_text, re_names = _form_master_re(regexs[state], reflags, ldict, linfo.toknames)
+        lexobj.lexstatere[state] = lexre
+        lexobj.lexstateretext[state] = re_text
+        lexobj.lexstaterenames[state] = re_names
+        if debug:
+            for i, text in enumerate(re_text):
+                debuglog.info("lex: state '%s' : regex[%d] = '%s'", state, i, text)
+
+    # For inclusive states, we need to add the regular expressions from the INITIAL state
+    for state, stype in stateinfo.items():
+        if state != 'INITIAL' and stype == 'inclusive':
+            lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL'])
+            lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL'])
+            lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL'])
+
+    lexobj.lexstateinfo = stateinfo
+    lexobj.lexre = lexobj.lexstatere['INITIAL']
+    lexobj.lexretext = lexobj.lexstateretext['INITIAL']
+    lexobj.lexreflags = reflags
+
+    # Set up ignore variables
+    lexobj.lexstateignore = linfo.ignore
+    lexobj.lexignore = lexobj.lexstateignore.get('INITIAL', '')
+
+    # Set up error functions
+    lexobj.lexstateerrorf = linfo.errorf
+    lexobj.lexerrorf = linfo.errorf.get('INITIAL', None)
+    if not lexobj.lexerrorf:
+        errorlog.warning('No t_error rule is defined')
+
+    # Set up eof functions
+    lexobj.lexstateeoff = linfo.eoff
+    lexobj.lexeoff = linfo.eoff.get('INITIAL', None)
+
+    # Check state information for ignore and error rules
+    for s, stype in stateinfo.items():
+        if stype == 'exclusive':
+            if s not in linfo.errorf:
+                errorlog.warning("No error rule is defined for exclusive state '%s'", s)
+            if s not in linfo.ignore and lexobj.lexignore:
+                errorlog.warning("No ignore rule is defined for exclusive state '%s'", s)
+        elif stype == 'inclusive':
+            if s not in linfo.errorf:
+                linfo.errorf[s] = linfo.errorf.get('INITIAL', None)
+            if s not in linfo.ignore:
+                linfo.ignore[s] = linfo.ignore.get('INITIAL', '')
+
+    # Create global versions of the token() and input() functions
+    token = lexobj.token
+    input = lexobj.input
+    lexer = lexobj
+
+    # If in optimize mode, we write the lextab
+    if lextab and optimize:
+        if outputdir is None:
+            # If no output directory is set, the location of the output files
+            # is determined according to the following rules:
+            #     - If lextab specifies a package, files go into that package directory
+            #     - Otherwise, files go in the same directory as the specifying module
+            if isinstance(lextab, types.ModuleType):
+                srcfile = lextab.__file__
+            else:
+                if '.' not in lextab:
+                    srcfile = ldict['__file__']
+                else:
+                    parts = lextab.split('.')
+                    pkgname = '.'.join(parts[:-1])
+                    exec('import %s' % pkgname)
+                    srcfile = getattr(sys.modules[pkgname], '__file__', '')
+            outputdir = os.path.dirname(srcfile)
+        try:
+            lexobj.writetab(lextab, outputdir)
+            if lextab in sys.modules:
+                del sys.modules[lextab]
+        except IOError as e:
+            errorlog.warning("Couldn't write lextab module %r. %s" % (lextab, e))
+
+    return lexobj
+
+# -----------------------------------------------------------------------------
+# runmain()
+#
+# This runs the lexer as a main program
+# -----------------------------------------------------------------------------
+
+def runmain(lexer=None, data=None):
+    if not data:
+        try:
+            filename = sys.argv[1]
+            f = open(filename)
+            data = f.read()
+            f.close()
+        except IndexError:
+            sys.stdout.write('Reading from standard input (type EOF to end):\n')
+            data = sys.stdin.read()
+
+    if lexer:
+        _input = lexer.input
+    else:
+        _input = input
+    _input(data)
+    if lexer:
+        _token = lexer.token
+    else:
+        _token = token
+
+    while True:
+        tok = _token()
+        if not tok:
+            break
+        sys.stdout.write('(%s,%r,%d,%d)\n' % (tok.type, tok.value, tok.lineno, tok.lexpos))
+
+# -----------------------------------------------------------------------------
+# @TOKEN(regex)
+#
+# This decorator function can be used to set the regex expression on a function
+# when its docstring might need to be set in an alternative way
+# -----------------------------------------------------------------------------
+
+def TOKEN(r):
+    def set_regex(f):
+        if hasattr(r, '__call__'):
+            f.regex = _get_regex(r)
+        else:
+            f.regex = r
+        return f
+    return set_regex
+
+# Alternative spelling of the TOKEN decorator
+Token = TOKEN
diff --git a/server/libs/ply/yacc.py b/server/libs/ply/yacc.py
new file mode 100644
index 0000000..88188a1
--- /dev/null
+++ b/server/libs/ply/yacc.py
@@ -0,0 +1,3502 @@
+# -----------------------------------------------------------------------------
+# ply: yacc.py
+#
+# Copyright (C) 2001-2018
+# David M. Beazley (Dabeaz LLC)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# * Neither the name of the David Beazley or Dabeaz LLC may be used to
+#   endorse or promote products derived from this software without
+#  specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+# -----------------------------------------------------------------------------
+#
+# This implements an LR parser that is constructed from grammar rules defined
+# as Python functions. The grammar is specified by supplying the BNF inside
+# Python documentation strings.  The inspiration for this technique was borrowed
+# from John Aycock's Spark parsing system.  PLY might be viewed as cross between
+# Spark and the GNU bison utility.
+#
+# The current implementation is only somewhat object-oriented. The
+# LR parser itself is defined in terms of an object (which allows multiple
+# parsers to co-exist).  However, most of the variables used during table
+# construction are defined in terms of global variables.  Users shouldn't
+# notice unless they are trying to define multiple parsers at the same
+# time using threads (in which case they should have their head examined).
+#
+# This implementation supports both SLR and LALR(1) parsing.  LALR(1)
+# support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu),
+# using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles,
+# Techniques, and Tools" (The Dragon Book).  LALR(1) has since been replaced
+# by the more efficient DeRemer and Pennello algorithm.
+#
+# :::::::: WARNING :::::::
+#
+# Construction of LR parsing tables is fairly complicated and expensive.
+# To make this module run fast, a *LOT* of work has been put into
+# optimization---often at the expensive of readability and what might
+# consider to be good Python "coding style."   Modify the code at your
+# own risk!
+# ----------------------------------------------------------------------------
+
+import re
+import types
+import sys
+import os.path
+import inspect
+import warnings
+
+__version__    = '3.11'
+__tabversion__ = '3.10'
+
+#-----------------------------------------------------------------------------
+#                     === User configurable parameters ===
+#
+# Change these to modify the default behavior of yacc (if you wish)
+#-----------------------------------------------------------------------------
+
+yaccdebug   = True             # Debugging mode.  If set, yacc generates a
+                               # a 'parser.out' file in the current directory
+
+debug_file  = 'parser.out'     # Default name of the debugging file
+tab_module  = 'parsetab'       # Default name of the table module
+default_lr  = 'LALR'           # Default LR table generation method
+
+error_count = 3                # Number of symbols that must be shifted to leave recovery mode
+
+yaccdevel   = False            # Set to True if developing yacc.  This turns off optimized
+                               # implementations of certain functions.
+
+resultlimit = 40               # Size limit of results when running in debug mode.
+
+pickle_protocol = 0            # Protocol to use when writing pickle files
+
+# String type-checking compatibility
+if sys.version_info[0] < 3:
+    string_types = basestring
+else:
+    string_types = str
+
+MAXINT = sys.maxsize
+
+# This object is a stand-in for a logging object created by the
+# logging module.   PLY will use this by default to create things
+# such as the parser.out file.  If a user wants more detailed
+# information, they can create their own logging object and pass
+# it into PLY.
+
+class PlyLogger(object):
+    def __init__(self, f):
+        self.f = f
+
+    def debug(self, msg, *args, **kwargs):
+        self.f.write((msg % args) + '\n')
+
+    info = debug
+
+    def warning(self, msg, *args, **kwargs):
+        self.f.write('WARNING: ' + (msg % args) + '\n')
+
+    def error(self, msg, *args, **kwargs):
+        self.f.write('ERROR: ' + (msg % args) + '\n')
+
+    critical = debug
+
+# Null logger is used when no output is generated. Does nothing.
+class NullLogger(object):
+    def __getattribute__(self, name):
+        return self
+
+    def __call__(self, *args, **kwargs):
+        return self
+
+# Exception raised for yacc-related errors
+class YaccError(Exception):
+    pass
+
+# Format the result message that the parser produces when running in debug mode.
+def format_result(r):
+    repr_str = repr(r)
+    if '\n' in repr_str:
+        repr_str = repr(repr_str)
+    if len(repr_str) > resultlimit:
+        repr_str = repr_str[:resultlimit] + ' ...'
+    result = '<%s @ 0x%x> (%s)' % (type(r).__name__, id(r), repr_str)
+    return result
+
+# Format stack entries when the parser is running in debug mode
+def format_stack_entry(r):
+    repr_str = repr(r)
+    if '\n' in repr_str:
+        repr_str = repr(repr_str)
+    if len(repr_str) < 16:
+        return repr_str
+    else:
+        return '<%s @ 0x%x>' % (type(r).__name__, id(r))
+
+# Panic mode error recovery support.   This feature is being reworked--much of the
+# code here is to offer a deprecation/backwards compatible transition
+
+_errok = None
+_token = None
+_restart = None
+_warnmsg = '''PLY: Don't use global functions errok(), token(), and restart() in p_error().
+Instead, invoke the methods on the associated parser instance:
+
+    def p_error(p):
+        ...
+        # Use parser.errok(), parser.token(), parser.restart()
+        ...
+
+    parser = yacc.yacc()
+'''
+
+def errok():
+    warnings.warn(_warnmsg)
+    return _errok()
+
+def restart():
+    warnings.warn(_warnmsg)
+    return _restart()
+
+def token():
+    warnings.warn(_warnmsg)
+    return _token()
+
+# Utility function to call the p_error() function with some deprecation hacks
+def call_errorfunc(errorfunc, token, parser):
+    global _errok, _token, _restart
+    _errok = parser.errok
+    _token = parser.token
+    _restart = parser.restart
+    r = errorfunc(token)
+    try:
+        del _errok, _token, _restart
+    except NameError:
+        pass
+    return r
+
+#-----------------------------------------------------------------------------
+#                        ===  LR Parsing Engine ===
+#
+# The following classes are used for the LR parser itself.  These are not
+# used during table construction and are independent of the actual LR
+# table generation algorithm
+#-----------------------------------------------------------------------------
+
+# This class is used to hold non-terminal grammar symbols during parsing.
+# It normally has the following attributes set:
+#        .type       = Grammar symbol type
+#        .value      = Symbol value
+#        .lineno     = Starting line number
+#        .endlineno  = Ending line number (optional, set automatically)
+#        .lexpos     = Starting lex position
+#        .endlexpos  = Ending lex position (optional, set automatically)
+
+class YaccSymbol:
+    def __str__(self):
+        return self.type
+
+    def __repr__(self):
+        return str(self)
+
+# This class is a wrapper around the objects actually passed to each
+# grammar rule.   Index lookup and assignment actually assign the
+# .value attribute of the underlying YaccSymbol object.
+# The lineno() method returns the line number of a given
+# item (or 0 if not defined).   The linespan() method returns
+# a tuple of (startline,endline) representing the range of lines
+# for a symbol.  The lexspan() method returns a tuple (lexpos,endlexpos)
+# representing the range of positional information for a symbol.
+
+class YaccProduction:
+    def __init__(self, s, stack=None):
+        self.slice = s
+        self.stack = stack
+        self.lexer = None
+        self.parser = None
+
+    def __getitem__(self, n):
+        if isinstance(n, slice):
+            return [s.value for s in self.slice[n]]
+        elif n >= 0:
+            return self.slice[n].value
+        else:
+            return self.stack[n].value
+
+    def __setitem__(self, n, v):
+        self.slice[n].value = v
+
+    def __getslice__(self, i, j):
+        return [s.value for s in self.slice[i:j]]
+
+    def __len__(self):
+        return len(self.slice)
+
+    def lineno(self, n):
+        return getattr(self.slice[n], 'lineno', 0)
+
+    def set_lineno(self, n, lineno):
+        self.slice[n].lineno = lineno
+
+    def linespan(self, n):
+        startline = getattr(self.slice[n], 'lineno', 0)
+        endline = getattr(self.slice[n], 'endlineno', startline)
+        return startline, endline
+
+    def lexpos(self, n):
+        return getattr(self.slice[n], 'lexpos', 0)
+
+    def set_lexpos(self, n, lexpos):
+        self.slice[n].lexpos = lexpos
+
+    def lexspan(self, n):
+        startpos = getattr(self.slice[n], 'lexpos', 0)
+        endpos = getattr(self.slice[n], 'endlexpos', startpos)
+        return startpos, endpos
+
+    def error(self):
+        raise SyntaxError
+
+# -----------------------------------------------------------------------------
+#                               == LRParser ==
+#
+# The LR Parsing engine.
+# -----------------------------------------------------------------------------
+
+class LRParser:
+    def __init__(self, lrtab, errorf):
+        self.productions = lrtab.lr_productions
+        self.action = lrtab.lr_action
+        self.goto = lrtab.lr_goto
+        self.errorfunc = errorf
+        self.set_defaulted_states()
+        self.errorok = True
+
+    def errok(self):
+        self.errorok = True
+
+    def restart(self):
+        del self.statestack[:]
+        del self.symstack[:]
+        sym = YaccSymbol()
+        sym.type = '$end'
+        self.symstack.append(sym)
+        self.statestack.append(0)
+
+    # Defaulted state support.
+    # This method identifies parser states where there is only one possible reduction action.
+    # For such states, the parser can make a choose to make a rule reduction without consuming
+    # the next look-ahead token.  This delayed invocation of the tokenizer can be useful in
+    # certain kinds of advanced parsing situations where the lexer and parser interact with
+    # each other or change states (i.e., manipulation of scope, lexer states, etc.).
+    #
+    # See:  http://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html#Default-Reductions
+    def set_defaulted_states(self):
+        self.defaulted_states = {}
+        for state, actions in self.action.items():
+            rules = list(actions.values())
+            if len(rules) == 1 and rules[0] < 0:
+                self.defaulted_states[state] = rules[0]
+
+    def disable_defaulted_states(self):
+        self.defaulted_states = {}
+
+    def parse(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None):
+        if debug or yaccdevel:
+            if isinstance(debug, int):
+                debug = PlyLogger(sys.stderr)
+            return self.parsedebug(input, lexer, debug, tracking, tokenfunc)
+        elif tracking:
+            return self.parseopt(input, lexer, debug, tracking, tokenfunc)
+        else:
+            return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
+
+
+    # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+    # parsedebug().
+    #
+    # This is the debugging enabled version of parse().  All changes made to the
+    # parsing engine should be made here.   Optimized versions of this function
+    # are automatically created by the ply/ygen.py script.  This script cuts out
+    # sections enclosed in markers such as this:
+    #
+    #      #--! DEBUG
+    #      statements
+    #      #--! DEBUG
+    #
+    # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+    def parsedebug(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None):
+        #--! parsedebug-start
+        lookahead = None                         # Current lookahead symbol
+        lookaheadstack = []                      # Stack of lookahead symbols
+        actions = self.action                    # Local reference to action table (to avoid lookup on self.)
+        goto    = self.goto                      # Local reference to goto table (to avoid lookup on self.)
+        prod    = self.productions               # Local reference to production list (to avoid lookup on self.)
+        defaulted_states = self.defaulted_states # Local reference to defaulted states
+        pslice  = YaccProduction(None)           # Production object passed to grammar rules
+        errorcount = 0                           # Used during error recovery
+
+        #--! DEBUG
+        debug.info('PLY: PARSE DEBUG START')
+        #--! DEBUG
+
+        # If no lexer was given, we will try to use the lex module
+        if not lexer:
+            from . import lex
+            lexer = lex.lexer
+
+        # Set up the lexer and parser objects on pslice
+        pslice.lexer = lexer
+        pslice.parser = self
+
+        # If input was supplied, pass to lexer
+        if input is not None:
+            lexer.input(input)
+
+        if tokenfunc is None:
+            # Tokenize function
+            get_token = lexer.token
+        else:
+            get_token = tokenfunc
+
+        # Set the parser() token method (sometimes used in error recovery)
+        self.token = get_token
+
+        # Set up the state and symbol stacks
+
+        statestack = []                # Stack of parsing states
+        self.statestack = statestack
+        symstack   = []                # Stack of grammar symbols
+        self.symstack = symstack
+
+        pslice.stack = symstack         # Put in the production
+        errtoken   = None               # Err token
+
+        # The start state is assumed to be (0,$end)
+
+        statestack.append(0)
+        sym = YaccSymbol()
+        sym.type = '$end'
+        symstack.append(sym)
+        state = 0
+        while True:
+            # Get the next symbol on the input.  If a lookahead symbol
+            # is already set, we just use that. Otherwise, we'll pull
+            # the next token off of the lookaheadstack or from the lexer
+
+            #--! DEBUG
+            debug.debug('')
+            debug.debug('State  : %s', state)
+            #--! DEBUG
+
+            if state not in defaulted_states:
+                if not lookahead:
+                    if not lookaheadstack:
+                        lookahead = get_token()     # Get the next token
+                    else:
+                        lookahead = lookaheadstack.pop()
+                    if not lookahead:
+                        lookahead = YaccSymbol()
+                        lookahead.type = '$end'
+
+                # Check the action table
+                ltype = lookahead.type
+                t = actions[state].get(ltype)
+            else:
+                t = defaulted_states[state]
+                #--! DEBUG
+                debug.debug('Defaulted state %s: Reduce using %d', state, -t)
+                #--! DEBUG
+
+            #--! DEBUG
+            debug.debug('Stack  : %s',
+                        ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip())
+            #--! DEBUG
+
+            if t is not None:
+                if t > 0:
+                    # shift a symbol on the stack
+                    statestack.append(t)
+                    state = t
+
+                    #--! DEBUG
+                    debug.debug('Action : Shift and goto state %s', t)
+                    #--! DEBUG
+
+                    symstack.append(lookahead)
+                    lookahead = None
+
+                    # Decrease error count on successful shift
+                    if errorcount:
+                        errorcount -= 1
+                    continue
+
+                if t < 0:
+                    # reduce a symbol on the stack, emit a production
+                    p = prod[-t]
+                    pname = p.name
+                    plen  = p.len
+
+                    # Get production function
+                    sym = YaccSymbol()
+                    sym.type = pname       # Production name
+                    sym.value = None
+
+                    #--! DEBUG
+                    if plen:
+                        debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str,
+                                   '['+','.join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+']',
+                                   goto[statestack[-1-plen]][pname])
+                    else:
+                        debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, [],
+                                   goto[statestack[-1]][pname])
+
+                    #--! DEBUG
+
+                    if plen:
+                        targ = symstack[-plen-1:]
+                        targ[0] = sym
+
+                        #--! TRACKING
+                        if tracking:
+                            t1 = targ[1]
+                            sym.lineno = t1.lineno
+                            sym.lexpos = t1.lexpos
+                            t1 = targ[-1]
+                            sym.endlineno = getattr(t1, 'endlineno', t1.lineno)
+                            sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos)
+                        #--! TRACKING
+
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                        # The code enclosed in this section is duplicated
+                        # below as a performance optimization.  Make sure
+                        # changes get made in both locations.
+
+                        pslice.slice = targ
+
+                        try:
+                            # Call the grammar rule with our special slice object
+                            del symstack[-plen:]
+                            self.state = state
+                            p.callable(pslice)
+                            del statestack[-plen:]
+                            #--! DEBUG
+                            debug.info('Result : %s', format_result(pslice[0]))
+                            #--! DEBUG
+                            symstack.append(sym)
+                            state = goto[statestack[-1]][pname]
+                            statestack.append(state)
+                        except SyntaxError:
+                            # If an error was set. Enter error recovery state
+                            lookaheadstack.append(lookahead)    # Save the current lookahead token
+                            symstack.extend(targ[1:-1])         # Put the production slice back on the stack
+                            statestack.pop()                    # Pop back one state (before the reduce)
+                            state = statestack[-1]
+                            sym.type = 'error'
+                            sym.value = 'error'
+                            lookahead = sym
+                            errorcount = error_count
+                            self.errorok = False
+
+                        continue
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+                    else:
+
+                        #--! TRACKING
+                        if tracking:
+                            sym.lineno = lexer.lineno
+                            sym.lexpos = lexer.lexpos
+                        #--! TRACKING
+
+                        targ = [sym]
+
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                        # The code enclosed in this section is duplicated
+                        # above as a performance optimization.  Make sure
+                        # changes get made in both locations.
+
+                        pslice.slice = targ
+
+                        try:
+                            # Call the grammar rule with our special slice object
+                            self.state = state
+                            p.callable(pslice)
+                            #--! DEBUG
+                            debug.info('Result : %s', format_result(pslice[0]))
+                            #--! DEBUG
+                            symstack.append(sym)
+                            state = goto[statestack[-1]][pname]
+                            statestack.append(state)
+                        except SyntaxError:
+                            # If an error was set. Enter error recovery state
+                            lookaheadstack.append(lookahead)    # Save the current lookahead token
+                            statestack.pop()                    # Pop back one state (before the reduce)
+                            state = statestack[-1]
+                            sym.type = 'error'
+                            sym.value = 'error'
+                            lookahead = sym
+                            errorcount = error_count
+                            self.errorok = False
+
+                        continue
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+                if t == 0:
+                    n = symstack[-1]
+                    result = getattr(n, 'value', None)
+                    #--! DEBUG
+                    debug.info('Done   : Returning %s', format_result(result))
+                    debug.info('PLY: PARSE DEBUG END')
+                    #--! DEBUG
+                    return result
+
+            if t is None:
+
+                #--! DEBUG
+                debug.error('Error  : %s',
+                            ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip())
+                #--! DEBUG
+
+                # We have some kind of parsing error here.  To handle
+                # this, we are going to push the current token onto
+                # the tokenstack and replace it with an 'error' token.
+                # If there are any synchronization rules, they may
+                # catch it.
+                #
+                # In addition to pushing the error token, we call call
+                # the user defined p_error() function if this is the
+                # first syntax error.  This function is only called if
+                # errorcount == 0.
+                if errorcount == 0 or self.errorok:
+                    errorcount = error_count
+                    self.errorok = False
+                    errtoken = lookahead
+                    if errtoken.type == '$end':
+                        errtoken = None               # End of file!
+                    if self.errorfunc:
+                        if errtoken and not hasattr(errtoken, 'lexer'):
+                            errtoken.lexer = lexer
+                        self.state = state
+                        tok = call_errorfunc(self.errorfunc, errtoken, self)
+                        if self.errorok:
+                            # User must have done some kind of panic
+                            # mode recovery on their own.  The
+                            # returned token is the next lookahead
+                            lookahead = tok
+                            errtoken = None
+                            continue
+                    else:
+                        if errtoken:
+                            if hasattr(errtoken, 'lineno'):
+                                lineno = lookahead.lineno
+                            else:
+                                lineno = 0
+                            if lineno:
+                                sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type))
+                            else:
+                                sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type)
+                        else:
+                            sys.stderr.write('yacc: Parse error in input. EOF\n')
+                            return
+
+                else:
+                    errorcount = error_count
+
+                # case 1:  the statestack only has 1 entry on it.  If we're in this state, the
+                # entire parse has been rolled back and we're completely hosed.   The token is
+                # discarded and we just keep going.
+
+                if len(statestack) <= 1 and lookahead.type != '$end':
+                    lookahead = None
+                    errtoken = None
+                    state = 0
+                    # Nuke the pushback stack
+                    del lookaheadstack[:]
+                    continue
+
+                # case 2: the statestack has a couple of entries on it, but we're
+                # at the end of the file. nuke the top entry and generate an error token
+
+                # Start nuking entries on the stack
+                if lookahead.type == '$end':
+                    # Whoa. We're really hosed here. Bail out
+                    return
+
+                if lookahead.type != 'error':
+                    sym = symstack[-1]
+                    if sym.type == 'error':
+                        # Hmmm. Error is on top of stack, we'll just nuke input
+                        # symbol and continue
+                        #--! TRACKING
+                        if tracking:
+                            sym.endlineno = getattr(lookahead, 'lineno', sym.lineno)
+                            sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos)
+                        #--! TRACKING
+                        lookahead = None
+                        continue
+
+                    # Create the error symbol for the first time and make it the new lookahead symbol
+                    t = YaccSymbol()
+                    t.type = 'error'
+
+                    if hasattr(lookahead, 'lineno'):
+                        t.lineno = t.endlineno = lookahead.lineno
+                    if hasattr(lookahead, 'lexpos'):
+                        t.lexpos = t.endlexpos = lookahead.lexpos
+                    t.value = lookahead
+                    lookaheadstack.append(lookahead)
+                    lookahead = t
+                else:
+                    sym = symstack.pop()
+                    #--! TRACKING
+                    if tracking:
+                        lookahead.lineno = sym.lineno
+                        lookahead.lexpos = sym.lexpos
+                    #--! TRACKING
+                    statestack.pop()
+                    state = statestack[-1]
+
+                continue
+
+            # Call an error function here
+            raise RuntimeError('yacc: internal parser error!!!\n')
+
+        #--! parsedebug-end
+
+    # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+    # parseopt().
+    #
+    # Optimized version of parse() method.  DO NOT EDIT THIS CODE DIRECTLY!
+    # This code is automatically generated by the ply/ygen.py script. Make
+    # changes to the parsedebug() method instead.
+    # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+    def parseopt(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None):
+        #--! parseopt-start
+        lookahead = None                         # Current lookahead symbol
+        lookaheadstack = []                      # Stack of lookahead symbols
+        actions = self.action                    # Local reference to action table (to avoid lookup on self.)
+        goto    = self.goto                      # Local reference to goto table (to avoid lookup on self.)
+        prod    = self.productions               # Local reference to production list (to avoid lookup on self.)
+        defaulted_states = self.defaulted_states # Local reference to defaulted states
+        pslice  = YaccProduction(None)           # Production object passed to grammar rules
+        errorcount = 0                           # Used during error recovery
+
+
+        # If no lexer was given, we will try to use the lex module
+        if not lexer:
+            from . import lex
+            lexer = lex.lexer
+
+        # Set up the lexer and parser objects on pslice
+        pslice.lexer = lexer
+        pslice.parser = self
+
+        # If input was supplied, pass to lexer
+        if input is not None:
+            lexer.input(input)
+
+        if tokenfunc is None:
+            # Tokenize function
+            get_token = lexer.token
+        else:
+            get_token = tokenfunc
+
+        # Set the parser() token method (sometimes used in error recovery)
+        self.token = get_token
+
+        # Set up the state and symbol stacks
+
+        statestack = []                # Stack of parsing states
+        self.statestack = statestack
+        symstack   = []                # Stack of grammar symbols
+        self.symstack = symstack
+
+        pslice.stack = symstack         # Put in the production
+        errtoken   = None               # Err token
+
+        # The start state is assumed to be (0,$end)
+
+        statestack.append(0)
+        sym = YaccSymbol()
+        sym.type = '$end'
+        symstack.append(sym)
+        state = 0
+        while True:
+            # Get the next symbol on the input.  If a lookahead symbol
+            # is already set, we just use that. Otherwise, we'll pull
+            # the next token off of the lookaheadstack or from the lexer
+
+
+            if state not in defaulted_states:
+                if not lookahead:
+                    if not lookaheadstack:
+                        lookahead = get_token()     # Get the next token
+                    else:
+                        lookahead = lookaheadstack.pop()
+                    if not lookahead:
+                        lookahead = YaccSymbol()
+                        lookahead.type = '$end'
+
+                # Check the action table
+                ltype = lookahead.type
+                t = actions[state].get(ltype)
+            else:
+                t = defaulted_states[state]
+
+
+            if t is not None:
+                if t > 0:
+                    # shift a symbol on the stack
+                    statestack.append(t)
+                    state = t
+
+
+                    symstack.append(lookahead)
+                    lookahead = None
+
+                    # Decrease error count on successful shift
+                    if errorcount:
+                        errorcount -= 1
+                    continue
+
+                if t < 0:
+                    # reduce a symbol on the stack, emit a production
+                    p = prod[-t]
+                    pname = p.name
+                    plen  = p.len
+
+                    # Get production function
+                    sym = YaccSymbol()
+                    sym.type = pname       # Production name
+                    sym.value = None
+
+
+                    if plen:
+                        targ = symstack[-plen-1:]
+                        targ[0] = sym
+
+                        #--! TRACKING
+                        if tracking:
+                            t1 = targ[1]
+                            sym.lineno = t1.lineno
+                            sym.lexpos = t1.lexpos
+                            t1 = targ[-1]
+                            sym.endlineno = getattr(t1, 'endlineno', t1.lineno)
+                            sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos)
+                        #--! TRACKING
+
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                        # The code enclosed in this section is duplicated
+                        # below as a performance optimization.  Make sure
+                        # changes get made in both locations.
+
+                        pslice.slice = targ
+
+                        try:
+                            # Call the grammar rule with our special slice object
+                            del symstack[-plen:]
+                            self.state = state
+                            p.callable(pslice)
+                            del statestack[-plen:]
+                            symstack.append(sym)
+                            state = goto[statestack[-1]][pname]
+                            statestack.append(state)
+                        except SyntaxError:
+                            # If an error was set. Enter error recovery state
+                            lookaheadstack.append(lookahead)    # Save the current lookahead token
+                            symstack.extend(targ[1:-1])         # Put the production slice back on the stack
+                            statestack.pop()                    # Pop back one state (before the reduce)
+                            state = statestack[-1]
+                            sym.type = 'error'
+                            sym.value = 'error'
+                            lookahead = sym
+                            errorcount = error_count
+                            self.errorok = False
+
+                        continue
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+                    else:
+
+                        #--! TRACKING
+                        if tracking:
+                            sym.lineno = lexer.lineno
+                            sym.lexpos = lexer.lexpos
+                        #--! TRACKING
+
+                        targ = [sym]
+
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                        # The code enclosed in this section is duplicated
+                        # above as a performance optimization.  Make sure
+                        # changes get made in both locations.
+
+                        pslice.slice = targ
+
+                        try:
+                            # Call the grammar rule with our special slice object
+                            self.state = state
+                            p.callable(pslice)
+                            symstack.append(sym)
+                            state = goto[statestack[-1]][pname]
+                            statestack.append(state)
+                        except SyntaxError:
+                            # If an error was set. Enter error recovery state
+                            lookaheadstack.append(lookahead)    # Save the current lookahead token
+                            statestack.pop()                    # Pop back one state (before the reduce)
+                            state = statestack[-1]
+                            sym.type = 'error'
+                            sym.value = 'error'
+                            lookahead = sym
+                            errorcount = error_count
+                            self.errorok = False
+
+                        continue
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+                if t == 0:
+                    n = symstack[-1]
+                    result = getattr(n, 'value', None)
+                    return result
+
+            if t is None:
+
+
+                # We have some kind of parsing error here.  To handle
+                # this, we are going to push the current token onto
+                # the tokenstack and replace it with an 'error' token.
+                # If there are any synchronization rules, they may
+                # catch it.
+                #
+                # In addition to pushing the error token, we call call
+                # the user defined p_error() function if this is the
+                # first syntax error.  This function is only called if
+                # errorcount == 0.
+                if errorcount == 0 or self.errorok:
+                    errorcount = error_count
+                    self.errorok = False
+                    errtoken = lookahead
+                    if errtoken.type == '$end':
+                        errtoken = None               # End of file!
+                    if self.errorfunc:
+                        if errtoken and not hasattr(errtoken, 'lexer'):
+                            errtoken.lexer = lexer
+                        self.state = state
+                        tok = call_errorfunc(self.errorfunc, errtoken, self)
+                        if self.errorok:
+                            # User must have done some kind of panic
+                            # mode recovery on their own.  The
+                            # returned token is the next lookahead
+                            lookahead = tok
+                            errtoken = None
+                            continue
+                    else:
+                        if errtoken:
+                            if hasattr(errtoken, 'lineno'):
+                                lineno = lookahead.lineno
+                            else:
+                                lineno = 0
+                            if lineno:
+                                sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type))
+                            else:
+                                sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type)
+                        else:
+                            sys.stderr.write('yacc: Parse error in input. EOF\n')
+                            return
+
+                else:
+                    errorcount = error_count
+
+                # case 1:  the statestack only has 1 entry on it.  If we're in this state, the
+                # entire parse has been rolled back and we're completely hosed.   The token is
+                # discarded and we just keep going.
+
+                if len(statestack) <= 1 and lookahead.type != '$end':
+                    lookahead = None
+                    errtoken = None
+                    state = 0
+                    # Nuke the pushback stack
+                    del lookaheadstack[:]
+                    continue
+
+                # case 2: the statestack has a couple of entries on it, but we're
+                # at the end of the file. nuke the top entry and generate an error token
+
+                # Start nuking entries on the stack
+                if lookahead.type == '$end':
+                    # Whoa. We're really hosed here. Bail out
+                    return
+
+                if lookahead.type != 'error':
+                    sym = symstack[-1]
+                    if sym.type == 'error':
+                        # Hmmm. Error is on top of stack, we'll just nuke input
+                        # symbol and continue
+                        #--! TRACKING
+                        if tracking:
+                            sym.endlineno = getattr(lookahead, 'lineno', sym.lineno)
+                            sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos)
+                        #--! TRACKING
+                        lookahead = None
+                        continue
+
+                    # Create the error symbol for the first time and make it the new lookahead symbol
+                    t = YaccSymbol()
+                    t.type = 'error'
+
+                    if hasattr(lookahead, 'lineno'):
+                        t.lineno = t.endlineno = lookahead.lineno
+                    if hasattr(lookahead, 'lexpos'):
+                        t.lexpos = t.endlexpos = lookahead.lexpos
+                    t.value = lookahead
+                    lookaheadstack.append(lookahead)
+                    lookahead = t
+                else:
+                    sym = symstack.pop()
+                    #--! TRACKING
+                    if tracking:
+                        lookahead.lineno = sym.lineno
+                        lookahead.lexpos = sym.lexpos
+                    #--! TRACKING
+                    statestack.pop()
+                    state = statestack[-1]
+
+                continue
+
+            # Call an error function here
+            raise RuntimeError('yacc: internal parser error!!!\n')
+
+        #--! parseopt-end
+
+    # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+    # parseopt_notrack().
+    #
+    # Optimized version of parseopt() with line number tracking removed.
+    # DO NOT EDIT THIS CODE DIRECTLY. This code is automatically generated
+    # by the ply/ygen.py script. Make changes to the parsedebug() method instead.
+    # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+    def parseopt_notrack(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None):
+        #--! parseopt-notrack-start
+        lookahead = None                         # Current lookahead symbol
+        lookaheadstack = []                      # Stack of lookahead symbols
+        actions = self.action                    # Local reference to action table (to avoid lookup on self.)
+        goto    = self.goto                      # Local reference to goto table (to avoid lookup on self.)
+        prod    = self.productions               # Local reference to production list (to avoid lookup on self.)
+        defaulted_states = self.defaulted_states # Local reference to defaulted states
+        pslice  = YaccProduction(None)           # Production object passed to grammar rules
+        errorcount = 0                           # Used during error recovery
+
+
+        # If no lexer was given, we will try to use the lex module
+        if not lexer:
+            from . import lex
+            lexer = lex.lexer
+
+        # Set up the lexer and parser objects on pslice
+        pslice.lexer = lexer
+        pslice.parser = self
+
+        # If input was supplied, pass to lexer
+        if input is not None:
+            lexer.input(input)
+
+        if tokenfunc is None:
+            # Tokenize function
+            get_token = lexer.token
+        else:
+            get_token = tokenfunc
+
+        # Set the parser() token method (sometimes used in error recovery)
+        self.token = get_token
+
+        # Set up the state and symbol stacks
+
+        statestack = []                # Stack of parsing states
+        self.statestack = statestack
+        symstack   = []                # Stack of grammar symbols
+        self.symstack = symstack
+
+        pslice.stack = symstack         # Put in the production
+        errtoken   = None               # Err token
+
+        # The start state is assumed to be (0,$end)
+
+        statestack.append(0)
+        sym = YaccSymbol()
+        sym.type = '$end'
+        symstack.append(sym)
+        state = 0
+        while True:
+            # Get the next symbol on the input.  If a lookahead symbol
+            # is already set, we just use that. Otherwise, we'll pull
+            # the next token off of the lookaheadstack or from the lexer
+
+
+            if state not in defaulted_states:
+                if not lookahead:
+                    if not lookaheadstack:
+                        lookahead = get_token()     # Get the next token
+                    else:
+                        lookahead = lookaheadstack.pop()
+                    if not lookahead:
+                        lookahead = YaccSymbol()
+                        lookahead.type = '$end'
+
+                # Check the action table
+                ltype = lookahead.type
+                t = actions[state].get(ltype)
+            else:
+                t = defaulted_states[state]
+
+
+            if t is not None:
+                if t > 0:
+                    # shift a symbol on the stack
+                    statestack.append(t)
+                    state = t
+
+
+                    symstack.append(lookahead)
+                    lookahead = None
+
+                    # Decrease error count on successful shift
+                    if errorcount:
+                        errorcount -= 1
+                    continue
+
+                if t < 0:
+                    # reduce a symbol on the stack, emit a production
+                    p = prod[-t]
+                    pname = p.name
+                    plen  = p.len
+
+                    # Get production function
+                    sym = YaccSymbol()
+                    sym.type = pname       # Production name
+                    sym.value = None
+
+
+                    if plen:
+                        targ = symstack[-plen-1:]
+                        targ[0] = sym
+
+
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                        # The code enclosed in this section is duplicated
+                        # below as a performance optimization.  Make sure
+                        # changes get made in both locations.
+
+                        pslice.slice = targ
+
+                        try:
+                            # Call the grammar rule with our special slice object
+                            del symstack[-plen:]
+                            self.state = state
+                            p.callable(pslice)
+                            del statestack[-plen:]
+                            symstack.append(sym)
+                            state = goto[statestack[-1]][pname]
+                            statestack.append(state)
+                        except SyntaxError:
+                            # If an error was set. Enter error recovery state
+                            lookaheadstack.append(lookahead)    # Save the current lookahead token
+                            symstack.extend(targ[1:-1])         # Put the production slice back on the stack
+                            statestack.pop()                    # Pop back one state (before the reduce)
+                            state = statestack[-1]
+                            sym.type = 'error'
+                            sym.value = 'error'
+                            lookahead = sym
+                            errorcount = error_count
+                            self.errorok = False
+
+                        continue
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+                    else:
+
+
+                        targ = [sym]
+
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+                        # The code enclosed in this section is duplicated
+                        # above as a performance optimization.  Make sure
+                        # changes get made in both locations.
+
+                        pslice.slice = targ
+
+                        try:
+                            # Call the grammar rule with our special slice object
+                            self.state = state
+                            p.callable(pslice)
+                            symstack.append(sym)
+                            state = goto[statestack[-1]][pname]
+                            statestack.append(state)
+                        except SyntaxError:
+                            # If an error was set. Enter error recovery state
+                            lookaheadstack.append(lookahead)    # Save the current lookahead token
+                            statestack.pop()                    # Pop back one state (before the reduce)
+                            state = statestack[-1]
+                            sym.type = 'error'
+                            sym.value = 'error'
+                            lookahead = sym
+                            errorcount = error_count
+                            self.errorok = False
+
+                        continue
+                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+                if t == 0:
+                    n = symstack[-1]
+                    result = getattr(n, 'value', None)
+                    return result
+
+            if t is None:
+
+
+                # We have some kind of parsing error here.  To handle
+                # this, we are going to push the current token onto
+                # the tokenstack and replace it with an 'error' token.
+                # If there are any synchronization rules, they may
+                # catch it.
+                #
+                # In addition to pushing the error token, we call call
+                # the user defined p_error() function if this is the
+                # first syntax error.  This function is only called if
+                # errorcount == 0.
+                if errorcount == 0 or self.errorok:
+                    errorcount = error_count
+                    self.errorok = False
+                    errtoken = lookahead
+                    if errtoken.type == '$end':
+                        errtoken = None               # End of file!
+                    if self.errorfunc:
+                        if errtoken and not hasattr(errtoken, 'lexer'):
+                            errtoken.lexer = lexer
+                        self.state = state
+                        tok = call_errorfunc(self.errorfunc, errtoken, self)
+                        if self.errorok:
+                            # User must have done some kind of panic
+                            # mode recovery on their own.  The
+                            # returned token is the next lookahead
+                            lookahead = tok
+                            errtoken = None
+                            continue
+                    else:
+                        if errtoken:
+                            if hasattr(errtoken, 'lineno'):
+                                lineno = lookahead.lineno
+                            else:
+                                lineno = 0
+                            if lineno:
+                                sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type))
+                            else:
+                                sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type)
+                        else:
+                            sys.stderr.write('yacc: Parse error in input. EOF\n')
+                            return
+
+                else:
+                    errorcount = error_count
+
+                # case 1:  the statestack only has 1 entry on it.  If we're in this state, the
+                # entire parse has been rolled back and we're completely hosed.   The token is
+                # discarded and we just keep going.
+
+                if len(statestack) <= 1 and lookahead.type != '$end':
+                    lookahead = None
+                    errtoken = None
+                    state = 0
+                    # Nuke the pushback stack
+                    del lookaheadstack[:]
+                    continue
+
+                # case 2: the statestack has a couple of entries on it, but we're
+                # at the end of the file. nuke the top entry and generate an error token
+
+                # Start nuking entries on the stack
+                if lookahead.type == '$end':
+                    # Whoa. We're really hosed here. Bail out
+                    return
+
+                if lookahead.type != 'error':
+                    sym = symstack[-1]
+                    if sym.type == 'error':
+                        # Hmmm. Error is on top of stack, we'll just nuke input
+                        # symbol and continue
+                        lookahead = None
+                        continue
+
+                    # Create the error symbol for the first time and make it the new lookahead symbol
+                    t = YaccSymbol()
+                    t.type = 'error'
+
+                    if hasattr(lookahead, 'lineno'):
+                        t.lineno = t.endlineno = lookahead.lineno
+                    if hasattr(lookahead, 'lexpos'):
+                        t.lexpos = t.endlexpos = lookahead.lexpos
+                    t.value = lookahead
+                    lookaheadstack.append(lookahead)
+                    lookahead = t
+                else:
+                    sym = symstack.pop()
+                    statestack.pop()
+                    state = statestack[-1]
+
+                continue
+
+            # Call an error function here
+            raise RuntimeError('yacc: internal parser error!!!\n')
+
+        #--! parseopt-notrack-end
+
+# -----------------------------------------------------------------------------
+#                          === Grammar Representation ===
+#
+# The following functions, classes, and variables are used to represent and
+# manipulate the rules that make up a grammar.
+# -----------------------------------------------------------------------------
+
+# regex matching identifiers
+_is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$')
+
+# -----------------------------------------------------------------------------
+# class Production:
+#
+# This class stores the raw information about a single production or grammar rule.
+# A grammar rule refers to a specification such as this:
+#
+#       expr : expr PLUS term
+#
+# Here are the basic attributes defined on all productions
+#
+#       name     - Name of the production.  For example 'expr'
+#       prod     - A list of symbols on the right side ['expr','PLUS','term']
+#       prec     - Production precedence level
+#       number   - Production number.
+#       func     - Function that executes on reduce
+#       file     - File where production function is defined
+#       lineno   - Line number where production function is defined
+#
+# The following attributes are defined or optional.
+#
+#       len       - Length of the production (number of symbols on right hand side)
+#       usyms     - Set of unique symbols found in the production
+# -----------------------------------------------------------------------------
+
+class Production(object):
+    reduced = 0
+    def __init__(self, number, name, prod, precedence=('right', 0), func=None, file='', line=0):
+        self.name     = name
+        self.prod     = tuple(prod)
+        self.number   = number
+        self.func     = func
+        self.callable = None
+        self.file     = file
+        self.line     = line
+        self.prec     = precedence
+
+        # Internal settings used during table construction
+
+        self.len  = len(self.prod)   # Length of the production
+
+        # Create a list of unique production symbols used in the production
+        self.usyms = []
+        for s in self.prod:
+            if s not in self.usyms:
+                self.usyms.append(s)
+
+        # List of all LR items for the production
+        self.lr_items = []
+        self.lr_next = None
+
+        # Create a string representation
+        if self.prod:
+            self.str = '%s -> %s' % (self.name, ' '.join(self.prod))
+        else:
+            self.str = '%s -> ' % self.name
+
+    def __str__(self):
+        return self.str
+
+    def __repr__(self):
+        return 'Production(' + str(self) + ')'
+
+    def __len__(self):
+        return len(self.prod)
+
+    def __nonzero__(self):
+        return 1
+
+    def __getitem__(self, index):
+        return self.prod[index]
+
+    # Return the nth lr_item from the production (or None if at the end)
+    def lr_item(self, n):
+        if n > len(self.prod):
+            return None
+        p = LRItem(self, n)
+        # Precompute the list of productions immediately following.
+        try:
+            p.lr_after = self.Prodnames[p.prod[n+1]]
+        except (IndexError, KeyError):
+            p.lr_after = []
+        try:
+            p.lr_before = p.prod[n-1]
+        except IndexError:
+            p.lr_before = None
+        return p
+
+    # Bind the production function name to a callable
+    def bind(self, pdict):
+        if self.func:
+            self.callable = pdict[self.func]
+
+# This class serves as a minimal standin for Production objects when
+# reading table data from files.   It only contains information
+# actually used by the LR parsing engine, plus some additional
+# debugging information.
+class MiniProduction(object):
+    def __init__(self, str, name, len, func, file, line):
+        self.name     = name
+        self.len      = len
+        self.func     = func
+        self.callable = None
+        self.file     = file
+        self.line     = line
+        self.str      = str
+
+    def __str__(self):
+        return self.str
+
+    def __repr__(self):
+        return 'MiniProduction(%s)' % self.str
+
+    # Bind the production function name to a callable
+    def bind(self, pdict):
+        if self.func:
+            self.callable = pdict[self.func]
+
+
+# -----------------------------------------------------------------------------
+# class LRItem
+#
+# This class represents a specific stage of parsing a production rule.  For
+# example:
+#
+#       expr : expr . PLUS term
+#
+# In the above, the "." represents the current location of the parse.  Here
+# basic attributes:
+#
+#       name       - Name of the production.  For example 'expr'
+#       prod       - A list of symbols on the right side ['expr','.', 'PLUS','term']
+#       number     - Production number.
+#
+#       lr_next      Next LR item. Example, if we are ' expr -> expr . PLUS term'
+#                    then lr_next refers to 'expr -> expr PLUS . term'
+#       lr_index   - LR item index (location of the ".") in the prod list.
+#       lookaheads - LALR lookahead symbols for this item
+#       len        - Length of the production (number of symbols on right hand side)
+#       lr_after    - List of all productions that immediately follow
+#       lr_before   - Grammar symbol immediately before
+# -----------------------------------------------------------------------------
+
+class LRItem(object):
+    def __init__(self, p, n):
+        self.name       = p.name
+        self.prod       = list(p.prod)
+        self.number     = p.number
+        self.lr_index   = n
+        self.lookaheads = {}
+        self.prod.insert(n, '.')
+        self.prod       = tuple(self.prod)
+        self.len        = len(self.prod)
+        self.usyms      = p.usyms
+
+    def __str__(self):
+        if self.prod:
+            s = '%s -> %s' % (self.name, ' '.join(self.prod))
+        else:
+            s = '%s -> ' % self.name
+        return s
+
+    def __repr__(self):
+        return 'LRItem(' + str(self) + ')'
+
+# -----------------------------------------------------------------------------
+# rightmost_terminal()
+#
+# Return the rightmost terminal from a list of symbols.  Used in add_production()
+# -----------------------------------------------------------------------------
+def rightmost_terminal(symbols, terminals):
+    i = len(symbols) - 1
+    while i >= 0:
+        if symbols[i] in terminals:
+            return symbols[i]
+        i -= 1
+    return None
+
+# -----------------------------------------------------------------------------
+#                           === GRAMMAR CLASS ===
+#
+# The following class represents the contents of the specified grammar along
+# with various computed properties such as first sets, follow sets, LR items, etc.
+# This data is used for critical parts of the table generation process later.
+# -----------------------------------------------------------------------------
+
+class GrammarError(YaccError):
+    pass
+
+class Grammar(object):
+    def __init__(self, terminals):
+        self.Productions  = [None]  # A list of all of the productions.  The first
+                                    # entry is always reserved for the purpose of
+                                    # building an augmented grammar
+
+        self.Prodnames    = {}      # A dictionary mapping the names of nonterminals to a list of all
+                                    # productions of that nonterminal.
+
+        self.Prodmap      = {}      # A dictionary that is only used to detect duplicate
+                                    # productions.
+
+        self.Terminals    = {}      # A dictionary mapping the names of terminal symbols to a
+                                    # list of the rules where they are used.
+
+        for term in terminals:
+            self.Terminals[term] = []
+
+        self.Terminals['error'] = []
+
+        self.Nonterminals = {}      # A dictionary mapping names of nonterminals to a list
+                                    # of rule numbers where they are used.
+
+        self.First        = {}      # A dictionary of precomputed FIRST(x) symbols
+
+        self.Follow       = {}      # A dictionary of precomputed FOLLOW(x) symbols
+
+        self.Precedence   = {}      # Precedence rules for each terminal. Contains tuples of the
+                                    # form ('right',level) or ('nonassoc', level) or ('left',level)
+
+        self.UsedPrecedence = set() # Precedence rules that were actually used by the grammer.
+                                    # This is only used to provide error checking and to generate
+                                    # a warning about unused precedence rules.
+
+        self.Start = None           # Starting symbol for the grammar
+
+
+    def __len__(self):
+        return len(self.Productions)
+
+    def __getitem__(self, index):
+        return self.Productions[index]
+
+    # -----------------------------------------------------------------------------
+    # set_precedence()
+    #
+    # Sets the precedence for a given terminal. assoc is the associativity such as
+    # 'left','right', or 'nonassoc'.  level is a numeric level.
+    #
+    # -----------------------------------------------------------------------------
+
+    def set_precedence(self, term, assoc, level):
+        assert self.Productions == [None], 'Must call set_precedence() before add_production()'
+        if term in self.Precedence:
+            raise GrammarError('Precedence already specified for terminal %r' % term)
+        if assoc not in ['left', 'right', 'nonassoc']:
+            raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'")
+        self.Precedence[term] = (assoc, level)
+
+    # -----------------------------------------------------------------------------
+    # add_production()
+    #
+    # Given an action function, this function assembles a production rule and
+    # computes its precedence level.
+    #
+    # The production rule is supplied as a list of symbols.   For example,
+    # a rule such as 'expr : expr PLUS term' has a production name of 'expr' and
+    # symbols ['expr','PLUS','term'].
+    #
+    # Precedence is determined by the precedence of the right-most non-terminal
+    # or the precedence of a terminal specified by %prec.
+    #
+    # A variety of error checks are performed to make sure production symbols
+    # are valid and that %prec is used correctly.
+    # -----------------------------------------------------------------------------
+
+    def add_production(self, prodname, syms, func=None, file='', line=0):
+
+        if prodname in self.Terminals:
+            raise GrammarError('%s:%d: Illegal rule name %r. Already defined as a token' % (file, line, prodname))
+        if prodname == 'error':
+            raise GrammarError('%s:%d: Illegal rule name %r. error is a reserved word' % (file, line, prodname))
+        if not _is_identifier.match(prodname):
+            raise GrammarError('%s:%d: Illegal rule name %r' % (file, line, prodname))
+
+        # Look for literal tokens
+        for n, s in enumerate(syms):
+            if s[0] in "'\"":
+                try:
+                    c = eval(s)
+                    if (len(c) > 1):
+                        raise GrammarError('%s:%d: Literal token %s in rule %r may only be a single character' %
+                                           (file, line, s, prodname))
+                    if c not in self.Terminals:
+                        self.Terminals[c] = []
+                    syms[n] = c
+                    continue
+                except SyntaxError:
+                    pass
+            if not _is_identifier.match(s) and s != '%prec':
+                raise GrammarError('%s:%d: Illegal name %r in rule %r' % (file, line, s, prodname))
+
+        # Determine the precedence level
+        if '%prec' in syms:
+            if syms[-1] == '%prec':
+                raise GrammarError('%s:%d: Syntax error. Nothing follows %%prec' % (file, line))
+            if syms[-2] != '%prec':
+                raise GrammarError('%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule' %
+                                   (file, line))
+            precname = syms[-1]
+            prodprec = self.Precedence.get(precname)
+            if not prodprec:
+                raise GrammarError('%s:%d: Nothing known about the precedence of %r' % (file, line, precname))
+            else:
+                self.UsedPrecedence.add(precname)
+            del syms[-2:]     # Drop %prec from the rule
+        else:
+            # If no %prec, precedence is determined by the rightmost terminal symbol
+            precname = rightmost_terminal(syms, self.Terminals)
+            prodprec = self.Precedence.get(precname, ('right', 0))
+
+        # See if the rule is already in the rulemap
+        map = '%s -> %s' % (prodname, syms)
+        if map in self.Prodmap:
+            m = self.Prodmap[map]
+            raise GrammarError('%s:%d: Duplicate rule %s. ' % (file, line, m) +
+                               'Previous definition at %s:%d' % (m.file, m.line))
+
+        # From this point on, everything is valid.  Create a new Production instance
+        pnumber  = len(self.Productions)
+        if prodname not in self.Nonterminals:
+            self.Nonterminals[prodname] = []
+
+        # Add the production number to Terminals and Nonterminals
+        for t in syms:
+            if t in self.Terminals:
+                self.Terminals[t].append(pnumber)
+            else:
+                if t not in self.Nonterminals:
+                    self.Nonterminals[t] = []
+                self.Nonterminals[t].append(pnumber)
+
+        # Create a production and add it to the list of productions
+        p = Production(pnumber, prodname, syms, prodprec, func, file, line)
+        self.Productions.append(p)
+        self.Prodmap[map] = p
+
+        # Add to the global productions list
+        try:
+            self.Prodnames[prodname].append(p)
+        except KeyError:
+            self.Prodnames[prodname] = [p]
+
+    # -----------------------------------------------------------------------------
+    # set_start()
+    #
+    # Sets the starting symbol and creates the augmented grammar.  Production
+    # rule 0 is S' -> start where start is the start symbol.
+    # -----------------------------------------------------------------------------
+
+    def set_start(self, start=None):
+        if not start:
+            start = self.Productions[1].name
+        if start not in self.Nonterminals:
+            raise GrammarError('start symbol %s undefined' % start)
+        self.Productions[0] = Production(0, "S'", [start])
+        self.Nonterminals[start].append(0)
+        self.Start = start
+
+    # -----------------------------------------------------------------------------
+    # find_unreachable()
+    #
+    # Find all of the nonterminal symbols that can't be reached from the starting
+    # symbol.  Returns a list of nonterminals that can't be reached.
+    # -----------------------------------------------------------------------------
+
+    def find_unreachable(self):
+
+        # Mark all symbols that are reachable from a symbol s
+        def mark_reachable_from(s):
+            if s in reachable:
+                return
+            reachable.add(s)
+            for p in self.Prodnames.get(s, []):
+                for r in p.prod:
+                    mark_reachable_from(r)
+
+        reachable = set()
+        mark_reachable_from(self.Productions[0].prod[0])
+        return [s for s in self.Nonterminals if s not in reachable]
+
+    # -----------------------------------------------------------------------------
+    # infinite_cycles()
+    #
+    # This function looks at the various parsing rules and tries to detect
+    # infinite recursion cycles (grammar rules where there is no possible way
+    # to derive a string of only terminals).
+    # -----------------------------------------------------------------------------
+
+    def infinite_cycles(self):
+        terminates = {}
+
+        # Terminals:
+        for t in self.Terminals:
+            terminates[t] = True
+
+        terminates['$end'] = True
+
+        # Nonterminals:
+
+        # Initialize to false:
+        for n in self.Nonterminals:
+            terminates[n] = False
+
+        # Then propagate termination until no change:
+        while True:
+            some_change = False
+            for (n, pl) in self.Prodnames.items():
+                # Nonterminal n terminates iff any of its productions terminates.
+                for p in pl:
+                    # Production p terminates iff all of its rhs symbols terminate.
+                    for s in p.prod:
+                        if not terminates[s]:
+                            # The symbol s does not terminate,
+                            # so production p does not terminate.
+                            p_terminates = False
+                            break
+                    else:
+                        # didn't break from the loop,
+                        # so every symbol s terminates
+                        # so production p terminates.
+                        p_terminates = True
+
+                    if p_terminates:
+                        # symbol n terminates!
+                        if not terminates[n]:
+                            terminates[n] = True
+                            some_change = True
+                        # Don't need to consider any more productions for this n.
+                        break
+
+            if not some_change:
+                break
+
+        infinite = []
+        for (s, term) in terminates.items():
+            if not term:
+                if s not in self.Prodnames and s not in self.Terminals and s != 'error':
+                    # s is used-but-not-defined, and we've already warned of that,
+                    # so it would be overkill to say that it's also non-terminating.
+                    pass
+                else:
+                    infinite.append(s)
+
+        return infinite
+
+    # -----------------------------------------------------------------------------
+    # undefined_symbols()
+    #
+    # Find all symbols that were used the grammar, but not defined as tokens or
+    # grammar rules.  Returns a list of tuples (sym, prod) where sym in the symbol
+    # and prod is the production where the symbol was used.
+    # -----------------------------------------------------------------------------
+    def undefined_symbols(self):
+        result = []
+        for p in self.Productions:
+            if not p:
+                continue
+
+            for s in p.prod:
+                if s not in self.Prodnames and s not in self.Terminals and s != 'error':
+                    result.append((s, p))
+        return result
+
+    # -----------------------------------------------------------------------------
+    # unused_terminals()
+    #
+    # Find all terminals that were defined, but not used by the grammar.  Returns
+    # a list of all symbols.
+    # -----------------------------------------------------------------------------
+    def unused_terminals(self):
+        unused_tok = []
+        for s, v in self.Terminals.items():
+            if s != 'error' and not v:
+                unused_tok.append(s)
+
+        return unused_tok
+
+    # ------------------------------------------------------------------------------
+    # unused_rules()
+    #
+    # Find all grammar rules that were defined,  but not used (maybe not reachable)
+    # Returns a list of productions.
+    # ------------------------------------------------------------------------------
+
+    def unused_rules(self):
+        unused_prod = []
+        for s, v in self.Nonterminals.items():
+            if not v:
+                p = self.Prodnames[s][0]
+                unused_prod.append(p)
+        return unused_prod
+
+    # -----------------------------------------------------------------------------
+    # unused_precedence()
+    #
+    # Returns a list of tuples (term,precedence) corresponding to precedence
+    # rules that were never used by the grammar.  term is the name of the terminal
+    # on which precedence was applied and precedence is a string such as 'left' or
+    # 'right' corresponding to the type of precedence.
+    # -----------------------------------------------------------------------------
+
+    def unused_precedence(self):
+        unused = []
+        for termname in self.Precedence:
+            if not (termname in self.Terminals or termname in self.UsedPrecedence):
+                unused.append((termname, self.Precedence[termname][0]))
+
+        return unused
+
+    # -------------------------------------------------------------------------
+    # _first()
+    #
+    # Compute the value of FIRST1(beta) where beta is a tuple of symbols.
+    #
+    # During execution of compute_first1, the result may be incomplete.
+    # Afterward (e.g., when called from compute_follow()), it will be complete.
+    # -------------------------------------------------------------------------
+    def _first(self, beta):
+
+        # We are computing First(x1,x2,x3,...,xn)
+        result = []
+        for x in beta:
+            x_produces_empty = False
+
+            # Add all the non- symbols of First[x] to the result.
+            for f in self.First[x]:
+                if f == '':
+                    x_produces_empty = True
+                else:
+                    if f not in result:
+                        result.append(f)
+
+            if x_produces_empty:
+                # We have to consider the next x in beta,
+                # i.e. stay in the loop.
+                pass
+            else:
+                # We don't have to consider any further symbols in beta.
+                break
+        else:
+            # There was no 'break' from the loop,
+            # so x_produces_empty was true for all x in beta,
+            # so beta produces empty as well.
+            result.append('')
+
+        return result
+
+    # -------------------------------------------------------------------------
+    # compute_first()
+    #
+    # Compute the value of FIRST1(X) for all symbols
+    # -------------------------------------------------------------------------
+    def compute_first(self):
+        if self.First:
+            return self.First
+
+        # Terminals:
+        for t in self.Terminals:
+            self.First[t] = [t]
+
+        self.First['$end'] = ['$end']
+
+        # Nonterminals:
+
+        # Initialize to the empty set:
+        for n in self.Nonterminals:
+            self.First[n] = []
+
+        # Then propagate symbols until no change:
+        while True:
+            some_change = False
+            for n in self.Nonterminals:
+                for p in self.Prodnames[n]:
+                    for f in self._first(p.prod):
+                        if f not in self.First[n]:
+                            self.First[n].append(f)
+                            some_change = True
+            if not some_change:
+                break
+
+        return self.First
+
+    # ---------------------------------------------------------------------
+    # compute_follow()
+    #
+    # Computes all of the follow sets for every non-terminal symbol.  The
+    # follow set is the set of all symbols that might follow a given
+    # non-terminal.  See the Dragon book, 2nd Ed. p. 189.
+    # ---------------------------------------------------------------------
+    def compute_follow(self, start=None):
+        # If already computed, return the result
+        if self.Follow:
+            return self.Follow
+
+        # If first sets not computed yet, do that first.
+        if not self.First:
+            self.compute_first()
+
+        # Add '$end' to the follow list of the start symbol
+        for k in self.Nonterminals:
+            self.Follow[k] = []
+
+        if not start:
+            start = self.Productions[1].name
+
+        self.Follow[start] = ['$end']
+
+        while True:
+            didadd = False
+            for p in self.Productions[1:]:
+                # Here is the production set
+                for i, B in enumerate(p.prod):
+                    if B in self.Nonterminals:
+                        # Okay. We got a non-terminal in a production
+                        fst = self._first(p.prod[i+1:])
+                        hasempty = False
+                        for f in fst:
+                            if f != '' and f not in self.Follow[B]:
+                                self.Follow[B].append(f)
+                                didadd = True
+                            if f == '':
+                                hasempty = True
+                        if hasempty or i == (len(p.prod)-1):
+                            # Add elements of follow(a) to follow(b)
+                            for f in self.Follow[p.name]:
+                                if f not in self.Follow[B]:
+                                    self.Follow[B].append(f)
+                                    didadd = True
+            if not didadd:
+                break
+        return self.Follow
+
+
+    # -----------------------------------------------------------------------------
+    # build_lritems()
+    #
+    # This function walks the list of productions and builds a complete set of the
+    # LR items.  The LR items are stored in two ways:  First, they are uniquely
+    # numbered and placed in the list _lritems.  Second, a linked list of LR items
+    # is built for each production.  For example:
+    #
+    #   E -> E PLUS E
+    #
+    # Creates the list
+    #
+    #  [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ]
+    # -----------------------------------------------------------------------------
+
+    def build_lritems(self):
+        for p in self.Productions:
+            lastlri = p
+            i = 0
+            lr_items = []
+            while True:
+                if i > len(p):
+                    lri = None
+                else:
+                    lri = LRItem(p, i)
+                    # Precompute the list of productions immediately following
+                    try:
+                        lri.lr_after = self.Prodnames[lri.prod[i+1]]
+                    except (IndexError, KeyError):
+                        lri.lr_after = []
+                    try:
+                        lri.lr_before = lri.prod[i-1]
+                    except IndexError:
+                        lri.lr_before = None
+
+                lastlri.lr_next = lri
+                if not lri:
+                    break
+                lr_items.append(lri)
+                lastlri = lri
+                i += 1
+            p.lr_items = lr_items
+
+# -----------------------------------------------------------------------------
+#                            == Class LRTable ==
+#
+# This basic class represents a basic table of LR parsing information.
+# Methods for generating the tables are not defined here.  They are defined
+# in the derived class LRGeneratedTable.
+# -----------------------------------------------------------------------------
+
+class VersionError(YaccError):
+    pass
+
+class LRTable(object):
+    def __init__(self):
+        self.lr_action = None
+        self.lr_goto = None
+        self.lr_productions = None
+        self.lr_method = None
+
+    def read_table(self, module):
+        if isinstance(module, types.ModuleType):
+            parsetab = module
+        else:
+            exec('import %s' % module)
+            parsetab = sys.modules[module]
+
+        if parsetab._tabversion != __tabversion__:
+            raise VersionError('yacc table file version is out of date')
+
+        self.lr_action = parsetab._lr_action
+        self.lr_goto = parsetab._lr_goto
+
+        self.lr_productions = []
+        for p in parsetab._lr_productions:
+            self.lr_productions.append(MiniProduction(*p))
+
+        self.lr_method = parsetab._lr_method
+        return parsetab._lr_signature
+
+    def read_pickle(self, filename):
+        try:
+            import cPickle as pickle
+        except ImportError:
+            import pickle
+
+        if not os.path.exists(filename):
+          raise ImportError
+
+        in_f = open(filename, 'rb')
+
+        tabversion = pickle.load(in_f)
+        if tabversion != __tabversion__:
+            raise VersionError('yacc table file version is out of date')
+        self.lr_method = pickle.load(in_f)
+        signature      = pickle.load(in_f)
+        self.lr_action = pickle.load(in_f)
+        self.lr_goto   = pickle.load(in_f)
+        productions    = pickle.load(in_f)
+
+        self.lr_productions = []
+        for p in productions:
+            self.lr_productions.append(MiniProduction(*p))
+
+        in_f.close()
+        return signature
+
+    # Bind all production function names to callable objects in pdict
+    def bind_callables(self, pdict):
+        for p in self.lr_productions:
+            p.bind(pdict)
+
+
+# -----------------------------------------------------------------------------
+#                           === LR Generator ===
+#
+# The following classes and functions are used to generate LR parsing tables on
+# a grammar.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# digraph()
+# traverse()
+#
+# The following two functions are used to compute set valued functions
+# of the form:
+#
+#     F(x) = F'(x) U U{F(y) | x R y}
+#
+# This is used to compute the values of Read() sets as well as FOLLOW sets
+# in LALR(1) generation.
+#
+# Inputs:  X    - An input set
+#          R    - A relation
+#          FP   - Set-valued function
+# ------------------------------------------------------------------------------
+
+def digraph(X, R, FP):
+    N = {}
+    for x in X:
+        N[x] = 0
+    stack = []
+    F = {}
+    for x in X:
+        if N[x] == 0:
+            traverse(x, N, stack, F, X, R, FP)
+    return F
+
+def traverse(x, N, stack, F, X, R, FP):
+    stack.append(x)
+    d = len(stack)
+    N[x] = d
+    F[x] = FP(x)             # F(X) <- F'(x)
+
+    rel = R(x)               # Get y's related to x
+    for y in rel:
+        if N[y] == 0:
+            traverse(y, N, stack, F, X, R, FP)
+        N[x] = min(N[x], N[y])
+        for a in F.get(y, []):
+            if a not in F[x]:
+                F[x].append(a)
+    if N[x] == d:
+        N[stack[-1]] = MAXINT
+        F[stack[-1]] = F[x]
+        element = stack.pop()
+        while element != x:
+            N[stack[-1]] = MAXINT
+            F[stack[-1]] = F[x]
+            element = stack.pop()
+
+class LALRError(YaccError):
+    pass
+
+# -----------------------------------------------------------------------------
+#                             == LRGeneratedTable ==
+#
+# This class implements the LR table generation algorithm.  There are no
+# public methods except for write()
+# -----------------------------------------------------------------------------
+
+class LRGeneratedTable(LRTable):
+    def __init__(self, grammar, method='LALR', log=None):
+        if method not in ['SLR', 'LALR']:
+            raise LALRError('Unsupported method %s' % method)
+
+        self.grammar = grammar
+        self.lr_method = method
+
+        # Set up the logger
+        if not log:
+            log = NullLogger()
+        self.log = log
+
+        # Internal attributes
+        self.lr_action     = {}        # Action table
+        self.lr_goto       = {}        # Goto table
+        self.lr_productions  = grammar.Productions    # Copy of grammar Production array
+        self.lr_goto_cache = {}        # Cache of computed gotos
+        self.lr0_cidhash   = {}        # Cache of closures
+
+        self._add_count    = 0         # Internal counter used to detect cycles
+
+        # Diagonistic information filled in by the table generator
+        self.sr_conflict   = 0
+        self.rr_conflict   = 0
+        self.conflicts     = []        # List of conflicts
+
+        self.sr_conflicts  = []
+        self.rr_conflicts  = []
+
+        # Build the tables
+        self.grammar.build_lritems()
+        self.grammar.compute_first()
+        self.grammar.compute_follow()
+        self.lr_parse_table()
+
+    # Compute the LR(0) closure operation on I, where I is a set of LR(0) items.
+
+    def lr0_closure(self, I):
+        self._add_count += 1
+
+        # Add everything in I to J
+        J = I[:]
+        didadd = True
+        while didadd:
+            didadd = False
+            for j in J:
+                for x in j.lr_after:
+                    if getattr(x, 'lr0_added', 0) == self._add_count:
+                        continue
+                    # Add B --> .G to J
+                    J.append(x.lr_next)
+                    x.lr0_added = self._add_count
+                    didadd = True
+
+        return J
+
+    # Compute the LR(0) goto function goto(I,X) where I is a set
+    # of LR(0) items and X is a grammar symbol.   This function is written
+    # in a way that guarantees uniqueness of the generated goto sets
+    # (i.e. the same goto set will never be returned as two different Python
+    # objects).  With uniqueness, we can later do fast set comparisons using
+    # id(obj) instead of element-wise comparison.
+
+    def lr0_goto(self, I, x):
+        # First we look for a previously cached entry
+        g = self.lr_goto_cache.get((id(I), x))
+        if g:
+            return g
+
+        # Now we generate the goto set in a way that guarantees uniqueness
+        # of the result
+
+        s = self.lr_goto_cache.get(x)
+        if not s:
+            s = {}
+            self.lr_goto_cache[x] = s
+
+        gs = []
+        for p in I:
+            n = p.lr_next
+            if n and n.lr_before == x:
+                s1 = s.get(id(n))
+                if not s1:
+                    s1 = {}
+                    s[id(n)] = s1
+                gs.append(n)
+                s = s1
+        g = s.get('$end')
+        if not g:
+            if gs:
+                g = self.lr0_closure(gs)
+                s['$end'] = g
+            else:
+                s['$end'] = gs
+        self.lr_goto_cache[(id(I), x)] = g
+        return g
+
+    # Compute the LR(0) sets of item function
+    def lr0_items(self):
+        C = [self.lr0_closure([self.grammar.Productions[0].lr_next])]
+        i = 0
+        for I in C:
+            self.lr0_cidhash[id(I)] = i
+            i += 1
+
+        # Loop over the items in C and each grammar symbols
+        i = 0
+        while i < len(C):
+            I = C[i]
+            i += 1
+
+            # Collect all of the symbols that could possibly be in the goto(I,X) sets
+            asyms = {}
+            for ii in I:
+                for s in ii.usyms:
+                    asyms[s] = None
+
+            for x in asyms:
+                g = self.lr0_goto(I, x)
+                if not g or id(g) in self.lr0_cidhash:
+                    continue
+                self.lr0_cidhash[id(g)] = len(C)
+                C.append(g)
+
+        return C
+
+    # -----------------------------------------------------------------------------
+    #                       ==== LALR(1) Parsing ====
+    #
+    # LALR(1) parsing is almost exactly the same as SLR except that instead of
+    # relying upon Follow() sets when performing reductions, a more selective
+    # lookahead set that incorporates the state of the LR(0) machine is utilized.
+    # Thus, we mainly just have to focus on calculating the lookahead sets.
+    #
+    # The method used here is due to DeRemer and Pennelo (1982).
+    #
+    # DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1)
+    #     Lookahead Sets", ACM Transactions on Programming Languages and Systems,
+    #     Vol. 4, No. 4, Oct. 1982, pp. 615-649
+    #
+    # Further details can also be found in:
+    #
+    #  J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing",
+    #      McGraw-Hill Book Company, (1985).
+    #
+    # -----------------------------------------------------------------------------
+
+    # -----------------------------------------------------------------------------
+    # compute_nullable_nonterminals()
+    #
+    # Creates a dictionary containing all of the non-terminals that might produce
+    # an empty production.
+    # -----------------------------------------------------------------------------
+
+    def compute_nullable_nonterminals(self):
+        nullable = set()
+        num_nullable = 0
+        while True:
+            for p in self.grammar.Productions[1:]:
+                if p.len == 0:
+                    nullable.add(p.name)
+                    continue
+                for t in p.prod:
+                    if t not in nullable:
+                        break
+                else:
+                    nullable.add(p.name)
+            if len(nullable) == num_nullable:
+                break
+            num_nullable = len(nullable)
+        return nullable
+
+    # -----------------------------------------------------------------------------
+    # find_nonterminal_trans(C)
+    #
+    # Given a set of LR(0) items, this functions finds all of the non-terminal
+    # transitions.    These are transitions in which a dot appears immediately before
+    # a non-terminal.   Returns a list of tuples of the form (state,N) where state
+    # is the state number and N is the nonterminal symbol.
+    #
+    # The input C is the set of LR(0) items.
+    # -----------------------------------------------------------------------------
+
+    def find_nonterminal_transitions(self, C):
+        trans = []
+        for stateno, state in enumerate(C):
+            for p in state:
+                if p.lr_index < p.len - 1:
+                    t = (stateno, p.prod[p.lr_index+1])
+                    if t[1] in self.grammar.Nonterminals:
+                        if t not in trans:
+                            trans.append(t)
+        return trans
+
+    # -----------------------------------------------------------------------------
+    # dr_relation()
+    #
+    # Computes the DR(p,A) relationships for non-terminal transitions.  The input
+    # is a tuple (state,N) where state is a number and N is a nonterminal symbol.
+    #
+    # Returns a list of terminals.
+    # -----------------------------------------------------------------------------
+
+    def dr_relation(self, C, trans, nullable):
+        state, N = trans
+        terms = []
+
+        g = self.lr0_goto(C[state], N)
+        for p in g:
+            if p.lr_index < p.len - 1:
+                a = p.prod[p.lr_index+1]
+                if a in self.grammar.Terminals:
+                    if a not in terms:
+                        terms.append(a)
+
+        # This extra bit is to handle the start state
+        if state == 0 and N == self.grammar.Productions[0].prod[0]:
+            terms.append('$end')
+
+        return terms
+
+    # -----------------------------------------------------------------------------
+    # reads_relation()
+    #
+    # Computes the READS() relation (p,A) READS (t,C).
+    # -----------------------------------------------------------------------------
+
+    def reads_relation(self, C, trans, empty):
+        # Look for empty transitions
+        rel = []
+        state, N = trans
+
+        g = self.lr0_goto(C[state], N)
+        j = self.lr0_cidhash.get(id(g), -1)
+        for p in g:
+            if p.lr_index < p.len - 1:
+                a = p.prod[p.lr_index + 1]
+                if a in empty:
+                    rel.append((j, a))
+
+        return rel
+
+    # -----------------------------------------------------------------------------
+    # compute_lookback_includes()
+    #
+    # Determines the lookback and includes relations
+    #
+    # LOOKBACK:
+    #
+    # This relation is determined by running the LR(0) state machine forward.
+    # For example, starting with a production "N : . A B C", we run it forward
+    # to obtain "N : A B C ."   We then build a relationship between this final
+    # state and the starting state.   These relationships are stored in a dictionary
+    # lookdict.
+    #
+    # INCLUDES:
+    #
+    # Computes the INCLUDE() relation (p,A) INCLUDES (p',B).
+    #
+    # This relation is used to determine non-terminal transitions that occur
+    # inside of other non-terminal transition states.   (p,A) INCLUDES (p', B)
+    # if the following holds:
+    #
+    #       B -> LAT, where T -> epsilon and p' -L-> p
+    #
+    # L is essentially a prefix (which may be empty), T is a suffix that must be
+    # able to derive an empty string.  State p' must lead to state p with the string L.
+    #
+    # -----------------------------------------------------------------------------
+
+    def compute_lookback_includes(self, C, trans, nullable):
+        lookdict = {}          # Dictionary of lookback relations
+        includedict = {}       # Dictionary of include relations
+
+        # Make a dictionary of non-terminal transitions
+        dtrans = {}
+        for t in trans:
+            dtrans[t] = 1
+
+        # Loop over all transitions and compute lookbacks and includes
+        for state, N in trans:
+            lookb = []
+            includes = []
+            for p in C[state]:
+                if p.name != N:
+                    continue
+
+                # Okay, we have a name match.  We now follow the production all the way
+                # through the state machine until we get the . on the right hand side
+
+                lr_index = p.lr_index
+                j = state
+                while lr_index < p.len - 1:
+                    lr_index = lr_index + 1
+                    t = p.prod[lr_index]
+
+                    # Check to see if this symbol and state are a non-terminal transition
+                    if (j, t) in dtrans:
+                        # Yes.  Okay, there is some chance that this is an includes relation
+                        # the only way to know for certain is whether the rest of the
+                        # production derives empty
+
+                        li = lr_index + 1
+                        while li < p.len:
+                            if p.prod[li] in self.grammar.Terminals:
+                                break      # No forget it
+                            if p.prod[li] not in nullable:
+                                break
+                            li = li + 1
+                        else:
+                            # Appears to be a relation between (j,t) and (state,N)
+                            includes.append((j, t))
+
+                    g = self.lr0_goto(C[j], t)               # Go to next set
+                    j = self.lr0_cidhash.get(id(g), -1)      # Go to next state
+
+                # When we get here, j is the final state, now we have to locate the production
+                for r in C[j]:
+                    if r.name != p.name:
+                        continue
+                    if r.len != p.len:
+                        continue
+                    i = 0
+                    # This look is comparing a production ". A B C" with "A B C ."
+                    while i < r.lr_index:
+                        if r.prod[i] != p.prod[i+1]:
+                            break
+                        i = i + 1
+                    else:
+                        lookb.append((j, r))
+            for i in includes:
+                if i not in includedict:
+                    includedict[i] = []
+                includedict[i].append((state, N))
+            lookdict[(state, N)] = lookb
+
+        return lookdict, includedict
+
+    # -----------------------------------------------------------------------------
+    # compute_read_sets()
+    #
+    # Given a set of LR(0) items, this function computes the read sets.
+    #
+    # Inputs:  C        =  Set of LR(0) items
+    #          ntrans   = Set of nonterminal transitions
+    #          nullable = Set of empty transitions
+    #
+    # Returns a set containing the read sets
+    # -----------------------------------------------------------------------------
+
+    def compute_read_sets(self, C, ntrans, nullable):
+        FP = lambda x: self.dr_relation(C, x, nullable)
+        R =  lambda x: self.reads_relation(C, x, nullable)
+        F = digraph(ntrans, R, FP)
+        return F
+
+    # -----------------------------------------------------------------------------
+    # compute_follow_sets()
+    #
+    # Given a set of LR(0) items, a set of non-terminal transitions, a readset,
+    # and an include set, this function computes the follow sets
+    #
+    # Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)}
+    #
+    # Inputs:
+    #            ntrans     = Set of nonterminal transitions
+    #            readsets   = Readset (previously computed)
+    #            inclsets   = Include sets (previously computed)
+    #
+    # Returns a set containing the follow sets
+    # -----------------------------------------------------------------------------
+
+    def compute_follow_sets(self, ntrans, readsets, inclsets):
+        FP = lambda x: readsets[x]
+        R  = lambda x: inclsets.get(x, [])
+        F = digraph(ntrans, R, FP)
+        return F
+
+    # -----------------------------------------------------------------------------
+    # add_lookaheads()
+    #
+    # Attaches the lookahead symbols to grammar rules.
+    #
+    # Inputs:    lookbacks         -  Set of lookback relations
+    #            followset         -  Computed follow set
+    #
+    # This function directly attaches the lookaheads to productions contained
+    # in the lookbacks set
+    # -----------------------------------------------------------------------------
+
+    def add_lookaheads(self, lookbacks, followset):
+        for trans, lb in lookbacks.items():
+            # Loop over productions in lookback
+            for state, p in lb:
+                if state not in p.lookaheads:
+                    p.lookaheads[state] = []
+                f = followset.get(trans, [])
+                for a in f:
+                    if a not in p.lookaheads[state]:
+                        p.lookaheads[state].append(a)
+
+    # -----------------------------------------------------------------------------
+    # add_lalr_lookaheads()
+    #
+    # This function does all of the work of adding lookahead information for use
+    # with LALR parsing
+    # -----------------------------------------------------------------------------
+
+    def add_lalr_lookaheads(self, C):
+        # Determine all of the nullable nonterminals
+        nullable = self.compute_nullable_nonterminals()
+
+        # Find all non-terminal transitions
+        trans = self.find_nonterminal_transitions(C)
+
+        # Compute read sets
+        readsets = self.compute_read_sets(C, trans, nullable)
+
+        # Compute lookback/includes relations
+        lookd, included = self.compute_lookback_includes(C, trans, nullable)
+
+        # Compute LALR FOLLOW sets
+        followsets = self.compute_follow_sets(trans, readsets, included)
+
+        # Add all of the lookaheads
+        self.add_lookaheads(lookd, followsets)
+
+    # -----------------------------------------------------------------------------
+    # lr_parse_table()
+    #
+    # This function constructs the parse tables for SLR or LALR
+    # -----------------------------------------------------------------------------
+    def lr_parse_table(self):
+        Productions = self.grammar.Productions
+        Precedence  = self.grammar.Precedence
+        goto   = self.lr_goto         # Goto array
+        action = self.lr_action       # Action array
+        log    = self.log             # Logger for output
+
+        actionp = {}                  # Action production array (temporary)
+
+        log.info('Parsing method: %s', self.lr_method)
+
+        # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items
+        # This determines the number of states
+
+        C = self.lr0_items()
+
+        if self.lr_method == 'LALR':
+            self.add_lalr_lookaheads(C)
+
+        # Build the parser table, state by state
+        st = 0
+        for I in C:
+            # Loop over each production in I
+            actlist = []              # List of actions
+            st_action  = {}
+            st_actionp = {}
+            st_goto    = {}
+            log.info('')
+            log.info('state %d', st)
+            log.info('')
+            for p in I:
+                log.info('    (%d) %s', p.number, p)
+            log.info('')
+
+            for p in I:
+                    if p.len == p.lr_index + 1:
+                        if p.name == "S'":
+                            # Start symbol. Accept!
+                            st_action['$end'] = 0
+                            st_actionp['$end'] = p
+                        else:
+                            # We are at the end of a production.  Reduce!
+                            if self.lr_method == 'LALR':
+                                laheads = p.lookaheads[st]
+                            else:
+                                laheads = self.grammar.Follow[p.name]
+                            for a in laheads:
+                                actlist.append((a, p, 'reduce using rule %d (%s)' % (p.number, p)))
+                                r = st_action.get(a)
+                                if r is not None:
+                                    # Whoa. Have a shift/reduce or reduce/reduce conflict
+                                    if r > 0:
+                                        # Need to decide on shift or reduce here
+                                        # By default we favor shifting. Need to add
+                                        # some precedence rules here.
+
+                                        # Shift precedence comes from the token
+                                        sprec, slevel = Precedence.get(a, ('right', 0))
+
+                                        # Reduce precedence comes from rule being reduced (p)
+                                        rprec, rlevel = Productions[p.number].prec
+
+                                        if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')):
+                                            # We really need to reduce here.
+                                            st_action[a] = -p.number
+                                            st_actionp[a] = p
+                                            if not slevel and not rlevel:
+                                                log.info('  ! shift/reduce conflict for %s resolved as reduce', a)
+                                                self.sr_conflicts.append((st, a, 'reduce'))
+                                            Productions[p.number].reduced += 1
+                                        elif (slevel == rlevel) and (rprec == 'nonassoc'):
+                                            st_action[a] = None
+                                        else:
+                                            # Hmmm. Guess we'll keep the shift
+                                            if not rlevel:
+                                                log.info('  ! shift/reduce conflict for %s resolved as shift', a)
+                                                self.sr_conflicts.append((st, a, 'shift'))
+                                    elif r < 0:
+                                        # Reduce/reduce conflict.   In this case, we favor the rule
+                                        # that was defined first in the grammar file
+                                        oldp = Productions[-r]
+                                        pp = Productions[p.number]
+                                        if oldp.line > pp.line:
+                                            st_action[a] = -p.number
+                                            st_actionp[a] = p
+                                            chosenp, rejectp = pp, oldp
+                                            Productions[p.number].reduced += 1
+                                            Productions[oldp.number].reduced -= 1
+                                        else:
+                                            chosenp, rejectp = oldp, pp
+                                        self.rr_conflicts.append((st, chosenp, rejectp))
+                                        log.info('  ! reduce/reduce conflict for %s resolved using rule %d (%s)',
+                                                 a, st_actionp[a].number, st_actionp[a])
+                                    else:
+                                        raise LALRError('Unknown conflict in state %d' % st)
+                                else:
+                                    st_action[a] = -p.number
+                                    st_actionp[a] = p
+                                    Productions[p.number].reduced += 1
+                    else:
+                        i = p.lr_index
+                        a = p.prod[i+1]       # Get symbol right after the "."
+                        if a in self.grammar.Terminals:
+                            g = self.lr0_goto(I, a)
+                            j = self.lr0_cidhash.get(id(g), -1)
+                            if j >= 0:
+                                # We are in a shift state
+                                actlist.append((a, p, 'shift and go to state %d' % j))
+                                r = st_action.get(a)
+                                if r is not None:
+                                    # Whoa have a shift/reduce or shift/shift conflict
+                                    if r > 0:
+                                        if r != j:
+                                            raise LALRError('Shift/shift conflict in state %d' % st)
+                                    elif r < 0:
+                                        # Do a precedence check.
+                                        #   -  if precedence of reduce rule is higher, we reduce.
+                                        #   -  if precedence of reduce is same and left assoc, we reduce.
+                                        #   -  otherwise we shift
+
+                                        # Shift precedence comes from the token
+                                        sprec, slevel = Precedence.get(a, ('right', 0))
+
+                                        # Reduce precedence comes from the rule that could have been reduced
+                                        rprec, rlevel = Productions[st_actionp[a].number].prec
+
+                                        if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')):
+                                            # We decide to shift here... highest precedence to shift
+                                            Productions[st_actionp[a].number].reduced -= 1
+                                            st_action[a] = j
+                                            st_actionp[a] = p
+                                            if not rlevel:
+                                                log.info('  ! shift/reduce conflict for %s resolved as shift', a)
+                                                self.sr_conflicts.append((st, a, 'shift'))
+                                        elif (slevel == rlevel) and (rprec == 'nonassoc'):
+                                            st_action[a] = None
+                                        else:
+                                            # Hmmm. Guess we'll keep the reduce
+                                            if not slevel and not rlevel:
+                                                log.info('  ! shift/reduce conflict for %s resolved as reduce', a)
+                                                self.sr_conflicts.append((st, a, 'reduce'))
+
+                                    else:
+                                        raise LALRError('Unknown conflict in state %d' % st)
+                                else:
+                                    st_action[a] = j
+                                    st_actionp[a] = p
+
+            # Print the actions associated with each terminal
+            _actprint = {}
+            for a, p, m in actlist:
+                if a in st_action:
+                    if p is st_actionp[a]:
+                        log.info('    %-15s %s', a, m)
+                        _actprint[(a, m)] = 1
+            log.info('')
+            # Print the actions that were not used. (debugging)
+            not_used = 0
+            for a, p, m in actlist:
+                if a in st_action:
+                    if p is not st_actionp[a]:
+                        if not (a, m) in _actprint:
+                            log.debug('  ! %-15s [ %s ]', a, m)
+                            not_used = 1
+                            _actprint[(a, m)] = 1
+            if not_used:
+                log.debug('')
+
+            # Construct the goto table for this state
+
+            nkeys = {}
+            for ii in I:
+                for s in ii.usyms:
+                    if s in self.grammar.Nonterminals:
+                        nkeys[s] = None
+            for n in nkeys:
+                g = self.lr0_goto(I, n)
+                j = self.lr0_cidhash.get(id(g), -1)
+                if j >= 0:
+                    st_goto[n] = j
+                    log.info('    %-30s shift and go to state %d', n, j)
+
+            action[st] = st_action
+            actionp[st] = st_actionp
+            goto[st] = st_goto
+            st += 1
+
+    # -----------------------------------------------------------------------------
+    # write()
+    #
+    # This function writes the LR parsing tables to a file
+    # -----------------------------------------------------------------------------
+
+    def write_table(self, tabmodule, outputdir='', signature=''):
+        if isinstance(tabmodule, types.ModuleType):
+            raise IOError("Won't overwrite existing tabmodule")
+
+        basemodulename = tabmodule.split('.')[-1]
+        filename = os.path.join(outputdir, basemodulename) + '.py'
+        try:
+            f = open(filename, 'w')
+
+            f.write('''
+# %s
+# This file is automatically generated. Do not edit.
+# pylint: disable=W,C,R
+_tabversion = %r
+
+_lr_method = %r
+
+_lr_signature = %r
+    ''' % (os.path.basename(filename), __tabversion__, self.lr_method, signature))
+
+            # Change smaller to 0 to go back to original tables
+            smaller = 1
+
+            # Factor out names to try and make smaller
+            if smaller:
+                items = {}
+
+                for s, nd in self.lr_action.items():
+                    for name, v in nd.items():
+                        i = items.get(name)
+                        if not i:
+                            i = ([], [])
+                            items[name] = i
+                        i[0].append(s)
+                        i[1].append(v)
+
+                f.write('\n_lr_action_items = {')
+                for k, v in items.items():
+                    f.write('%r:([' % k)
+                    for i in v[0]:
+                        f.write('%r,' % i)
+                    f.write('],[')
+                    for i in v[1]:
+                        f.write('%r,' % i)
+
+                    f.write(']),')
+                f.write('}\n')
+
+                f.write('''
+_lr_action = {}
+for _k, _v in _lr_action_items.items():
+   for _x,_y in zip(_v[0],_v[1]):
+      if not _x in _lr_action:  _lr_action[_x] = {}
+      _lr_action[_x][_k] = _y
+del _lr_action_items
+''')
+
+            else:
+                f.write('\n_lr_action = { ')
+                for k, v in self.lr_action.items():
+                    f.write('(%r,%r):%r,' % (k[0], k[1], v))
+                f.write('}\n')
+
+            if smaller:
+                # Factor out names to try and make smaller
+                items = {}
+
+                for s, nd in self.lr_goto.items():
+                    for name, v in nd.items():
+                        i = items.get(name)
+                        if not i:
+                            i = ([], [])
+                            items[name] = i
+                        i[0].append(s)
+                        i[1].append(v)
+
+                f.write('\n_lr_goto_items = {')
+                for k, v in items.items():
+                    f.write('%r:([' % k)
+                    for i in v[0]:
+                        f.write('%r,' % i)
+                    f.write('],[')
+                    for i in v[1]:
+                        f.write('%r,' % i)
+
+                    f.write(']),')
+                f.write('}\n')
+
+                f.write('''
+_lr_goto = {}
+for _k, _v in _lr_goto_items.items():
+   for _x, _y in zip(_v[0], _v[1]):
+       if not _x in _lr_goto: _lr_goto[_x] = {}
+       _lr_goto[_x][_k] = _y
+del _lr_goto_items
+''')
+            else:
+                f.write('\n_lr_goto = { ')
+                for k, v in self.lr_goto.items():
+                    f.write('(%r,%r):%r,' % (k[0], k[1], v))
+                f.write('}\n')
+
+            # Write production table
+            f.write('_lr_productions = [\n')
+            for p in self.lr_productions:
+                if p.func:
+                    f.write('  (%r,%r,%d,%r,%r,%d),\n' % (p.str, p.name, p.len,
+                                                          p.func, os.path.basename(p.file), p.line))
+                else:
+                    f.write('  (%r,%r,%d,None,None,None),\n' % (str(p), p.name, p.len))
+            f.write(']\n')
+            f.close()
+
+        except IOError as e:
+            raise
+
+
+    # -----------------------------------------------------------------------------
+    # pickle_table()
+    #
+    # This function pickles the LR parsing tables to a supplied file object
+    # -----------------------------------------------------------------------------
+
+    def pickle_table(self, filename, signature=''):
+        try:
+            import cPickle as pickle
+        except ImportError:
+            import pickle
+        with open(filename, 'wb') as outf:
+            pickle.dump(__tabversion__, outf, pickle_protocol)
+            pickle.dump(self.lr_method, outf, pickle_protocol)
+            pickle.dump(signature, outf, pickle_protocol)
+            pickle.dump(self.lr_action, outf, pickle_protocol)
+            pickle.dump(self.lr_goto, outf, pickle_protocol)
+
+            outp = []
+            for p in self.lr_productions:
+                if p.func:
+                    outp.append((p.str, p.name, p.len, p.func, os.path.basename(p.file), p.line))
+                else:
+                    outp.append((str(p), p.name, p.len, None, None, None))
+            pickle.dump(outp, outf, pickle_protocol)
+
+# -----------------------------------------------------------------------------
+#                            === INTROSPECTION ===
+#
+# The following functions and classes are used to implement the PLY
+# introspection features followed by the yacc() function itself.
+# -----------------------------------------------------------------------------
+
+# -----------------------------------------------------------------------------
+# get_caller_module_dict()
+#
+# This function returns a dictionary containing all of the symbols defined within
+# a caller further down the call stack.  This is used to get the environment
+# associated with the yacc() call if none was provided.
+# -----------------------------------------------------------------------------
+
+def get_caller_module_dict(levels):
+    f = sys._getframe(levels)
+    ldict = f.f_globals.copy()
+    if f.f_globals != f.f_locals:
+        ldict.update(f.f_locals)
+    return ldict
+
+# -----------------------------------------------------------------------------
+# parse_grammar()
+#
+# This takes a raw grammar rule string and parses it into production data
+# -----------------------------------------------------------------------------
+def parse_grammar(doc, file, line):
+    grammar = []
+    # Split the doc string into lines
+    pstrings = doc.splitlines()
+    lastp = None
+    dline = line
+    for ps in pstrings:
+        dline += 1
+        p = ps.split()
+        if not p:
+            continue
+        try:
+            if p[0] == '|':
+                # This is a continuation of a previous rule
+                if not lastp:
+                    raise SyntaxError("%s:%d: Misplaced '|'" % (file, dline))
+                prodname = lastp
+                syms = p[1:]
+            else:
+                prodname = p[0]
+                lastp = prodname
+                syms   = p[2:]
+                assign = p[1]
+                if assign != ':' and assign != '::=':
+                    raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file, dline))
+
+            grammar.append((file, dline, prodname, syms))
+        except SyntaxError:
+            raise
+        except Exception:
+            raise SyntaxError('%s:%d: Syntax error in rule %r' % (file, dline, ps.strip()))
+
+    return grammar
+
+# -----------------------------------------------------------------------------
+# ParserReflect()
+#
+# This class represents information extracted for building a parser including
+# start symbol, error function, tokens, precedence list, action functions,
+# etc.
+# -----------------------------------------------------------------------------
+class ParserReflect(object):
+    def __init__(self, pdict, log=None):
+        self.pdict      = pdict
+        self.start      = None
+        self.error_func = None
+        self.tokens     = None
+        self.modules    = set()
+        self.grammar    = []
+        self.error      = False
+
+        if log is None:
+            self.log = PlyLogger(sys.stderr)
+        else:
+            self.log = log
+
+    # Get all of the basic information
+    def get_all(self):
+        self.get_start()
+        self.get_error_func()
+        self.get_tokens()
+        self.get_precedence()
+        self.get_pfunctions()
+
+    # Validate all of the information
+    def validate_all(self):
+        self.validate_start()
+        self.validate_error_func()
+        self.validate_tokens()
+        self.validate_precedence()
+        self.validate_pfunctions()
+        self.validate_modules()
+        return self.error
+
+    # Compute a signature over the grammar
+    def signature(self):
+        parts = []
+        try:
+            if self.start:
+                parts.append(self.start)
+            if self.prec:
+                parts.append(''.join([''.join(p) for p in self.prec]))
+            if self.tokens:
+                parts.append(' '.join(self.tokens))
+            for f in self.pfuncs:
+                if f[3]:
+                    parts.append(f[3])
+        except (TypeError, ValueError):
+            pass
+        return ''.join(parts)
+
+    # -----------------------------------------------------------------------------
+    # validate_modules()
+    #
+    # This method checks to see if there are duplicated p_rulename() functions
+    # in the parser module file.  Without this function, it is really easy for
+    # users to make mistakes by cutting and pasting code fragments (and it's a real
+    # bugger to try and figure out why the resulting parser doesn't work).  Therefore,
+    # we just do a little regular expression pattern matching of def statements
+    # to try and detect duplicates.
+    # -----------------------------------------------------------------------------
+
+    def validate_modules(self):
+        # Match def p_funcname(
+        fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(')
+
+        for module in self.modules:
+            try:
+                lines, linen = inspect.getsourcelines(module)
+            except IOError:
+                continue
+
+            counthash = {}
+            for linen, line in enumerate(lines):
+                linen += 1
+                m = fre.match(line)
+                if m:
+                    name = m.group(1)
+                    prev = counthash.get(name)
+                    if not prev:
+                        counthash[name] = linen
+                    else:
+                        filename = inspect.getsourcefile(module)
+                        self.log.warning('%s:%d: Function %s redefined. Previously defined on line %d',
+                                         filename, linen, name, prev)
+
+    # Get the start symbol
+    def get_start(self):
+        self.start = self.pdict.get('start')
+
+    # Validate the start symbol
+    def validate_start(self):
+        if self.start is not None:
+            if not isinstance(self.start, string_types):
+                self.log.error("'start' must be a string")
+
+    # Look for error handler
+    def get_error_func(self):
+        self.error_func = self.pdict.get('p_error')
+
+    # Validate the error function
+    def validate_error_func(self):
+        if self.error_func:
+            if isinstance(self.error_func, types.FunctionType):
+                ismethod = 0
+            elif isinstance(self.error_func, types.MethodType):
+                ismethod = 1
+            else:
+                self.log.error("'p_error' defined, but is not a function or method")
+                self.error = True
+                return
+
+            eline = self.error_func.__code__.co_firstlineno
+            efile = self.error_func.__code__.co_filename
+            module = inspect.getmodule(self.error_func)
+            self.modules.add(module)
+
+            argcount = self.error_func.__code__.co_argcount - ismethod
+            if argcount != 1:
+                self.log.error('%s:%d: p_error() requires 1 argument', efile, eline)
+                self.error = True
+
+    # Get the tokens map
+    def get_tokens(self):
+        tokens = self.pdict.get('tokens')
+        if not tokens:
+            self.log.error('No token list is defined')
+            self.error = True
+            return
+
+        if not isinstance(tokens, (list, tuple)):
+            self.log.error('tokens must be a list or tuple')
+            self.error = True
+            return
+
+        if not tokens:
+            self.log.error('tokens is empty')
+            self.error = True
+            return
+
+        self.tokens = sorted(tokens)
+
+    # Validate the tokens
+    def validate_tokens(self):
+        # Validate the tokens.
+        if 'error' in self.tokens:
+            self.log.error("Illegal token name 'error'. Is a reserved word")
+            self.error = True
+            return
+
+        terminals = set()
+        for n in self.tokens:
+            if n in terminals:
+                self.log.warning('Token %r multiply defined', n)
+            terminals.add(n)
+
+    # Get the precedence map (if any)
+    def get_precedence(self):
+        self.prec = self.pdict.get('precedence')
+
+    # Validate and parse the precedence map
+    def validate_precedence(self):
+        preclist = []
+        if self.prec:
+            if not isinstance(self.prec, (list, tuple)):
+                self.log.error('precedence must be a list or tuple')
+                self.error = True
+                return
+            for level, p in enumerate(self.prec):
+                if not isinstance(p, (list, tuple)):
+                    self.log.error('Bad precedence table')
+                    self.error = True
+                    return
+
+                if len(p) < 2:
+                    self.log.error('Malformed precedence entry %s. Must be (assoc, term, ..., term)', p)
+                    self.error = True
+                    return
+                assoc = p[0]
+                if not isinstance(assoc, string_types):
+                    self.log.error('precedence associativity must be a string')
+                    self.error = True
+                    return
+                for term in p[1:]:
+                    if not isinstance(term, string_types):
+                        self.log.error('precedence items must be strings')
+                        self.error = True
+                        return
+                    preclist.append((term, assoc, level+1))
+        self.preclist = preclist
+
+    # Get all p_functions from the grammar
+    def get_pfunctions(self):
+        p_functions = []
+        for name, item in self.pdict.items():
+            if not name.startswith('p_') or name == 'p_error':
+                continue
+            if isinstance(item, (types.FunctionType, types.MethodType)):
+                line = getattr(item, 'co_firstlineno', item.__code__.co_firstlineno)
+                module = inspect.getmodule(item)
+                p_functions.append((line, module, name, item.__doc__))
+
+        # Sort all of the actions by line number; make sure to stringify
+        # modules to make them sortable, since `line` may not uniquely sort all
+        # p functions
+        p_functions.sort(key=lambda p_function: (
+            p_function[0],
+            str(p_function[1]),
+            p_function[2],
+            p_function[3]))
+        self.pfuncs = p_functions
+
+    # Validate all of the p_functions
+    def validate_pfunctions(self):
+        grammar = []
+        # Check for non-empty symbols
+        if len(self.pfuncs) == 0:
+            self.log.error('no rules of the form p_rulename are defined')
+            self.error = True
+            return
+
+        for line, module, name, doc in self.pfuncs:
+            file = inspect.getsourcefile(module)
+            func = self.pdict[name]
+            if isinstance(func, types.MethodType):
+                reqargs = 2
+            else:
+                reqargs = 1
+            if func.__code__.co_argcount > reqargs:
+                self.log.error('%s:%d: Rule %r has too many arguments', file, line, func.__name__)
+                self.error = True
+            elif func.__code__.co_argcount < reqargs:
+                self.log.error('%s:%d: Rule %r requires an argument', file, line, func.__name__)
+                self.error = True
+            elif not func.__doc__:
+                self.log.warning('%s:%d: No documentation string specified in function %r (ignored)',
+                                 file, line, func.__name__)
+            else:
+                try:
+                    parsed_g = parse_grammar(doc, file, line)
+                    for g in parsed_g:
+                        grammar.append((name, g))
+                except SyntaxError as e:
+                    self.log.error(str(e))
+                    self.error = True
+
+                # Looks like a valid grammar rule
+                # Mark the file in which defined.
+                self.modules.add(module)
+
+        # Secondary validation step that looks for p_ definitions that are not functions
+        # or functions that look like they might be grammar rules.
+
+        for n, v in self.pdict.items():
+            if n.startswith('p_') and isinstance(v, (types.FunctionType, types.MethodType)):
+                continue
+            if n.startswith('t_'):
+                continue
+            if n.startswith('p_') and n != 'p_error':
+                self.log.warning('%r not defined as a function', n)
+            if ((isinstance(v, types.FunctionType) and v.__code__.co_argcount == 1) or
+                   (isinstance(v, types.MethodType) and v.__func__.__code__.co_argcount == 2)):
+                if v.__doc__:
+                    try:
+                        doc = v.__doc__.split(' ')
+                        if doc[1] == ':':
+                            self.log.warning('%s:%d: Possible grammar rule %r defined without p_ prefix',
+                                             v.__code__.co_filename, v.__code__.co_firstlineno, n)
+                    except IndexError:
+                        pass
+
+        self.grammar = grammar
+
+# -----------------------------------------------------------------------------
+# yacc(module)
+#
+# Build a parser
+# -----------------------------------------------------------------------------
+
+def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None,
+         check_recursion=True, optimize=False, write_tables=True, debugfile=debug_file,
+         outputdir=None, debuglog=None, errorlog=None, picklefile=None):
+
+    if tabmodule is None:
+        tabmodule = tab_module
+
+    # Reference to the parsing method of the last built parser
+    global parse
+
+    # If pickling is enabled, table files are not created
+    if picklefile:
+        write_tables = 0
+
+    if errorlog is None:
+        errorlog = PlyLogger(sys.stderr)
+
+    # Get the module dictionary used for the parser
+    if module:
+        _items = [(k, getattr(module, k)) for k in dir(module)]
+        pdict = dict(_items)
+        # If no __file__ or __package__ attributes are available, try to obtain them
+        # from the __module__ instead
+        if '__file__' not in pdict:
+            pdict['__file__'] = sys.modules[pdict['__module__']].__file__
+        if '__package__' not in pdict and '__module__' in pdict:
+            if hasattr(sys.modules[pdict['__module__']], '__package__'):
+                pdict['__package__'] = sys.modules[pdict['__module__']].__package__
+    else:
+        pdict = get_caller_module_dict(2)
+
+    if outputdir is None:
+        # If no output directory is set, the location of the output files
+        # is determined according to the following rules:
+        #     - If tabmodule specifies a package, files go into that package directory
+        #     - Otherwise, files go in the same directory as the specifying module
+        if isinstance(tabmodule, types.ModuleType):
+            srcfile = tabmodule.__file__
+        else:
+            if '.' not in tabmodule:
+                srcfile = pdict['__file__']
+            else:
+                parts = tabmodule.split('.')
+                pkgname = '.'.join(parts[:-1])
+                exec('import %s' % pkgname)
+                srcfile = getattr(sys.modules[pkgname], '__file__', '')
+        outputdir = os.path.dirname(srcfile)
+
+    # Determine if the module is package of a package or not.
+    # If so, fix the tabmodule setting so that tables load correctly
+    pkg = pdict.get('__package__')
+    if pkg and isinstance(tabmodule, str):
+        if '.' not in tabmodule:
+            tabmodule = pkg + '.' + tabmodule
+
+
+
+    # Set start symbol if it's specified directly using an argument
+    if start is not None:
+        pdict['start'] = start
+
+    # Collect parser information from the dictionary
+    pinfo = ParserReflect(pdict, log=errorlog)
+    pinfo.get_all()
+
+    if pinfo.error:
+        raise YaccError('Unable to build parser')
+
+    # Check signature against table files (if any)
+    signature = pinfo.signature()
+
+    # Read the tables
+    try:
+        lr = LRTable()
+        if picklefile:
+            read_signature = lr.read_pickle(picklefile)
+        else:
+            read_signature = lr.read_table(tabmodule)
+        if optimize or (read_signature == signature):
+            try:
+                lr.bind_callables(pinfo.pdict)
+                parser = LRParser(lr, pinfo.error_func)
+                parse = parser.parse
+                return parser
+            except Exception as e:
+                errorlog.warning('There was a problem loading the table file: %r', e)
+    except VersionError as e:
+        errorlog.warning(str(e))
+    except ImportError:
+        pass
+
+    if debuglog is None:
+        if debug:
+            try:
+                debuglog = PlyLogger(open(os.path.join(outputdir, debugfile), 'w'))
+            except IOError as e:
+                errorlog.warning("Couldn't open %r. %s" % (debugfile, e))
+                debuglog = NullLogger()
+        else:
+            debuglog = NullLogger()
+
+    debuglog.info('Created by PLY version %s (http://www.dabeaz.com/ply)', __version__)
+
+    errors = False
+
+    # Validate the parser information
+    if pinfo.validate_all():
+        raise YaccError('Unable to build parser')
+
+    if not pinfo.error_func:
+        errorlog.warning('no p_error() function is defined')
+
+    # Create a grammar object
+    grammar = Grammar(pinfo.tokens)
+
+    # Set precedence level for terminals
+    for term, assoc, level in pinfo.preclist:
+        try:
+            grammar.set_precedence(term, assoc, level)
+        except GrammarError as e:
+            errorlog.warning('%s', e)
+
+    # Add productions to the grammar
+    for funcname, gram in pinfo.grammar:
+        file, line, prodname, syms = gram
+        try:
+            grammar.add_production(prodname, syms, funcname, file, line)
+        except GrammarError as e:
+            errorlog.error('%s', e)
+            errors = True
+
+    # Set the grammar start symbols
+    try:
+        if start is None:
+            grammar.set_start(pinfo.start)
+        else:
+            grammar.set_start(start)
+    except GrammarError as e:
+        errorlog.error(str(e))
+        errors = True
+
+    if errors:
+        raise YaccError('Unable to build parser')
+
+    # Verify the grammar structure
+    undefined_symbols = grammar.undefined_symbols()
+    for sym, prod in undefined_symbols:
+        errorlog.error('%s:%d: Symbol %r used, but not defined as a token or a rule', prod.file, prod.line, sym)
+        errors = True
+
+    unused_terminals = grammar.unused_terminals()
+    if unused_terminals:
+        debuglog.info('')
+        debuglog.info('Unused terminals:')
+        debuglog.info('')
+        for term in unused_terminals:
+            errorlog.warning('Token %r defined, but not used', term)
+            debuglog.info('    %s', term)
+
+    # Print out all productions to the debug log
+    if debug:
+        debuglog.info('')
+        debuglog.info('Grammar')
+        debuglog.info('')
+        for n, p in enumerate(grammar.Productions):
+            debuglog.info('Rule %-5d %s', n, p)
+
+    # Find unused non-terminals
+    unused_rules = grammar.unused_rules()
+    for prod in unused_rules:
+        errorlog.warning('%s:%d: Rule %r defined, but not used', prod.file, prod.line, prod.name)
+
+    if len(unused_terminals) == 1:
+        errorlog.warning('There is 1 unused token')
+    if len(unused_terminals) > 1:
+        errorlog.warning('There are %d unused tokens', len(unused_terminals))
+
+    if len(unused_rules) == 1:
+        errorlog.warning('There is 1 unused rule')
+    if len(unused_rules) > 1:
+        errorlog.warning('There are %d unused rules', len(unused_rules))
+
+    if debug:
+        debuglog.info('')
+        debuglog.info('Terminals, with rules where they appear')
+        debuglog.info('')
+        terms = list(grammar.Terminals)
+        terms.sort()
+        for term in terms:
+            debuglog.info('%-20s : %s', term, ' '.join([str(s) for s in grammar.Terminals[term]]))
+
+        debuglog.info('')
+        debuglog.info('Nonterminals, with rules where they appear')
+        debuglog.info('')
+        nonterms = list(grammar.Nonterminals)
+        nonterms.sort()
+        for nonterm in nonterms:
+            debuglog.info('%-20s : %s', nonterm, ' '.join([str(s) for s in grammar.Nonterminals[nonterm]]))
+        debuglog.info('')
+
+    if check_recursion:
+        unreachable = grammar.find_unreachable()
+        for u in unreachable:
+            errorlog.warning('Symbol %r is unreachable', u)
+
+        infinite = grammar.infinite_cycles()
+        for inf in infinite:
+            errorlog.error('Infinite recursion detected for symbol %r', inf)
+            errors = True
+
+    unused_prec = grammar.unused_precedence()
+    for term, assoc in unused_prec:
+        errorlog.error('Precedence rule %r defined for unknown symbol %r', assoc, term)
+        errors = True
+
+    if errors:
+        raise YaccError('Unable to build parser')
+
+    # Run the LRGeneratedTable on the grammar
+    if debug:
+        errorlog.debug('Generating %s tables', method)
+
+    lr = LRGeneratedTable(grammar, method, debuglog)
+
+    if debug:
+        num_sr = len(lr.sr_conflicts)
+
+        # Report shift/reduce and reduce/reduce conflicts
+        if num_sr == 1:
+            errorlog.warning('1 shift/reduce conflict')
+        elif num_sr > 1:
+            errorlog.warning('%d shift/reduce conflicts', num_sr)
+
+        num_rr = len(lr.rr_conflicts)
+        if num_rr == 1:
+            errorlog.warning('1 reduce/reduce conflict')
+        elif num_rr > 1:
+            errorlog.warning('%d reduce/reduce conflicts', num_rr)
+
+    # Write out conflicts to the output file
+    if debug and (lr.sr_conflicts or lr.rr_conflicts):
+        debuglog.warning('')
+        debuglog.warning('Conflicts:')
+        debuglog.warning('')
+
+        for state, tok, resolution in lr.sr_conflicts:
+            debuglog.warning('shift/reduce conflict for %s in state %d resolved as %s',  tok, state, resolution)
+
+        already_reported = set()
+        for state, rule, rejected in lr.rr_conflicts:
+            if (state, id(rule), id(rejected)) in already_reported:
+                continue
+            debuglog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule)
+            debuglog.warning('rejected rule (%s) in state %d', rejected, state)
+            errorlog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule)
+            errorlog.warning('rejected rule (%s) in state %d', rejected, state)
+            already_reported.add((state, id(rule), id(rejected)))
+
+        warned_never = []
+        for state, rule, rejected in lr.rr_conflicts:
+            if not rejected.reduced and (rejected not in warned_never):
+                debuglog.warning('Rule (%s) is never reduced', rejected)
+                errorlog.warning('Rule (%s) is never reduced', rejected)
+                warned_never.append(rejected)
+
+    # Write the table file if requested
+    if write_tables:
+        try:
+            lr.write_table(tabmodule, outputdir, signature)
+            if tabmodule in sys.modules:
+                del sys.modules[tabmodule]
+        except IOError as e:
+            errorlog.warning("Couldn't create %r. %s" % (tabmodule, e))
+
+    # Write a pickled version of the tables
+    if picklefile:
+        try:
+            lr.pickle_table(picklefile, signature)
+        except IOError as e:
+            errorlog.warning("Couldn't create %r. %s" % (picklefile, e))
+
+    # Build the parser
+    lr.bind_callables(pinfo.pdict)
+    parser = LRParser(lr, pinfo.error_func)
+
+    parse = parser.parse
+    return parser
diff --git a/server/libs/ply/ygen.py b/server/libs/ply/ygen.py
new file mode 100644
index 0000000..03b9318
--- /dev/null
+++ b/server/libs/ply/ygen.py
@@ -0,0 +1,69 @@
+# ply: ygen.py
+#
+# This is a support program that auto-generates different versions of the YACC parsing
+# function with different features removed for the purposes of performance.
+#
+# Users should edit the method LRParser.parsedebug() in yacc.py.   The source code
+# for that method is then used to create the other methods.   See the comments in
+# yacc.py for further details.
+
+import os.path
+import shutil
+
+def get_source_range(lines, tag):
+    srclines = enumerate(lines)
+    start_tag = '#--! %s-start' % tag
+    end_tag = '#--! %s-end' % tag
+
+    for start_index, line in srclines:
+        if line.strip().startswith(start_tag):
+            break
+
+    for end_index, line in srclines:
+        if line.strip().endswith(end_tag):
+            break
+
+    return (start_index + 1, end_index)
+
+def filter_section(lines, tag):
+    filtered_lines = []
+    include = True
+    tag_text = '#--! %s' % tag
+    for line in lines:
+        if line.strip().startswith(tag_text):
+            include = not include
+        elif include:
+            filtered_lines.append(line)
+    return filtered_lines
+
+def main():
+    dirname = os.path.dirname(__file__)
+    shutil.copy2(os.path.join(dirname, 'yacc.py'), os.path.join(dirname, 'yacc.py.bak'))
+    with open(os.path.join(dirname, 'yacc.py'), 'r') as f:
+        lines = f.readlines()
+
+    parse_start, parse_end = get_source_range(lines, 'parsedebug')
+    parseopt_start, parseopt_end = get_source_range(lines, 'parseopt')
+    parseopt_notrack_start, parseopt_notrack_end = get_source_range(lines, 'parseopt-notrack')
+
+    # Get the original source
+    orig_lines = lines[parse_start:parse_end]
+
+    # Filter the DEBUG sections out
+    parseopt_lines = filter_section(orig_lines, 'DEBUG')
+
+    # Filter the TRACKING sections out
+    parseopt_notrack_lines = filter_section(parseopt_lines, 'TRACKING')
+
+    # Replace the parser source sections with updated versions
+    lines[parseopt_notrack_start:parseopt_notrack_end] = parseopt_notrack_lines
+    lines[parseopt_start:parseopt_end] = parseopt_lines
+
+    lines = [line.rstrip()+'\n' for line in lines]
+    with open(os.path.join(dirname, 'yacc.py'), 'w') as f:
+        f.writelines(lines)
+
+    print('Updated yacc.py')
+
+if __name__ == '__main__':
+    main()
diff --git a/server/libs/pygls-1.3.1.dist-info/INSTALLER b/server/libs/pygls-1.3.1.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/server/libs/pygls-1.3.1.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/server/libs/pygls-1.3.1.dist-info/LICENSE.txt b/server/libs/pygls-1.3.1.dist-info/LICENSE.txt
new file mode 100644
index 0000000..80f617c
--- /dev/null
+++ b/server/libs/pygls-1.3.1.dist-info/LICENSE.txt
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright (c) Open Law Library. All rights reserved.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/server/libs/pygls-1.3.1.dist-info/METADATA b/server/libs/pygls-1.3.1.dist-info/METADATA
new file mode 100644
index 0000000..70de8e1
--- /dev/null
+++ b/server/libs/pygls-1.3.1.dist-info/METADATA
@@ -0,0 +1,105 @@
+Metadata-Version: 2.1
+Name: pygls
+Version: 1.3.1
+Summary: A pythonic generic language server (pronounced like 'pie glass')
+Home-page: https://github.com/openlawlibrary/pygls
+License: Apache-2.0
+Author: Open Law Library
+Author-email: info@openlawlib.org
+Maintainer: Tom BH
+Maintainer-email: tom@tombh.co.uk
+Requires-Python: >=3.8
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Provides-Extra: ws
+Requires-Dist: cattrs (>=23.1.2)
+Requires-Dist: lsprotocol (==2023.0.1)
+Requires-Dist: websockets (>=11.0.3) ; extra == "ws"
+Project-URL: Documentation, https://pygls.readthedocs.io/en/latest
+Project-URL: Repository, https://github.com/openlawlibrary/pygls
+Description-Content-Type: text/markdown
+
+[![PyPI Version](https://img.shields.io/pypi/v/pygls.svg)](https://pypi.org/project/pygls/) ![!pyversions](https://img.shields.io/pypi/pyversions/pygls.svg) ![license](https://img.shields.io/pypi/l/pygls.svg) [![Documentation Status](https://img.shields.io/badge/docs-latest-green.svg)](https://pygls.readthedocs.io/en/latest/)
+
+# pygls: The Generic Language Server Framework
+
+_pygls_ (pronounced like "pie glass") is a pythonic generic implementation of the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/specification) for use as a foundation for writing your own [Language Servers](https://langserver.org/) in just a few lines of code.
+
+## Quickstart
+```python
+from pygls.server import LanguageServer
+from lsprotocol.types import (
+    TEXT_DOCUMENT_COMPLETION,
+    CompletionItem,
+    CompletionList,
+    CompletionParams,
+)
+
+server = LanguageServer("example-server", "v0.1")
+
+@server.feature(TEXT_DOCUMENT_COMPLETION)
+def completions(params: CompletionParams):
+    items = []
+    document = server.workspace.get_document(params.text_document.uri)
+    current_line = document.lines[params.position.line].strip()
+    if current_line.endswith("hello."):
+        items = [
+            CompletionItem(label="world"),
+            CompletionItem(label="friend"),
+        ]
+    return CompletionList(is_incomplete=False, items=items)
+
+server.start_io()
+```
+
+Which might look something like this when you trigger autocompletion in your editor:
+
+![completions](https://raw.githubusercontent.com/openlawlibrary/pygls/master/docs/assets/hello-world-completion.png)
+
+## Docs and Tutorial
+
+The full documentation and a tutorial are available at .
+
+## Projects based on _pygls_
+
+We keep a table of all known _pygls_ [implementations](https://github.com/openlawlibrary/pygls/blob/master/Implementations.md). Please submit a Pull Request with your own or any that you find are missing.
+
+## Alternatives
+
+The main alternative to _pygls_ is Microsoft's [NodeJS-based Generic Language Server Framework](https://github.com/microsoft/vscode-languageserver-node). Being from Microsoft it is focussed on extending VSCode, although in theory it could be used to support any editor. So this is where pygls might be a better choice if you want to support more editors, as pygls is not focussed around VSCode.
+
+There are also other Language Servers with "general" in their descriptons, or at least intentions. They are however only general in the sense of having powerful _configuration_. They achieve generality in so much as configuration is able to, as opposed to what programming (in _pygls'_ case) can achieve.
+  * https://github.com/iamcco/diagnostic-languageserver
+  * https://github.com/mattn/efm-langserver
+  * https://github.com/jose-elias-alvarez/null-ls.nvim (Neovim only)
+
+## Tests
+All Pygls sub-tasks require the Poetry `poe` plugin: https://github.com/nat-n/poethepoet
+
+* `poetry install --all-extras`
+* `poetry run poe test`
+* `poetry run poe test-pyodide`
+
+
+## Contributing
+
+Your contributions to _pygls_ are most welcome ❤️ Please review the [Contributing](https://github.com/openlawlibrary/pygls/blob/master/CONTRIBUTING.md) and [Code of Conduct](https://github.com/openlawlibrary/pygls/blob/master/CODE_OF_CONDUCT.md) documents for how to get started.
+
+## Donating
+
+[Open Law Library](http://www.openlawlib.org/) is a 501(c)(3) tax exempt organization. Help us maintain our open source projects and open the law to all with [sponsorship](https://github.com/sponsors/openlawlibrary).
+
+### Supporters
+
+We would like to give special thanks to the following supporters:
+* [mpourmpoulis](https://github.com/mpourmpoulis)
+
+## License
+
+Apache-2.0
+
diff --git a/server/libs/pygls-1.3.1.dist-info/RECORD b/server/libs/pygls-1.3.1.dist-info/RECORD
new file mode 100644
index 0000000..518102f
--- /dev/null
+++ b/server/libs/pygls-1.3.1.dist-info/RECORD
@@ -0,0 +1,45 @@
+pygls-1.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+pygls-1.3.1.dist-info/LICENSE.txt,sha256=b0kVxr8adbxhHDGM8t6T3jWLMbQJ7QLrngwkWnnWCl8,11367
+pygls-1.3.1.dist-info/METADATA,sha256=ZZmXz51Jk7TTtWO1hLfSWQynv6rDCM798YWVbloCqHk,4726
+pygls-1.3.1.dist-info/RECORD,,
+pygls-1.3.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+pygls-1.3.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
+pygls/__init__.py,sha256=rdTb3X-53tCjgNpS5dRsxv0ukMDjEly2pVEOPTnjx5k,1488
+pygls/__pycache__/__init__.cpython-311.pyc,,
+pygls/__pycache__/capabilities.cpython-311.pyc,,
+pygls/__pycache__/client.cpython-311.pyc,,
+pygls/__pycache__/constants.cpython-311.pyc,,
+pygls/__pycache__/exceptions.cpython-311.pyc,,
+pygls/__pycache__/feature_manager.cpython-311.pyc,,
+pygls/__pycache__/progress.cpython-311.pyc,,
+pygls/__pycache__/server.cpython-311.pyc,,
+pygls/__pycache__/uris.cpython-311.pyc,,
+pygls/capabilities.py,sha256=3tl-cqu82QpxHz-Z3NtlU6z-gbPqIvyvZ6gyZ-dr0-0,16756
+pygls/client.py,sha256=loVwqagoY0BnS6RwfpYtXwhg0NiIE6Tvbj2DzpOF7GA,6292
+pygls/constants.py,sha256=0YsX4Egp9jVLuAu9v8T_ThrObrVKGP916-X-9bkmId8,1470
+pygls/exceptions.py,sha256=skJKYaJCXI5At2eS1pNdQ6r3_B9Z-SMlyqr0_dwUVEw,6302
+pygls/feature_manager.py,sha256=7C--ra3GaG44LoZd8MaZ6Jh_8uxQMZo5jzykGwT7Fdg,8494
+pygls/lsp/__init__.py,sha256=pp9PCQhGzgPPFfWID47cdz_Q0LpZjT2kFPgTS-N7iVM,5236
+pygls/lsp/__pycache__/__init__.cpython-311.pyc,,
+pygls/lsp/__pycache__/client.cpython-311.pyc,,
+pygls/lsp/client.py,sha256=8JVgyjXXOblPlDhLAvNbukK-qrQNf__757elz9XYkCo,76358
+pygls/progress.py,sha256=Ml8vgJ9ueFC4YUqwvrgHatX8l_O3odhvmoPOLIcMNok,2789
+pygls/protocol/__init__.py,sha256=YI5xMBILWYKexx343wUDrHUVPuJFu9A48VSf0ieXYJM,1822
+pygls/protocol/__pycache__/__init__.cpython-311.pyc,,
+pygls/protocol/__pycache__/json_rpc.cpython-311.pyc,,
+pygls/protocol/__pycache__/language_server.cpython-311.pyc,,
+pygls/protocol/__pycache__/lsp_meta.cpython-311.pyc,,
+pygls/protocol/json_rpc.py,sha256=01nqJoCNDmZQ78tyFxPfA9_kEBHyuBe-7ZUXCqwmu4g,20124
+pygls/protocol/language_server.py,sha256=b6X30DCLN4sdG85OhLVdrBJ9cweL4eKVgsc10bnMzXk,20109
+pygls/protocol/lsp_meta.py,sha256=kX1nL7XGVIYWp6UUyT3yDFnhcvpoCU_3mdyzpDTtUyQ,1593
+pygls/py.typed,sha256=ZfGKUcVseOxYpg6BU9EuhkP4dErsepCA4apkj_9YnYc,65
+pygls/server.py,sha256=T-k2wsP0W5a6KHmE9Afrv2PTy3qACEuCPGSJPmzC30w,20753
+pygls/uris.py,sha256=lknA_8hNYfs47mOFQxnFYW2TWH-8iL68ghTVD0Cccsc,5764
+pygls/workspace/__init__.py,sha256=tD6ahYMIPVsDdsWJ32EhF8wK-IJy6IQGInkI-BmIAxY,2883
+pygls/workspace/__pycache__/__init__.cpython-311.pyc,,
+pygls/workspace/__pycache__/position_codec.cpython-311.pyc,,
+pygls/workspace/__pycache__/text_document.cpython-311.pyc,,
+pygls/workspace/__pycache__/workspace.cpython-311.pyc,,
+pygls/workspace/position_codec.py,sha256=UDv1kXFMyCDnu-iGhEbylCJP74L0TAL2hFvhoFrSpOY,8019
+pygls/workspace/text_document.py,sha256=8LcxsQeawuPNDrNDm3A2oMjWxYA3YxXm3NiHyQJQp8M,9031
+pygls/workspace/workspace.py,sha256=Zpd96kvVM7po7cI_XLenRAq8Wp3mjlMBX_YxO6Ecvs8,11556
diff --git a/server/libs/pygls-1.3.1.dist-info/REQUESTED b/server/libs/pygls-1.3.1.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/server/libs/pygls-1.3.1.dist-info/WHEEL b/server/libs/pygls-1.3.1.dist-info/WHEEL
new file mode 100644
index 0000000..d73ccaa
--- /dev/null
+++ b/server/libs/pygls-1.3.1.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: poetry-core 1.9.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/server/libs/pygls/__init__.py b/server/libs/pygls/__init__.py
new file mode 100644
index 0000000..147cd9e
--- /dev/null
+++ b/server/libs/pygls/__init__.py
@@ -0,0 +1,25 @@
+############################################################################
+# Original work Copyright 2018 Palantir Technologies, Inc.                 #
+# Original work licensed under the MIT License.                            #
+# See ThirdPartyNotices.txt in the project root for license information.   #
+# All modifications Copyright (c) Open Law Library. All rights reserved.   #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import os
+import sys
+
+IS_WIN = os.name == "nt"
+IS_PYODIDE = "pyodide" in sys.modules
+
+pygls = "pygls"
diff --git a/server/libs/pygls/capabilities.py b/server/libs/pygls/capabilities.py
new file mode 100644
index 0000000..9db4744
--- /dev/null
+++ b/server/libs/pygls/capabilities.py
@@ -0,0 +1,460 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved.                      #
+# See ThirdPartyNotices.txt in the project root for additional notices.    #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+from functools import reduce
+from typing import Any, Dict, List, Optional, Set, Union, TypeVar
+import logging
+
+from lsprotocol import types
+
+
+logger = logging.getLogger(__name__)
+T = TypeVar("T")
+
+
+def get_capability(
+    client_capabilities: types.ClientCapabilities, field: str, default: Any = None
+) -> Any:
+    """Check if ClientCapabilities has some nested value without raising
+    AttributeError.
+    e.g. get_capability('text_document.synchronization.will_save')
+    """
+    try:
+        value = reduce(getattr, field.split("."), client_capabilities)
+    except AttributeError:
+        return default
+
+    # If we reach the desired leaf value but it's None, return the default.
+    return default if value is None else value
+
+
+class ServerCapabilitiesBuilder:
+    """Create `ServerCapabilities` instance depending on builtin and user registered
+    features.
+    """
+
+    def __init__(
+        self,
+        client_capabilities: types.ClientCapabilities,
+        features: Set[str],
+        feature_options: Dict[str, Any],
+        commands: List[str],
+        text_document_sync_kind: types.TextDocumentSyncKind,
+        notebook_document_sync: Optional[types.NotebookDocumentSyncOptions] = None,
+    ):
+        self.client_capabilities = client_capabilities
+        self.features = features
+        self.feature_options = feature_options
+        self.commands = commands
+        self.text_document_sync_kind = text_document_sync_kind
+        self.notebook_document_sync = notebook_document_sync
+
+        self.server_cap = types.ServerCapabilities()
+
+    def _provider_options(self, feature: str, default: T) -> Optional[Union[T, Any]]:
+        if feature in self.features:
+            return self.feature_options.get(feature, default)
+        return None
+
+    def _with_text_document_sync(self):
+        open_close = (
+            types.TEXT_DOCUMENT_DID_OPEN in self.features
+            or types.TEXT_DOCUMENT_DID_CLOSE in self.features
+        )
+        will_save = (
+            get_capability(
+                self.client_capabilities, "text_document.synchronization.will_save"
+            )
+            and types.TEXT_DOCUMENT_WILL_SAVE in self.features
+        )
+        will_save_wait_until = (
+            get_capability(
+                self.client_capabilities,
+                "text_document.synchronization.will_save_wait_until",
+            )
+            and types.TEXT_DOCUMENT_WILL_SAVE_WAIT_UNTIL in self.features
+        )
+        if types.TEXT_DOCUMENT_DID_SAVE in self.features:
+            save = self.feature_options.get(types.TEXT_DOCUMENT_DID_SAVE, True)
+        else:
+            save = False
+
+        self.server_cap.text_document_sync = types.TextDocumentSyncOptions(
+            open_close=open_close,
+            change=self.text_document_sync_kind,
+            will_save=will_save,
+            will_save_wait_until=will_save_wait_until,
+            save=save,
+        )
+
+        return self
+
+    def _with_notebook_document_sync(self):
+        if self.client_capabilities.notebook_document is None:
+            return self
+
+        self.server_cap.notebook_document_sync = self.notebook_document_sync
+        return self
+
+    def _with_completion(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_COMPLETION, default=types.CompletionOptions()
+        )
+        if value is not None:
+            self.server_cap.completion_provider = value
+        return self
+
+    def _with_hover(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_HOVER, default=True)
+        if value is not None:
+            self.server_cap.hover_provider = value
+        return self
+
+    def _with_signature_help(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_SIGNATURE_HELP, default=types.SignatureHelpOptions()
+        )
+        if value is not None:
+            self.server_cap.signature_help_provider = value
+        return self
+
+    def _with_declaration(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_DECLARATION, default=True)
+        if value is not None:
+            self.server_cap.declaration_provider = value
+        return self
+
+    def _with_definition(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_DEFINITION, default=True)
+        if value is not None:
+            self.server_cap.definition_provider = value
+        return self
+
+    def _with_type_definition(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_TYPE_DEFINITION, default=types.TypeDefinitionOptions()
+        )
+        if value is not None:
+            self.server_cap.type_definition_provider = value
+        return self
+
+    def _with_inlay_hints(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_INLAY_HINT, default=types.InlayHintOptions()
+        )
+        if value is not None:
+            value.resolve_provider = types.INLAY_HINT_RESOLVE in self.features
+            self.server_cap.inlay_hint_provider = value
+        return self
+
+    def _with_implementation(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_IMPLEMENTATION, default=types.ImplementationOptions()
+        )
+        if value is not None:
+            self.server_cap.implementation_provider = value
+        return self
+
+    def _with_references(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_REFERENCES, default=True)
+        if value is not None:
+            self.server_cap.references_provider = value
+        return self
+
+    def _with_document_highlight(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_DOCUMENT_HIGHLIGHT, default=True
+        )
+        if value is not None:
+            self.server_cap.document_highlight_provider = value
+        return self
+
+    def _with_document_symbol(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_DOCUMENT_SYMBOL, default=True
+        )
+        if value is not None:
+            self.server_cap.document_symbol_provider = value
+        return self
+
+    def _with_code_action(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_CODE_ACTION, default=True)
+        if value is not None:
+            self.server_cap.code_action_provider = value
+        return self
+
+    def _with_code_lens(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_CODE_LENS, default=types.CodeLensOptions()
+        )
+        if value is not None:
+            self.server_cap.code_lens_provider = value
+        return self
+
+    def _with_document_link(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_DOCUMENT_LINK, default=types.DocumentLinkOptions()
+        )
+        if value is not None:
+            self.server_cap.document_link_provider = value
+        return self
+
+    def _with_color(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_DOCUMENT_COLOR, default=True)
+        if value is not None:
+            self.server_cap.color_provider = value
+        return self
+
+    def _with_document_formatting(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_FORMATTING, default=True)
+        if value is not None:
+            self.server_cap.document_formatting_provider = value
+        return self
+
+    def _with_document_range_formatting(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_RANGE_FORMATTING, default=True
+        )
+        if value is not None:
+            self.server_cap.document_range_formatting_provider = value
+        return self
+
+    def _with_document_on_type_formatting(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_ON_TYPE_FORMATTING, default=None
+        )
+        if value is not None:
+            self.server_cap.document_on_type_formatting_provider = value
+        return self
+
+    def _with_rename(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_RENAME, default=True)
+        if value is not None:
+            self.server_cap.rename_provider = value
+        return self
+
+    def _with_folding_range(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_FOLDING_RANGE, default=True)
+        if value is not None:
+            self.server_cap.folding_range_provider = value
+        return self
+
+    def _with_execute_command(self):
+        self.server_cap.execute_command_provider = types.ExecuteCommandOptions(
+            commands=self.commands
+        )
+        return self
+
+    def _with_selection_range(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_SELECTION_RANGE, default=True
+        )
+        if value is not None:
+            self.server_cap.selection_range_provider = value
+        return self
+
+    def _with_call_hierarchy(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_PREPARE_CALL_HIERARCHY, default=True
+        )
+        if value is not None:
+            self.server_cap.call_hierarchy_provider = value
+        return self
+
+    def _with_type_hierarchy(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_PREPARE_TYPE_HIERARCHY, default=True
+        )
+        if value is not None:
+            self.server_cap.type_hierarchy_provider = value
+        return self
+
+    def _with_semantic_tokens(self):
+        providers = [
+            types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+            types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+            types.TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+        ]
+
+        value = None
+        for provider in providers:
+            value = self._provider_options(provider, default=None)
+            if value is not None:
+                break
+
+        if value is None:
+            return self
+
+        if isinstance(value, types.SemanticTokensRegistrationOptions):
+            self.server_cap.semantic_tokens_provider = value
+            return self
+
+        full_support: Union[bool, types.SemanticTokensOptionsFullType1] = (
+            types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL in self.features
+        )
+
+        if types.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA in self.features:
+            full_support = types.SemanticTokensOptionsFullType1(delta=True)
+
+        options = types.SemanticTokensOptions(
+            legend=value,
+            full=full_support or None,
+            range=types.TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE in self.features or None,
+        )
+
+        if options.full or options.range:
+            self.server_cap.semantic_tokens_provider = options
+
+        return self
+
+    def _with_linked_editing_range(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_LINKED_EDITING_RANGE, default=True
+        )
+        if value is not None:
+            self.server_cap.linked_editing_range_provider = value
+        return self
+
+    def _with_moniker(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_MONIKER, default=True)
+        if value is not None:
+            self.server_cap.moniker_provider = value
+        return self
+
+    def _with_workspace_symbol(self):
+        value = self._provider_options(
+            types.WORKSPACE_SYMBOL, default=types.WorkspaceSymbolOptions()
+        )
+        if value is not None:
+            value.resolve_provider = types.WORKSPACE_SYMBOL_RESOLVE in self.features
+            self.server_cap.workspace_symbol_provider = value
+        return self
+
+    def _with_workspace_capabilities(self):
+        # File operations
+        file_operations = types.FileOperationOptions()
+        operations = [
+            (types.WORKSPACE_WILL_CREATE_FILES, "will_create"),
+            (types.WORKSPACE_DID_CREATE_FILES, "did_create"),
+            (types.WORKSPACE_WILL_DELETE_FILES, "will_delete"),
+            (types.WORKSPACE_DID_DELETE_FILES, "did_delete"),
+            (types.WORKSPACE_WILL_RENAME_FILES, "will_rename"),
+            (types.WORKSPACE_DID_RENAME_FILES, "did_rename"),
+        ]
+
+        for method_name, capability_name in operations:
+            client_supports_method = get_capability(
+                self.client_capabilities, f"workspace.file_operations.{capability_name}"
+            )
+
+            if client_supports_method:
+                value = self._provider_options(method_name, default=None)
+                setattr(file_operations, capability_name, value)
+
+        self.server_cap.workspace = types.ServerCapabilitiesWorkspaceType(
+            workspace_folders=types.WorkspaceFoldersServerCapabilities(
+                supported=True,
+                change_notifications=True,
+            ),
+            file_operations=file_operations,
+        )
+        return self
+
+    def _with_diagnostic_provider(self):
+        value = self._provider_options(
+            types.TEXT_DOCUMENT_DIAGNOSTIC,
+            default=types.DiagnosticOptions(
+                inter_file_dependencies=False, workspace_diagnostics=False
+            ),
+        )
+        if value is not None:
+            value.workspace_diagnostics = types.WORKSPACE_DIAGNOSTIC in self.features
+            self.server_cap.diagnostic_provider = value
+        return self
+
+    def _with_inline_value_provider(self):
+        value = self._provider_options(types.TEXT_DOCUMENT_INLINE_VALUE, default=True)
+        if value is not None:
+            self.server_cap.inline_value_provider = value
+        return self
+
+    def _with_position_encodings(self):
+        self.server_cap.position_encoding = types.PositionEncodingKind.Utf16
+
+        general = self.client_capabilities.general
+        if general is None:
+            return self
+
+        encodings = general.position_encodings
+        if encodings is None:
+            return self
+
+        if types.PositionEncodingKind.Utf16 in encodings:
+            return self
+
+        if types.PositionEncodingKind.Utf32 in encodings:
+            self.server_cap.position_encoding = types.PositionEncodingKind.Utf32
+            return self
+
+        if types.PositionEncodingKind.Utf8 in encodings:
+            self.server_cap.position_encoding = types.PositionEncodingKind.Utf8
+            return self
+
+        logger.warning(f"Unknown `PositionEncoding`s: {encodings}")
+
+        return self
+
+    def _build(self):
+        return self.server_cap
+
+    def build(self):
+        return (
+            self._with_text_document_sync()
+            ._with_notebook_document_sync()
+            ._with_completion()
+            ._with_hover()
+            ._with_signature_help()
+            ._with_declaration()
+            ._with_definition()
+            ._with_type_definition()
+            ._with_inlay_hints()
+            ._with_implementation()
+            ._with_references()
+            ._with_document_highlight()
+            ._with_document_symbol()
+            ._with_code_action()
+            ._with_code_lens()
+            ._with_document_link()
+            ._with_color()
+            ._with_document_formatting()
+            ._with_document_range_formatting()
+            ._with_document_on_type_formatting()
+            ._with_rename()
+            ._with_folding_range()
+            ._with_execute_command()
+            ._with_selection_range()
+            ._with_call_hierarchy()
+            ._with_type_hierarchy()
+            ._with_semantic_tokens()
+            ._with_linked_editing_range()
+            ._with_moniker()
+            ._with_workspace_symbol()
+            ._with_workspace_capabilities()
+            ._with_diagnostic_provider()
+            ._with_inline_value_provider()
+            ._with_position_encodings()
+            ._build()
+        )
diff --git a/server/libs/pygls/client.py b/server/libs/pygls/client.py
new file mode 100644
index 0000000..577f05e
--- /dev/null
+++ b/server/libs/pygls/client.py
@@ -0,0 +1,176 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved.                      #
+# See ThirdPartyNotices.txt in the project root for additional notices.    #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import asyncio
+import logging
+import re
+from threading import Event
+from typing import Any
+from typing import Callable
+from typing import List
+from typing import Optional
+from typing import Type
+from typing import Union
+
+from cattrs import Converter
+
+from pygls.exceptions import PyglsError, JsonRpcException
+from pygls.protocol import JsonRPCProtocol, default_converter
+
+
+logger = logging.getLogger(__name__)
+
+
+async def aio_readline(stop_event, reader, message_handler):
+    CONTENT_LENGTH_PATTERN = re.compile(rb"^Content-Length: (\d+)\r\n$")
+
+    # Initialize message buffer
+    message = []
+    content_length = 0
+
+    while not stop_event.is_set():
+        # Read a header line
+        header = await reader.readline()
+        if not header:
+            break
+        message.append(header)
+
+        # Extract content length if possible
+        if not content_length:
+            match = CONTENT_LENGTH_PATTERN.fullmatch(header)
+            if match:
+                content_length = int(match.group(1))
+                logger.debug("Content length: %s", content_length)
+
+        # Check if all headers have been read (as indicated by an empty line \r\n)
+        if content_length and not header.strip():
+            # Read body
+            body = await reader.readexactly(content_length)
+            if not body:
+                break
+            message.append(body)
+
+            # Pass message to protocol
+            message_handler(b"".join(message))
+
+            # Reset the buffer
+            message = []
+            content_length = 0
+
+
+class JsonRPCClient:
+    """Base JSON-RPC client."""
+
+    def __init__(
+        self,
+        protocol_cls: Type[JsonRPCProtocol] = JsonRPCProtocol,
+        converter_factory: Callable[[], Converter] = default_converter,
+    ):
+        # Strictly speaking `JsonRPCProtocol` wants a `LanguageServer`, not a
+        # `JsonRPCClient`. However there similar enough for our purposes, which is
+        # that this client will mostly be used in testing contexts.
+        self.protocol = protocol_cls(self, converter_factory())  # type: ignore
+
+        self._server: Optional[asyncio.subprocess.Process] = None
+        self._stop_event = Event()
+        self._async_tasks: List[asyncio.Task] = []
+
+    @property
+    def stopped(self) -> bool:
+        """Return ``True`` if the client has been stopped."""
+        return self._stop_event.is_set()
+
+    def feature(
+        self,
+        feature_name: str,
+        options: Optional[Any] = None,
+    ):
+        """Decorator used to register LSP features.
+
+        Example
+        -------
+        ::
+
+           import logging
+           from pygls.client import JsonRPCClient
+
+           ls = JsonRPCClient()
+
+           @ls.feature('window/logMessage')
+           def completions(ls, params):
+               logging.info("%s", params.message)
+        """
+        return self.protocol.fm.feature(feature_name, options)
+
+    async def start_io(self, cmd: str, *args, **kwargs):
+        """Start the given server and communicate with it over stdio."""
+
+        logger.debug("Starting server process: %s", " ".join([cmd, *args]))
+        server = await asyncio.create_subprocess_exec(
+            cmd,
+            *args,
+            stdout=asyncio.subprocess.PIPE,
+            stdin=asyncio.subprocess.PIPE,
+            stderr=asyncio.subprocess.PIPE,
+            **kwargs,
+        )
+
+        self.protocol.connection_made(server.stdin)  # type: ignore
+        connection = asyncio.create_task(
+            aio_readline(self._stop_event, server.stdout, self.protocol.data_received)
+        )
+        notify_exit = asyncio.create_task(self._server_exit())
+
+        self._server = server
+        self._async_tasks.extend([connection, notify_exit])
+
+    async def _server_exit(self):
+        if self._server is not None:
+            await self._server.wait()
+            logger.debug(
+                "Server process %s exited with return code: %s",
+                self._server.pid,
+                self._server.returncode,
+            )
+            await self.server_exit(self._server)
+            self._stop_event.set()
+
+    async def server_exit(self, server: asyncio.subprocess.Process):
+        """Called when the server process exits."""
+
+    def _report_server_error(
+        self, error: Exception, source: Union[PyglsError, JsonRpcException]
+    ):
+        try:
+            self.report_server_error(error, source)
+        except Exception:
+            logger.error("Unable to report error", exc_info=True)
+
+    def report_server_error(
+        self, error: Exception, source: Union[PyglsError, JsonRpcException]
+    ):
+        """Called when the server does something unexpected e.g. respond with malformed
+        JSON."""
+
+    async def stop(self):
+        self._stop_event.set()
+
+        if self._server is not None and self._server.returncode is None:
+            logger.debug("Terminating server process: %s", self._server.pid)
+            self._server.terminate()
+
+        if len(self._async_tasks) > 0:
+            await asyncio.gather(*self._async_tasks)
diff --git a/server/libs/pygls/constants.py b/server/libs/pygls/constants.py
new file mode 100644
index 0000000..ec2fa09
--- /dev/null
+++ b/server/libs/pygls/constants.py
@@ -0,0 +1,26 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved.                      #
+# See ThirdPartyNotices.txt in the project root for additional notices.    #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+
+# Dynamically assigned attributes
+ATTR_EXECUTE_IN_THREAD = "execute_in_thread"
+ATTR_COMMAND_TYPE = "command"
+ATTR_FEATURE_TYPE = "feature"
+ATTR_REGISTERED_NAME = "reg_name"
+ATTR_REGISTERED_TYPE = "reg_type"
+
+# Parameters
+PARAM_LS = "ls"
diff --git a/server/libs/pygls/exceptions.py b/server/libs/pygls/exceptions.py
new file mode 100644
index 0000000..5faf269
--- /dev/null
+++ b/server/libs/pygls/exceptions.py
@@ -0,0 +1,215 @@
+############################################################################
+# Original work Copyright 2018 Palantir Technologies, Inc.                 #
+# Original work licensed under the MIT License.                            #
+# See ThirdPartyNotices.txt in the project root for license information.   #
+# All modifications Copyright (c) Open Law Library. All rights reserved.   #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import traceback
+from typing import Set
+from typing import Type
+from lsprotocol.types import ResponseError
+
+
+class JsonRpcException(Exception):
+    """A class used as a base class for json rpc exceptions."""
+
+    def __init__(self, message=None, code=None, data=None):
+        message = message or getattr(self.__class__, "MESSAGE")
+        super().__init__(message)
+        self.message = message
+        self.code = code or getattr(self.__class__, "CODE")
+        self.data = data
+
+    def __eq__(self, other):
+        return (
+            isinstance(other, self.__class__)
+            and self.code == other.code
+            and self.message == other.message
+        )
+
+    def __hash__(self):
+        return hash((self.code, self.message))
+
+    @staticmethod
+    def from_error(error):
+        for exc_class in _EXCEPTIONS:
+            if exc_class.supports_code(error.code):
+                return exc_class(
+                    code=error.code, message=error.message, data=error.data
+                )
+
+        return JsonRpcException(code=error.code, message=error.message, data=error.data)
+
+    @classmethod
+    def supports_code(cls, code):
+        # Defaults to UnknownErrorCode
+        return getattr(cls, "CODE", -32001) == code
+
+    def to_response_error(self) -> ResponseError:
+        return ResponseError(code=self.code, message=self.message, data=self.data)
+
+
+class JsonRpcInternalError(JsonRpcException):
+    CODE = -32603
+    MESSAGE = "Internal Error"
+
+    @classmethod
+    def of(cls, exc_info):
+        exc_type, exc_value, exc_tb = exc_info
+        return cls(
+            message="".join(
+                traceback.format_exception_only(exc_type, exc_value)
+            ).strip(),
+            data={"traceback": traceback.format_tb(exc_tb)},
+        )
+
+
+class JsonRpcInvalidParams(JsonRpcException):
+    CODE = -32602
+    MESSAGE = "Invalid Params"
+
+
+class JsonRpcInvalidRequest(JsonRpcException):
+    CODE = -32600
+    MESSAGE = "Invalid Request"
+
+
+class JsonRpcMethodNotFound(JsonRpcException):
+    CODE = -32601
+    MESSAGE = "Method Not Found"
+
+    @classmethod
+    def of(cls, method):
+        return cls(message=cls.MESSAGE + ": " + method)
+
+
+class JsonRpcParseError(JsonRpcException):
+    CODE = -32700
+    MESSAGE = "Parse Error"
+
+
+class JsonRpcRequestCancelled(JsonRpcException):
+    CODE = -32800
+    MESSAGE = "Request Cancelled"
+
+
+class JsonRpcContentModified(JsonRpcException):
+    CODE = -32801
+    MESSAGE = "Content Modified"
+
+
+class JsonRpcServerNotInitialized(JsonRpcException):
+    CODE = -32002
+    MESSAGE = "ServerNotInitialized"
+
+
+class JsonRpcUnknownErrorCode(JsonRpcException):
+    CODE = -32001
+    MESSAGE = "UnknownErrorCode"
+
+
+class JsonRpcReservedErrorRangeStart(JsonRpcException):
+    CODE = -32099
+    MESSAGE = "jsonrpcReservedErrorRangeStart"
+
+
+class JsonRpcReservedErrorRangeEnd(JsonRpcException):
+    CODE = -32000
+    MESSAGE = "jsonrpcReservedErrorRangeEnd"
+
+
+class LspReservedErrorRangeStart(JsonRpcException):
+    CODE = -32899
+    MESSAGE = "lspReservedErrorRangeStart"
+
+
+class LspReservedErrorRangeEnd(JsonRpcException):
+    CODE = -32800
+    MESSAGE = "lspReservedErrorRangeEnd"
+
+
+class JsonRpcServerError(JsonRpcException):
+    def __init__(self, message, code, data=None):
+        if not _is_server_error_code(code):
+            raise ValueError("Error code should be in range -32099 - -32000")
+        super().__init__(message=message, code=code, data=data)
+
+    @classmethod
+    def supports_code(cls, code):
+        return _is_server_error_code(code)
+
+
+def _is_server_error_code(code):
+    return -32099 <= code <= -32000
+
+
+_EXCEPTIONS: Set[Type[JsonRpcException]] = {
+    JsonRpcInternalError,
+    JsonRpcInvalidParams,
+    JsonRpcInvalidRequest,
+    JsonRpcMethodNotFound,
+    JsonRpcParseError,
+    JsonRpcRequestCancelled,
+    JsonRpcServerError,
+}
+
+
+class PyglsError(Exception):
+    pass
+
+
+class CommandAlreadyRegisteredError(PyglsError):
+    def __init__(self, command_name):
+        self.command_name = command_name
+
+    def __repr__(self):
+        return f'Command "{self.command_name}" is already registered.'
+
+
+class FeatureAlreadyRegisteredError(PyglsError):
+    def __init__(self, feature_name):
+        self.feature_name = feature_name
+
+    def __repr__(self):
+        return f'Feature "{self.feature_name}" is already registered.'
+
+
+class FeatureRequestError(PyglsError):
+    pass
+
+
+class FeatureNotificationError(PyglsError):
+    pass
+
+
+class MethodTypeNotRegisteredError(PyglsError):
+    def __init__(self, name):
+        self.name = name
+
+    def __repr__(self):
+        return f'"{self.name}" is not added to `pygls.lsp.LSP_METHODS_MAP`.'
+
+
+class ThreadDecoratorError(PyglsError):
+    pass
+
+
+class ValidationError(PyglsError):
+    def __init__(self, errors=None):
+        self.errors = errors or []
+
+    def __repr__(self):
+        opt_errs = "\n-".join([e for e in self.errors])
+        return f"Missing options: {opt_errs}"
diff --git a/server/libs/pygls/feature_manager.py b/server/libs/pygls/feature_manager.py
new file mode 100644
index 0000000..d00283a
--- /dev/null
+++ b/server/libs/pygls/feature_manager.py
@@ -0,0 +1,244 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved.                      #
+# See ThirdPartyNotices.txt in the project root for additional notices.    #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import asyncio
+import functools
+import inspect
+import itertools
+import logging
+from typing import Any, Callable, Dict, Optional, get_type_hints
+
+from pygls.constants import (
+    ATTR_COMMAND_TYPE,
+    ATTR_EXECUTE_IN_THREAD,
+    ATTR_FEATURE_TYPE,
+    ATTR_REGISTERED_NAME,
+    ATTR_REGISTERED_TYPE,
+    PARAM_LS,
+)
+from pygls.exceptions import (
+    CommandAlreadyRegisteredError,
+    FeatureAlreadyRegisteredError,
+    ThreadDecoratorError,
+    ValidationError,
+)
+from pygls.lsp import get_method_options_type, is_instance
+
+logger = logging.getLogger(__name__)
+
+
+def assign_help_attrs(f, reg_name, reg_type):
+    setattr(f, ATTR_REGISTERED_NAME, reg_name)
+    setattr(f, ATTR_REGISTERED_TYPE, reg_type)
+
+
+def assign_thread_attr(f):
+    setattr(f, ATTR_EXECUTE_IN_THREAD, True)
+
+
+def get_help_attrs(f):
+    return getattr(f, ATTR_REGISTERED_NAME, None), getattr(
+        f, ATTR_REGISTERED_TYPE, None
+    )
+
+
+def has_ls_param_or_annotation(f, annotation):
+    """Returns true if callable has first parameter named `ls` or type of
+    annotation"""
+    try:
+        sig = inspect.signature(f)
+        first_p = next(itertools.islice(sig.parameters.values(), 0, 1))
+        return first_p.name == PARAM_LS or get_type_hints(f)[first_p.name] == annotation
+    except Exception:
+        return False
+
+
+def is_thread_function(f):
+    return getattr(f, ATTR_EXECUTE_IN_THREAD, False)
+
+
+def wrap_with_server(f, server):
+    """Returns a new callable/coroutine with server as first argument."""
+    if not has_ls_param_or_annotation(f, type(server)):
+        return f
+
+    if asyncio.iscoroutinefunction(f):
+
+        async def wrapped(*args, **kwargs):
+            return await f(server, *args, **kwargs)
+
+    else:
+        wrapped = functools.partial(f, server)
+        if is_thread_function(f):
+            assign_thread_attr(wrapped)
+
+    return wrapped
+
+
+class FeatureManager:
+    """A class for managing server features.
+
+    Attributes:
+        _builtin_features(dict): Predefined set of lsp methods
+        _feature_options(dict): Registered feature's options
+        _features(dict): Registered features
+        _commands(dict): Registered commands
+        server(LanguageServer): Reference to the language server
+                                If passed, server will be passed to registered
+                                features/commands with first parameter:
+                                    1. ls - parameter naming convention
+                                    2. name: LanguageServer - add typings
+    """
+
+    def __init__(self, server=None, converter=None):
+        self._builtin_features = {}
+        self._feature_options = {}
+        self._features = {}
+        self._commands = {}
+        self.server = server
+        self.converter = converter
+
+    def add_builtin_feature(self, feature_name: str, func: Callable) -> None:
+        """Registers builtin (predefined) feature."""
+        self._builtin_features[feature_name] = func
+        logger.info("Registered builtin feature %s", feature_name)
+
+    @property
+    def builtin_features(self) -> Dict:
+        """Returns server builtin features."""
+        return self._builtin_features
+
+    def command(self, command_name: str) -> Callable:
+        """Decorator used to register custom commands.
+
+        Example:
+            @ls.command('myCustomCommand')
+        """
+
+        def decorator(f):
+            # Validate
+            if command_name is None or command_name.strip() == "":
+                logger.error("Missing command name.")
+                raise ValidationError("Command name is required.")
+
+            # Check if not already registered
+            if command_name in self._commands:
+                logger.error('Command "%s" is already registered.', command_name)
+                raise CommandAlreadyRegisteredError(command_name)
+
+            assign_help_attrs(f, command_name, ATTR_COMMAND_TYPE)
+
+            wrapped = wrap_with_server(f, self.server)
+            # Assign help attributes for thread decorator
+            assign_help_attrs(wrapped, command_name, ATTR_COMMAND_TYPE)
+
+            self._commands[command_name] = wrapped
+
+            logger.info('Command "%s" is successfully registered.', command_name)
+
+            return f
+
+        return decorator
+
+    @property
+    def commands(self) -> Dict:
+        """Returns registered custom commands."""
+        return self._commands
+
+    def feature(
+        self,
+        feature_name: str,
+        options: Optional[Any] = None,
+    ) -> Callable:
+        """Decorator used to register LSP features.
+
+        Example:
+            @ls.feature('textDocument/completion', CompletionItems(trigger_characters=['.']))
+        """
+
+        def decorator(f):
+            # Validate
+            if feature_name is None or feature_name.strip() == "":
+                logger.error("Missing feature name.")
+                raise ValidationError("Feature name is required.")
+
+            # Add feature if not exists
+            if feature_name in self._features:
+                logger.error('Feature "%s" is already registered.', feature_name)
+                raise FeatureAlreadyRegisteredError(feature_name)
+
+            assign_help_attrs(f, feature_name, ATTR_FEATURE_TYPE)
+
+            wrapped = wrap_with_server(f, self.server)
+            # Assign help attributes for thread decorator
+            assign_help_attrs(wrapped, feature_name, ATTR_FEATURE_TYPE)
+
+            self._features[feature_name] = wrapped
+
+            if options:
+                options_type = get_method_options_type(feature_name)
+                if options_type and not is_instance(
+                    self.converter, options, options_type
+                ):
+                    raise TypeError(
+                        (
+                            f'Options of method "{feature_name}"'
+                            f" should be instance of type {options_type}"
+                        )
+                    )
+                self._feature_options[feature_name] = options
+
+            logger.info('Registered "%s" with options "%s"', feature_name, options)
+
+            return f
+
+        return decorator
+
+    @property
+    def feature_options(self) -> Dict:
+        """Returns feature options for registered features."""
+        return self._feature_options
+
+    @property
+    def features(self) -> Dict:
+        """Returns registered features"""
+        return self._features
+
+    def thread(self) -> Callable:
+        """Decorator that mark function to execute it in a thread."""
+
+        def decorator(f):
+            if asyncio.iscoroutinefunction(f):
+                raise ThreadDecoratorError(
+                    f'Thread decorator cannot be used with async functions "{f.__name__}"'
+                )
+
+            # Allow any decorator order
+            try:
+                reg_name = getattr(f, ATTR_REGISTERED_NAME)
+                reg_type = getattr(f, ATTR_REGISTERED_TYPE)
+
+                if reg_type is ATTR_FEATURE_TYPE:
+                    assign_thread_attr(self.features[reg_name])
+                elif reg_type is ATTR_COMMAND_TYPE:
+                    assign_thread_attr(self.commands[reg_name])
+
+            except AttributeError:
+                assign_thread_attr(f)
+
+            return f
+
+        return decorator
diff --git a/server/libs/pygls/lsp/__init__.py b/server/libs/pygls/lsp/__init__.py
new file mode 100644
index 0000000..aa0725d
--- /dev/null
+++ b/server/libs/pygls/lsp/__init__.py
@@ -0,0 +1,139 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved.                      #
+# See ThirdPartyNotices.txt in the project root for additional notices.    #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import cattrs
+from typing import Any, Callable, List, Optional, Union
+
+from lsprotocol.types import (
+    ALL_TYPES_MAP,
+    METHOD_TO_TYPES,
+    TEXT_DOCUMENT_DID_SAVE,
+    TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
+    TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA,
+    TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE,
+    WORKSPACE_DID_CREATE_FILES,
+    WORKSPACE_DID_DELETE_FILES,
+    WORKSPACE_DID_RENAME_FILES,
+    WORKSPACE_WILL_CREATE_FILES,
+    WORKSPACE_WILL_DELETE_FILES,
+    WORKSPACE_WILL_RENAME_FILES,
+    FileOperationRegistrationOptions,
+    SaveOptions,
+    SemanticTokensLegend,
+    SemanticTokensRegistrationOptions,
+    ShowDocumentResult,
+)
+
+from pygls.exceptions import MethodTypeNotRegisteredError
+
+ConfigCallbackType = Callable[[List[Any]], None]
+ShowDocumentCallbackType = Callable[[ShowDocumentResult], None]
+
+METHOD_TO_OPTIONS = {
+    TEXT_DOCUMENT_DID_SAVE: SaveOptions,
+    TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL: Union[
+        SemanticTokensLegend, SemanticTokensRegistrationOptions
+    ],
+    TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL_DELTA: Union[
+        SemanticTokensLegend, SemanticTokensRegistrationOptions
+    ],
+    TEXT_DOCUMENT_SEMANTIC_TOKENS_RANGE: Union[
+        SemanticTokensLegend, SemanticTokensRegistrationOptions
+    ],
+    WORKSPACE_DID_CREATE_FILES: FileOperationRegistrationOptions,
+    WORKSPACE_DID_DELETE_FILES: FileOperationRegistrationOptions,
+    WORKSPACE_DID_RENAME_FILES: FileOperationRegistrationOptions,
+    WORKSPACE_WILL_CREATE_FILES: FileOperationRegistrationOptions,
+    WORKSPACE_WILL_DELETE_FILES: FileOperationRegistrationOptions,
+    WORKSPACE_WILL_RENAME_FILES: FileOperationRegistrationOptions,
+}
+
+
+def get_method_registration_options_type(
+    method_name, lsp_methods_map=METHOD_TO_TYPES
+) -> Optional[Any]:
+    """The type corresponding with a method's options when dynamically registering
+    capability for it."""
+
+    try:
+        return lsp_methods_map[method_name][3]
+    except KeyError:
+        raise MethodTypeNotRegisteredError(method_name)
+
+
+def get_method_options_type(
+    method_name, lsp_options_map=METHOD_TO_OPTIONS, lsp_methods_map=METHOD_TO_TYPES
+) -> Optional[Any]:
+    """Return the type corresponding with a method's ``ServerCapabilities`` fields.
+
+    In the majority of cases this simply means returning the ``Options``
+    type, which we can easily derive from the method's
+    ``RegistrationOptions`` type.
+
+    However, where the options are more involved (such as semantic tokens) and
+    ``pygls`` does some extra work to help derive the options for the user the type
+    has to be provided via the ``lsp_options_map``
+
+    Arguments:
+        method_name:
+            The lsp method name to retrieve the options for
+
+        lsp_options_map:
+            The map used to override the default options type finding behavior
+
+        lsp_methods_map:
+            The standard map used to look up the various method types.
+    """
+
+    options_type = lsp_options_map.get(method_name, None)
+    if options_type is not None:
+        return options_type
+
+    registration_type = get_method_registration_options_type(
+        method_name, lsp_methods_map
+    )
+    if registration_type is None:
+        return None
+
+    type_name = registration_type.__name__.replace("Registration", "")
+    options_type = ALL_TYPES_MAP.get(type_name, None)
+
+    if options_type is None:
+        raise MethodTypeNotRegisteredError(method_name)
+
+    return options_type
+
+
+def get_method_params_type(method_name, lsp_methods_map=METHOD_TO_TYPES):
+    try:
+        return lsp_methods_map[method_name][2]
+    except KeyError:
+        raise MethodTypeNotRegisteredError(method_name)
+
+
+def get_method_return_type(method_name, lsp_methods_map=METHOD_TO_TYPES):
+    try:
+        return lsp_methods_map[method_name][1]
+    except KeyError:
+        raise MethodTypeNotRegisteredError(method_name)
+
+
+def is_instance(cv: cattrs.Converter, o, t):
+    try:
+        cv.unstructure(o, t)
+        return True
+    except TypeError:
+        return False
diff --git a/server/libs/pygls/lsp/client.py b/server/libs/pygls/lsp/client.py
new file mode 100644
index 0000000..c877fdb
--- /dev/null
+++ b/server/libs/pygls/lsp/client.py
@@ -0,0 +1,1961 @@
+# GENERATED FROM scripts/gen-client.py -- DO NOT EDIT
+# flake8: noqa
+from concurrent.futures import Future
+from lsprotocol import types
+from pygls.client import JsonRPCClient
+from pygls.protocol import LanguageServerProtocol
+from pygls.protocol import default_converter
+from typing import Any
+from typing import Callable
+from typing import List
+from typing import Optional
+from typing import Union
+
+
+class BaseLanguageClient(JsonRPCClient):
+
+    def __init__(
+        self,
+        name: str,
+        version: str,
+        protocol_cls=LanguageServerProtocol,
+        converter_factory=default_converter,
+        **kwargs,
+    ):
+        self.name = name
+        self.version = version
+        super().__init__(protocol_cls, converter_factory, **kwargs)
+
+    def call_hierarchy_incoming_calls(
+        self,
+        params: types.CallHierarchyIncomingCallsParams,
+        callback: Optional[Callable[[Optional[List[types.CallHierarchyIncomingCall]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`callHierarchy/incomingCalls` request.
+
+        A request to resolve the incoming calls for a given `CallHierarchyItem`.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("callHierarchy/incomingCalls", params, callback)
+
+    async def call_hierarchy_incoming_calls_async(
+        self,
+        params: types.CallHierarchyIncomingCallsParams,
+    ) -> Optional[List[types.CallHierarchyIncomingCall]]:
+        """Make a :lsp:`callHierarchy/incomingCalls` request.
+
+        A request to resolve the incoming calls for a given `CallHierarchyItem`.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("callHierarchy/incomingCalls", params)
+
+    def call_hierarchy_outgoing_calls(
+        self,
+        params: types.CallHierarchyOutgoingCallsParams,
+        callback: Optional[Callable[[Optional[List[types.CallHierarchyOutgoingCall]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`callHierarchy/outgoingCalls` request.
+
+        A request to resolve the outgoing calls for a given `CallHierarchyItem`.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("callHierarchy/outgoingCalls", params, callback)
+
+    async def call_hierarchy_outgoing_calls_async(
+        self,
+        params: types.CallHierarchyOutgoingCallsParams,
+    ) -> Optional[List[types.CallHierarchyOutgoingCall]]:
+        """Make a :lsp:`callHierarchy/outgoingCalls` request.
+
+        A request to resolve the outgoing calls for a given `CallHierarchyItem`.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("callHierarchy/outgoingCalls", params)
+
+    def code_action_resolve(
+        self,
+        params: types.CodeAction,
+        callback: Optional[Callable[[types.CodeAction], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`codeAction/resolve` request.
+
+        Request to resolve additional information for a given code action.The request's
+        parameter is of type {@link CodeAction} the response
+        is of type {@link CodeAction} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("codeAction/resolve", params, callback)
+
+    async def code_action_resolve_async(
+        self,
+        params: types.CodeAction,
+    ) -> types.CodeAction:
+        """Make a :lsp:`codeAction/resolve` request.
+
+        Request to resolve additional information for a given code action.The request's
+        parameter is of type {@link CodeAction} the response
+        is of type {@link CodeAction} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("codeAction/resolve", params)
+
+    def code_lens_resolve(
+        self,
+        params: types.CodeLens,
+        callback: Optional[Callable[[types.CodeLens], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`codeLens/resolve` request.
+
+        A request to resolve a command for a given code lens.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("codeLens/resolve", params, callback)
+
+    async def code_lens_resolve_async(
+        self,
+        params: types.CodeLens,
+    ) -> types.CodeLens:
+        """Make a :lsp:`codeLens/resolve` request.
+
+        A request to resolve a command for a given code lens.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("codeLens/resolve", params)
+
+    def completion_item_resolve(
+        self,
+        params: types.CompletionItem,
+        callback: Optional[Callable[[types.CompletionItem], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`completionItem/resolve` request.
+
+        Request to resolve additional information for a given completion item.The request's
+        parameter is of type {@link CompletionItem} the response
+        is of type {@link CompletionItem} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("completionItem/resolve", params, callback)
+
+    async def completion_item_resolve_async(
+        self,
+        params: types.CompletionItem,
+    ) -> types.CompletionItem:
+        """Make a :lsp:`completionItem/resolve` request.
+
+        Request to resolve additional information for a given completion item.The request's
+        parameter is of type {@link CompletionItem} the response
+        is of type {@link CompletionItem} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("completionItem/resolve", params)
+
+    def document_link_resolve(
+        self,
+        params: types.DocumentLink,
+        callback: Optional[Callable[[types.DocumentLink], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`documentLink/resolve` request.
+
+        Request to resolve additional information for a given document link. The request's
+        parameter is of type {@link DocumentLink} the response
+        is of type {@link DocumentLink} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("documentLink/resolve", params, callback)
+
+    async def document_link_resolve_async(
+        self,
+        params: types.DocumentLink,
+    ) -> types.DocumentLink:
+        """Make a :lsp:`documentLink/resolve` request.
+
+        Request to resolve additional information for a given document link. The request's
+        parameter is of type {@link DocumentLink} the response
+        is of type {@link DocumentLink} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("documentLink/resolve", params)
+
+    def initialize(
+        self,
+        params: types.InitializeParams,
+        callback: Optional[Callable[[types.InitializeResult], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`initialize` request.
+
+        The initialize request is sent from the client to the server.
+        It is sent once as the request after starting up the server.
+        The requests parameter is of type {@link InitializeParams}
+        the response if of type {@link InitializeResult} of a Thenable that
+        resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("initialize", params, callback)
+
+    async def initialize_async(
+        self,
+        params: types.InitializeParams,
+    ) -> types.InitializeResult:
+        """Make a :lsp:`initialize` request.
+
+        The initialize request is sent from the client to the server.
+        It is sent once as the request after starting up the server.
+        The requests parameter is of type {@link InitializeParams}
+        the response if of type {@link InitializeResult} of a Thenable that
+        resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("initialize", params)
+
+    def inlay_hint_resolve(
+        self,
+        params: types.InlayHint,
+        callback: Optional[Callable[[types.InlayHint], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`inlayHint/resolve` request.
+
+        A request to resolve additional properties for an inlay hint.
+        The request's parameter is of type {@link InlayHint}, the response is
+        of type {@link InlayHint} or a Thenable that resolves to such.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("inlayHint/resolve", params, callback)
+
+    async def inlay_hint_resolve_async(
+        self,
+        params: types.InlayHint,
+    ) -> types.InlayHint:
+        """Make a :lsp:`inlayHint/resolve` request.
+
+        A request to resolve additional properties for an inlay hint.
+        The request's parameter is of type {@link InlayHint}, the response is
+        of type {@link InlayHint} or a Thenable that resolves to such.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("inlayHint/resolve", params)
+
+    def shutdown(
+        self,
+        params: None,
+        callback: Optional[Callable[[None], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`shutdown` request.
+
+        A shutdown request is sent from the client to the server.
+        It is sent once when the client decides to shutdown the
+        server. The only notification that is sent after a shutdown request
+        is the exit event.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("shutdown", params, callback)
+
+    async def shutdown_async(
+        self,
+        params: None,
+    ) -> None:
+        """Make a :lsp:`shutdown` request.
+
+        A shutdown request is sent from the client to the server.
+        It is sent once when the client decides to shutdown the
+        server. The only notification that is sent after a shutdown request
+        is the exit event.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("shutdown", params)
+
+    def text_document_code_action(
+        self,
+        params: types.CodeActionParams,
+        callback: Optional[Callable[[Optional[List[Union[types.Command, types.CodeAction]]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/codeAction` request.
+
+        A request to provide commands for the given text document and range.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/codeAction", params, callback)
+
+    async def text_document_code_action_async(
+        self,
+        params: types.CodeActionParams,
+    ) -> Optional[List[Union[types.Command, types.CodeAction]]]:
+        """Make a :lsp:`textDocument/codeAction` request.
+
+        A request to provide commands for the given text document and range.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/codeAction", params)
+
+    def text_document_code_lens(
+        self,
+        params: types.CodeLensParams,
+        callback: Optional[Callable[[Optional[List[types.CodeLens]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/codeLens` request.
+
+        A request to provide code lens for the given text document.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/codeLens", params, callback)
+
+    async def text_document_code_lens_async(
+        self,
+        params: types.CodeLensParams,
+    ) -> Optional[List[types.CodeLens]]:
+        """Make a :lsp:`textDocument/codeLens` request.
+
+        A request to provide code lens for the given text document.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/codeLens", params)
+
+    def text_document_color_presentation(
+        self,
+        params: types.ColorPresentationParams,
+        callback: Optional[Callable[[List[types.ColorPresentation]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/colorPresentation` request.
+
+        A request to list all presentation for a color. The request's
+        parameter is of type {@link ColorPresentationParams} the
+        response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/colorPresentation", params, callback)
+
+    async def text_document_color_presentation_async(
+        self,
+        params: types.ColorPresentationParams,
+    ) -> List[types.ColorPresentation]:
+        """Make a :lsp:`textDocument/colorPresentation` request.
+
+        A request to list all presentation for a color. The request's
+        parameter is of type {@link ColorPresentationParams} the
+        response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/colorPresentation", params)
+
+    def text_document_completion(
+        self,
+        params: types.CompletionParams,
+        callback: Optional[Callable[[Union[List[types.CompletionItem], types.CompletionList, None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/completion` request.
+
+        Request to request completion at a given text document position. The request's
+        parameter is of type {@link TextDocumentPosition} the response
+        is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList}
+        or a Thenable that resolves to such.
+
+        The request can delay the computation of the {@link CompletionItem.detail `detail`}
+        and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve`
+        request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
+        `filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/completion", params, callback)
+
+    async def text_document_completion_async(
+        self,
+        params: types.CompletionParams,
+    ) -> Union[List[types.CompletionItem], types.CompletionList, None]:
+        """Make a :lsp:`textDocument/completion` request.
+
+        Request to request completion at a given text document position. The request's
+        parameter is of type {@link TextDocumentPosition} the response
+        is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList}
+        or a Thenable that resolves to such.
+
+        The request can delay the computation of the {@link CompletionItem.detail `detail`}
+        and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve`
+        request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
+        `filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/completion", params)
+
+    def text_document_declaration(
+        self,
+        params: types.DeclarationParams,
+        callback: Optional[Callable[[Union[types.Location, List[types.Location], List[types.LocationLink], None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/declaration` request.
+
+        A request to resolve the type definition locations of a symbol at a given text
+        document position. The request's parameter is of type {@link TextDocumentPositionParams}
+        the response is of type {@link Declaration} or a typed array of {@link DeclarationLink}
+        or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/declaration", params, callback)
+
+    async def text_document_declaration_async(
+        self,
+        params: types.DeclarationParams,
+    ) -> Union[types.Location, List[types.Location], List[types.LocationLink], None]:
+        """Make a :lsp:`textDocument/declaration` request.
+
+        A request to resolve the type definition locations of a symbol at a given text
+        document position. The request's parameter is of type {@link TextDocumentPositionParams}
+        the response is of type {@link Declaration} or a typed array of {@link DeclarationLink}
+        or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/declaration", params)
+
+    def text_document_definition(
+        self,
+        params: types.DefinitionParams,
+        callback: Optional[Callable[[Union[types.Location, List[types.Location], List[types.LocationLink], None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/definition` request.
+
+        A request to resolve the definition location of a symbol at a given text
+        document position. The request's parameter is of type {@link TextDocumentPosition}
+        the response is of either type {@link Definition} or a typed array of
+        {@link DefinitionLink} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/definition", params, callback)
+
+    async def text_document_definition_async(
+        self,
+        params: types.DefinitionParams,
+    ) -> Union[types.Location, List[types.Location], List[types.LocationLink], None]:
+        """Make a :lsp:`textDocument/definition` request.
+
+        A request to resolve the definition location of a symbol at a given text
+        document position. The request's parameter is of type {@link TextDocumentPosition}
+        the response is of either type {@link Definition} or a typed array of
+        {@link DefinitionLink} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/definition", params)
+
+    def text_document_diagnostic(
+        self,
+        params: types.DocumentDiagnosticParams,
+        callback: Optional[Callable[[Union[types.RelatedFullDocumentDiagnosticReport, types.RelatedUnchangedDocumentDiagnosticReport]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/diagnostic` request.
+
+        The document diagnostic request definition.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/diagnostic", params, callback)
+
+    async def text_document_diagnostic_async(
+        self,
+        params: types.DocumentDiagnosticParams,
+    ) -> Union[types.RelatedFullDocumentDiagnosticReport, types.RelatedUnchangedDocumentDiagnosticReport]:
+        """Make a :lsp:`textDocument/diagnostic` request.
+
+        The document diagnostic request definition.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/diagnostic", params)
+
+    def text_document_document_color(
+        self,
+        params: types.DocumentColorParams,
+        callback: Optional[Callable[[List[types.ColorInformation]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/documentColor` request.
+
+        A request to list all color symbols found in a given text document. The request's
+        parameter is of type {@link DocumentColorParams} the
+        response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/documentColor", params, callback)
+
+    async def text_document_document_color_async(
+        self,
+        params: types.DocumentColorParams,
+    ) -> List[types.ColorInformation]:
+        """Make a :lsp:`textDocument/documentColor` request.
+
+        A request to list all color symbols found in a given text document. The request's
+        parameter is of type {@link DocumentColorParams} the
+        response is of type {@link ColorInformation ColorInformation[]} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/documentColor", params)
+
+    def text_document_document_highlight(
+        self,
+        params: types.DocumentHighlightParams,
+        callback: Optional[Callable[[Optional[List[types.DocumentHighlight]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/documentHighlight` request.
+
+        Request to resolve a {@link DocumentHighlight} for a given
+        text document position. The request's parameter is of type {@link TextDocumentPosition}
+        the request response is an array of type {@link DocumentHighlight}
+        or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/documentHighlight", params, callback)
+
+    async def text_document_document_highlight_async(
+        self,
+        params: types.DocumentHighlightParams,
+    ) -> Optional[List[types.DocumentHighlight]]:
+        """Make a :lsp:`textDocument/documentHighlight` request.
+
+        Request to resolve a {@link DocumentHighlight} for a given
+        text document position. The request's parameter is of type {@link TextDocumentPosition}
+        the request response is an array of type {@link DocumentHighlight}
+        or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/documentHighlight", params)
+
+    def text_document_document_link(
+        self,
+        params: types.DocumentLinkParams,
+        callback: Optional[Callable[[Optional[List[types.DocumentLink]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/documentLink` request.
+
+        A request to provide document links
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/documentLink", params, callback)
+
+    async def text_document_document_link_async(
+        self,
+        params: types.DocumentLinkParams,
+    ) -> Optional[List[types.DocumentLink]]:
+        """Make a :lsp:`textDocument/documentLink` request.
+
+        A request to provide document links
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/documentLink", params)
+
+    def text_document_document_symbol(
+        self,
+        params: types.DocumentSymbolParams,
+        callback: Optional[Callable[[Union[List[types.SymbolInformation], List[types.DocumentSymbol], None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/documentSymbol` request.
+
+        A request to list all symbols found in a given text document. The request's
+        parameter is of type {@link TextDocumentIdentifier} the
+        response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/documentSymbol", params, callback)
+
+    async def text_document_document_symbol_async(
+        self,
+        params: types.DocumentSymbolParams,
+    ) -> Union[List[types.SymbolInformation], List[types.DocumentSymbol], None]:
+        """Make a :lsp:`textDocument/documentSymbol` request.
+
+        A request to list all symbols found in a given text document. The request's
+        parameter is of type {@link TextDocumentIdentifier} the
+        response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/documentSymbol", params)
+
+    def text_document_folding_range(
+        self,
+        params: types.FoldingRangeParams,
+        callback: Optional[Callable[[Optional[List[types.FoldingRange]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/foldingRange` request.
+
+        A request to provide folding ranges in a document. The request's
+        parameter is of type {@link FoldingRangeParams}, the
+        response is of type {@link FoldingRangeList} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/foldingRange", params, callback)
+
+    async def text_document_folding_range_async(
+        self,
+        params: types.FoldingRangeParams,
+    ) -> Optional[List[types.FoldingRange]]:
+        """Make a :lsp:`textDocument/foldingRange` request.
+
+        A request to provide folding ranges in a document. The request's
+        parameter is of type {@link FoldingRangeParams}, the
+        response is of type {@link FoldingRangeList} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/foldingRange", params)
+
+    def text_document_formatting(
+        self,
+        params: types.DocumentFormattingParams,
+        callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/formatting` request.
+
+        A request to format a whole document.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/formatting", params, callback)
+
+    async def text_document_formatting_async(
+        self,
+        params: types.DocumentFormattingParams,
+    ) -> Optional[List[types.TextEdit]]:
+        """Make a :lsp:`textDocument/formatting` request.
+
+        A request to format a whole document.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/formatting", params)
+
+    def text_document_hover(
+        self,
+        params: types.HoverParams,
+        callback: Optional[Callable[[Optional[types.Hover]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/hover` request.
+
+        Request to request hover information at a given text document position. The request's
+        parameter is of type {@link TextDocumentPosition} the response is of
+        type {@link Hover} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/hover", params, callback)
+
+    async def text_document_hover_async(
+        self,
+        params: types.HoverParams,
+    ) -> Optional[types.Hover]:
+        """Make a :lsp:`textDocument/hover` request.
+
+        Request to request hover information at a given text document position. The request's
+        parameter is of type {@link TextDocumentPosition} the response is of
+        type {@link Hover} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/hover", params)
+
+    def text_document_implementation(
+        self,
+        params: types.ImplementationParams,
+        callback: Optional[Callable[[Union[types.Location, List[types.Location], List[types.LocationLink], None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/implementation` request.
+
+        A request to resolve the implementation locations of a symbol at a given text
+        document position. The request's parameter is of type {@link TextDocumentPositionParams}
+        the response is of type {@link Definition} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/implementation", params, callback)
+
+    async def text_document_implementation_async(
+        self,
+        params: types.ImplementationParams,
+    ) -> Union[types.Location, List[types.Location], List[types.LocationLink], None]:
+        """Make a :lsp:`textDocument/implementation` request.
+
+        A request to resolve the implementation locations of a symbol at a given text
+        document position. The request's parameter is of type {@link TextDocumentPositionParams}
+        the response is of type {@link Definition} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/implementation", params)
+
+    def text_document_inlay_hint(
+        self,
+        params: types.InlayHintParams,
+        callback: Optional[Callable[[Optional[List[types.InlayHint]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/inlayHint` request.
+
+        A request to provide inlay hints in a document. The request's parameter is of
+        type {@link InlayHintsParams}, the response is of type
+        {@link InlayHint InlayHint[]} or a Thenable that resolves to such.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/inlayHint", params, callback)
+
+    async def text_document_inlay_hint_async(
+        self,
+        params: types.InlayHintParams,
+    ) -> Optional[List[types.InlayHint]]:
+        """Make a :lsp:`textDocument/inlayHint` request.
+
+        A request to provide inlay hints in a document. The request's parameter is of
+        type {@link InlayHintsParams}, the response is of type
+        {@link InlayHint InlayHint[]} or a Thenable that resolves to such.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/inlayHint", params)
+
+    def text_document_inline_completion(
+        self,
+        params: types.InlineCompletionParams,
+        callback: Optional[Callable[[Union[types.InlineCompletionList, List[types.InlineCompletionItem], None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/inlineCompletion` request.
+
+        A request to provide inline completions in a document. The request's parameter is of
+        type {@link InlineCompletionParams}, the response is of type
+        {@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such.
+
+        @since 3.18.0
+        @proposed
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/inlineCompletion", params, callback)
+
+    async def text_document_inline_completion_async(
+        self,
+        params: types.InlineCompletionParams,
+    ) -> Union[types.InlineCompletionList, List[types.InlineCompletionItem], None]:
+        """Make a :lsp:`textDocument/inlineCompletion` request.
+
+        A request to provide inline completions in a document. The request's parameter is of
+        type {@link InlineCompletionParams}, the response is of type
+        {@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such.
+
+        @since 3.18.0
+        @proposed
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/inlineCompletion", params)
+
+    def text_document_inline_value(
+        self,
+        params: types.InlineValueParams,
+        callback: Optional[Callable[[Optional[List[Union[types.InlineValueText, types.InlineValueVariableLookup, types.InlineValueEvaluatableExpression]]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/inlineValue` request.
+
+        A request to provide inline values in a document. The request's parameter is of
+        type {@link InlineValueParams}, the response is of type
+        {@link InlineValue InlineValue[]} or a Thenable that resolves to such.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/inlineValue", params, callback)
+
+    async def text_document_inline_value_async(
+        self,
+        params: types.InlineValueParams,
+    ) -> Optional[List[Union[types.InlineValueText, types.InlineValueVariableLookup, types.InlineValueEvaluatableExpression]]]:
+        """Make a :lsp:`textDocument/inlineValue` request.
+
+        A request to provide inline values in a document. The request's parameter is of
+        type {@link InlineValueParams}, the response is of type
+        {@link InlineValue InlineValue[]} or a Thenable that resolves to such.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/inlineValue", params)
+
+    def text_document_linked_editing_range(
+        self,
+        params: types.LinkedEditingRangeParams,
+        callback: Optional[Callable[[Optional[types.LinkedEditingRanges]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/linkedEditingRange` request.
+
+        A request to provide ranges that can be edited together.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/linkedEditingRange", params, callback)
+
+    async def text_document_linked_editing_range_async(
+        self,
+        params: types.LinkedEditingRangeParams,
+    ) -> Optional[types.LinkedEditingRanges]:
+        """Make a :lsp:`textDocument/linkedEditingRange` request.
+
+        A request to provide ranges that can be edited together.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/linkedEditingRange", params)
+
+    def text_document_moniker(
+        self,
+        params: types.MonikerParams,
+        callback: Optional[Callable[[Optional[List[types.Moniker]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/moniker` request.
+
+        A request to get the moniker of a symbol at a given text document position.
+        The request parameter is of type {@link TextDocumentPositionParams}.
+        The response is of type {@link Moniker Moniker[]} or `null`.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/moniker", params, callback)
+
+    async def text_document_moniker_async(
+        self,
+        params: types.MonikerParams,
+    ) -> Optional[List[types.Moniker]]:
+        """Make a :lsp:`textDocument/moniker` request.
+
+        A request to get the moniker of a symbol at a given text document position.
+        The request parameter is of type {@link TextDocumentPositionParams}.
+        The response is of type {@link Moniker Moniker[]} or `null`.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/moniker", params)
+
+    def text_document_on_type_formatting(
+        self,
+        params: types.DocumentOnTypeFormattingParams,
+        callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/onTypeFormatting` request.
+
+        A request to format a document on type.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/onTypeFormatting", params, callback)
+
+    async def text_document_on_type_formatting_async(
+        self,
+        params: types.DocumentOnTypeFormattingParams,
+    ) -> Optional[List[types.TextEdit]]:
+        """Make a :lsp:`textDocument/onTypeFormatting` request.
+
+        A request to format a document on type.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/onTypeFormatting", params)
+
+    def text_document_prepare_call_hierarchy(
+        self,
+        params: types.CallHierarchyPrepareParams,
+        callback: Optional[Callable[[Optional[List[types.CallHierarchyItem]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/prepareCallHierarchy` request.
+
+        A request to result a `CallHierarchyItem` in a document at a given position.
+        Can be used as an input to an incoming or outgoing call hierarchy.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/prepareCallHierarchy", params, callback)
+
+    async def text_document_prepare_call_hierarchy_async(
+        self,
+        params: types.CallHierarchyPrepareParams,
+    ) -> Optional[List[types.CallHierarchyItem]]:
+        """Make a :lsp:`textDocument/prepareCallHierarchy` request.
+
+        A request to result a `CallHierarchyItem` in a document at a given position.
+        Can be used as an input to an incoming or outgoing call hierarchy.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/prepareCallHierarchy", params)
+
+    def text_document_prepare_rename(
+        self,
+        params: types.PrepareRenameParams,
+        callback: Optional[Callable[[Union[types.Range, types.PrepareRenameResult_Type1, types.PrepareRenameResult_Type2, None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/prepareRename` request.
+
+        A request to test and perform the setup necessary for a rename.
+
+        @since 3.16 - support for default behavior
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/prepareRename", params, callback)
+
+    async def text_document_prepare_rename_async(
+        self,
+        params: types.PrepareRenameParams,
+    ) -> Union[types.Range, types.PrepareRenameResult_Type1, types.PrepareRenameResult_Type2, None]:
+        """Make a :lsp:`textDocument/prepareRename` request.
+
+        A request to test and perform the setup necessary for a rename.
+
+        @since 3.16 - support for default behavior
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/prepareRename", params)
+
+    def text_document_prepare_type_hierarchy(
+        self,
+        params: types.TypeHierarchyPrepareParams,
+        callback: Optional[Callable[[Optional[List[types.TypeHierarchyItem]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/prepareTypeHierarchy` request.
+
+        A request to result a `TypeHierarchyItem` in a document at a given position.
+        Can be used as an input to a subtypes or supertypes type hierarchy.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/prepareTypeHierarchy", params, callback)
+
+    async def text_document_prepare_type_hierarchy_async(
+        self,
+        params: types.TypeHierarchyPrepareParams,
+    ) -> Optional[List[types.TypeHierarchyItem]]:
+        """Make a :lsp:`textDocument/prepareTypeHierarchy` request.
+
+        A request to result a `TypeHierarchyItem` in a document at a given position.
+        Can be used as an input to a subtypes or supertypes type hierarchy.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/prepareTypeHierarchy", params)
+
+    def text_document_ranges_formatting(
+        self,
+        params: types.DocumentRangesFormattingParams,
+        callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/rangesFormatting` request.
+
+        A request to format ranges in a document.
+
+        @since 3.18.0
+        @proposed
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/rangesFormatting", params, callback)
+
+    async def text_document_ranges_formatting_async(
+        self,
+        params: types.DocumentRangesFormattingParams,
+    ) -> Optional[List[types.TextEdit]]:
+        """Make a :lsp:`textDocument/rangesFormatting` request.
+
+        A request to format ranges in a document.
+
+        @since 3.18.0
+        @proposed
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/rangesFormatting", params)
+
+    def text_document_range_formatting(
+        self,
+        params: types.DocumentRangeFormattingParams,
+        callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/rangeFormatting` request.
+
+        A request to format a range in a document.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/rangeFormatting", params, callback)
+
+    async def text_document_range_formatting_async(
+        self,
+        params: types.DocumentRangeFormattingParams,
+    ) -> Optional[List[types.TextEdit]]:
+        """Make a :lsp:`textDocument/rangeFormatting` request.
+
+        A request to format a range in a document.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/rangeFormatting", params)
+
+    def text_document_references(
+        self,
+        params: types.ReferenceParams,
+        callback: Optional[Callable[[Optional[List[types.Location]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/references` request.
+
+        A request to resolve project-wide references for the symbol denoted
+        by the given text document position. The request's parameter is of
+        type {@link ReferenceParams} the response is of type
+        {@link Location Location[]} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/references", params, callback)
+
+    async def text_document_references_async(
+        self,
+        params: types.ReferenceParams,
+    ) -> Optional[List[types.Location]]:
+        """Make a :lsp:`textDocument/references` request.
+
+        A request to resolve project-wide references for the symbol denoted
+        by the given text document position. The request's parameter is of
+        type {@link ReferenceParams} the response is of type
+        {@link Location Location[]} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/references", params)
+
+    def text_document_rename(
+        self,
+        params: types.RenameParams,
+        callback: Optional[Callable[[Optional[types.WorkspaceEdit]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/rename` request.
+
+        A request to rename a symbol.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/rename", params, callback)
+
+    async def text_document_rename_async(
+        self,
+        params: types.RenameParams,
+    ) -> Optional[types.WorkspaceEdit]:
+        """Make a :lsp:`textDocument/rename` request.
+
+        A request to rename a symbol.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/rename", params)
+
+    def text_document_selection_range(
+        self,
+        params: types.SelectionRangeParams,
+        callback: Optional[Callable[[Optional[List[types.SelectionRange]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/selectionRange` request.
+
+        A request to provide selection ranges in a document. The request's
+        parameter is of type {@link SelectionRangeParams}, the
+        response is of type {@link SelectionRange SelectionRange[]} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/selectionRange", params, callback)
+
+    async def text_document_selection_range_async(
+        self,
+        params: types.SelectionRangeParams,
+    ) -> Optional[List[types.SelectionRange]]:
+        """Make a :lsp:`textDocument/selectionRange` request.
+
+        A request to provide selection ranges in a document. The request's
+        parameter is of type {@link SelectionRangeParams}, the
+        response is of type {@link SelectionRange SelectionRange[]} or a Thenable
+        that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/selectionRange", params)
+
+    def text_document_semantic_tokens_full(
+        self,
+        params: types.SemanticTokensParams,
+        callback: Optional[Callable[[Optional[types.SemanticTokens]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/semanticTokens/full` request.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/semanticTokens/full", params, callback)
+
+    async def text_document_semantic_tokens_full_async(
+        self,
+        params: types.SemanticTokensParams,
+    ) -> Optional[types.SemanticTokens]:
+        """Make a :lsp:`textDocument/semanticTokens/full` request.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/semanticTokens/full", params)
+
+    def text_document_semantic_tokens_full_delta(
+        self,
+        params: types.SemanticTokensDeltaParams,
+        callback: Optional[Callable[[Union[types.SemanticTokens, types.SemanticTokensDelta, None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/semanticTokens/full/delta` request.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/semanticTokens/full/delta", params, callback)
+
+    async def text_document_semantic_tokens_full_delta_async(
+        self,
+        params: types.SemanticTokensDeltaParams,
+    ) -> Union[types.SemanticTokens, types.SemanticTokensDelta, None]:
+        """Make a :lsp:`textDocument/semanticTokens/full/delta` request.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/semanticTokens/full/delta", params)
+
+    def text_document_semantic_tokens_range(
+        self,
+        params: types.SemanticTokensRangeParams,
+        callback: Optional[Callable[[Optional[types.SemanticTokens]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/semanticTokens/range` request.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/semanticTokens/range", params, callback)
+
+    async def text_document_semantic_tokens_range_async(
+        self,
+        params: types.SemanticTokensRangeParams,
+    ) -> Optional[types.SemanticTokens]:
+        """Make a :lsp:`textDocument/semanticTokens/range` request.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/semanticTokens/range", params)
+
+    def text_document_signature_help(
+        self,
+        params: types.SignatureHelpParams,
+        callback: Optional[Callable[[Optional[types.SignatureHelp]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/signatureHelp` request.
+
+
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/signatureHelp", params, callback)
+
+    async def text_document_signature_help_async(
+        self,
+        params: types.SignatureHelpParams,
+    ) -> Optional[types.SignatureHelp]:
+        """Make a :lsp:`textDocument/signatureHelp` request.
+
+
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/signatureHelp", params)
+
+    def text_document_type_definition(
+        self,
+        params: types.TypeDefinitionParams,
+        callback: Optional[Callable[[Union[types.Location, List[types.Location], List[types.LocationLink], None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/typeDefinition` request.
+
+        A request to resolve the type definition locations of a symbol at a given text
+        document position. The request's parameter is of type {@link TextDocumentPositionParams}
+        the response is of type {@link Definition} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/typeDefinition", params, callback)
+
+    async def text_document_type_definition_async(
+        self,
+        params: types.TypeDefinitionParams,
+    ) -> Union[types.Location, List[types.Location], List[types.LocationLink], None]:
+        """Make a :lsp:`textDocument/typeDefinition` request.
+
+        A request to resolve the type definition locations of a symbol at a given text
+        document position. The request's parameter is of type {@link TextDocumentPositionParams}
+        the response is of type {@link Definition} or a Thenable that resolves to such.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/typeDefinition", params)
+
+    def text_document_will_save_wait_until(
+        self,
+        params: types.WillSaveTextDocumentParams,
+        callback: Optional[Callable[[Optional[List[types.TextEdit]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`textDocument/willSaveWaitUntil` request.
+
+        A document will save request is sent from the client to the server before
+        the document is actually saved. The request can return an array of TextEdits
+        which will be applied to the text document before it is saved. Please note that
+        clients might drop results if computing the text edits took too long or if a
+        server constantly fails on this request. This is done to keep the save fast and
+        reliable.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("textDocument/willSaveWaitUntil", params, callback)
+
+    async def text_document_will_save_wait_until_async(
+        self,
+        params: types.WillSaveTextDocumentParams,
+    ) -> Optional[List[types.TextEdit]]:
+        """Make a :lsp:`textDocument/willSaveWaitUntil` request.
+
+        A document will save request is sent from the client to the server before
+        the document is actually saved. The request can return an array of TextEdits
+        which will be applied to the text document before it is saved. Please note that
+        clients might drop results if computing the text edits took too long or if a
+        server constantly fails on this request. This is done to keep the save fast and
+        reliable.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("textDocument/willSaveWaitUntil", params)
+
+    def type_hierarchy_subtypes(
+        self,
+        params: types.TypeHierarchySubtypesParams,
+        callback: Optional[Callable[[Optional[List[types.TypeHierarchyItem]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`typeHierarchy/subtypes` request.
+
+        A request to resolve the subtypes for a given `TypeHierarchyItem`.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("typeHierarchy/subtypes", params, callback)
+
+    async def type_hierarchy_subtypes_async(
+        self,
+        params: types.TypeHierarchySubtypesParams,
+    ) -> Optional[List[types.TypeHierarchyItem]]:
+        """Make a :lsp:`typeHierarchy/subtypes` request.
+
+        A request to resolve the subtypes for a given `TypeHierarchyItem`.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("typeHierarchy/subtypes", params)
+
+    def type_hierarchy_supertypes(
+        self,
+        params: types.TypeHierarchySupertypesParams,
+        callback: Optional[Callable[[Optional[List[types.TypeHierarchyItem]]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`typeHierarchy/supertypes` request.
+
+        A request to resolve the supertypes for a given `TypeHierarchyItem`.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("typeHierarchy/supertypes", params, callback)
+
+    async def type_hierarchy_supertypes_async(
+        self,
+        params: types.TypeHierarchySupertypesParams,
+    ) -> Optional[List[types.TypeHierarchyItem]]:
+        """Make a :lsp:`typeHierarchy/supertypes` request.
+
+        A request to resolve the supertypes for a given `TypeHierarchyItem`.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("typeHierarchy/supertypes", params)
+
+    def workspace_diagnostic(
+        self,
+        params: types.WorkspaceDiagnosticParams,
+        callback: Optional[Callable[[types.WorkspaceDiagnosticReport], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`workspace/diagnostic` request.
+
+        The workspace diagnostic request definition.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("workspace/diagnostic", params, callback)
+
+    async def workspace_diagnostic_async(
+        self,
+        params: types.WorkspaceDiagnosticParams,
+    ) -> types.WorkspaceDiagnosticReport:
+        """Make a :lsp:`workspace/diagnostic` request.
+
+        The workspace diagnostic request definition.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("workspace/diagnostic", params)
+
+    def workspace_execute_command(
+        self,
+        params: types.ExecuteCommandParams,
+        callback: Optional[Callable[[Optional[Any]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`workspace/executeCommand` request.
+
+        A request send from the client to the server to execute a command. The request might return
+        a workspace edit which the client will apply to the workspace.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("workspace/executeCommand", params, callback)
+
+    async def workspace_execute_command_async(
+        self,
+        params: types.ExecuteCommandParams,
+    ) -> Optional[Any]:
+        """Make a :lsp:`workspace/executeCommand` request.
+
+        A request send from the client to the server to execute a command. The request might return
+        a workspace edit which the client will apply to the workspace.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("workspace/executeCommand", params)
+
+    def workspace_symbol(
+        self,
+        params: types.WorkspaceSymbolParams,
+        callback: Optional[Callable[[Union[List[types.SymbolInformation], List[types.WorkspaceSymbol], None]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`workspace/symbol` request.
+
+        A request to list project-wide symbols matching the query string given
+        by the {@link WorkspaceSymbolParams}. The response is
+        of type {@link SymbolInformation SymbolInformation[]} or a Thenable that
+        resolves to such.
+
+        @since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients
+         need to advertise support for WorkspaceSymbols via the client capability
+         `workspace.symbol.resolveSupport`.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("workspace/symbol", params, callback)
+
+    async def workspace_symbol_async(
+        self,
+        params: types.WorkspaceSymbolParams,
+    ) -> Union[List[types.SymbolInformation], List[types.WorkspaceSymbol], None]:
+        """Make a :lsp:`workspace/symbol` request.
+
+        A request to list project-wide symbols matching the query string given
+        by the {@link WorkspaceSymbolParams}. The response is
+        of type {@link SymbolInformation SymbolInformation[]} or a Thenable that
+        resolves to such.
+
+        @since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients
+         need to advertise support for WorkspaceSymbols via the client capability
+         `workspace.symbol.resolveSupport`.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("workspace/symbol", params)
+
+    def workspace_symbol_resolve(
+        self,
+        params: types.WorkspaceSymbol,
+        callback: Optional[Callable[[types.WorkspaceSymbol], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`workspaceSymbol/resolve` request.
+
+        A request to resolve the range inside the workspace
+        symbol's location.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("workspaceSymbol/resolve", params, callback)
+
+    async def workspace_symbol_resolve_async(
+        self,
+        params: types.WorkspaceSymbol,
+    ) -> types.WorkspaceSymbol:
+        """Make a :lsp:`workspaceSymbol/resolve` request.
+
+        A request to resolve the range inside the workspace
+        symbol's location.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("workspaceSymbol/resolve", params)
+
+    def workspace_will_create_files(
+        self,
+        params: types.CreateFilesParams,
+        callback: Optional[Callable[[Optional[types.WorkspaceEdit]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`workspace/willCreateFiles` request.
+
+        The will create files request is sent from the client to the server before files are actually
+        created as long as the creation is triggered from within the client.
+
+        The request can return a `WorkspaceEdit` which will be applied to workspace before the
+        files are created. Hence the `WorkspaceEdit` can not manipulate the content of the file
+        to be created.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("workspace/willCreateFiles", params, callback)
+
+    async def workspace_will_create_files_async(
+        self,
+        params: types.CreateFilesParams,
+    ) -> Optional[types.WorkspaceEdit]:
+        """Make a :lsp:`workspace/willCreateFiles` request.
+
+        The will create files request is sent from the client to the server before files are actually
+        created as long as the creation is triggered from within the client.
+
+        The request can return a `WorkspaceEdit` which will be applied to workspace before the
+        files are created. Hence the `WorkspaceEdit` can not manipulate the content of the file
+        to be created.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("workspace/willCreateFiles", params)
+
+    def workspace_will_delete_files(
+        self,
+        params: types.DeleteFilesParams,
+        callback: Optional[Callable[[Optional[types.WorkspaceEdit]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`workspace/willDeleteFiles` request.
+
+        The did delete files notification is sent from the client to the server when
+        files were deleted from within the client.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("workspace/willDeleteFiles", params, callback)
+
+    async def workspace_will_delete_files_async(
+        self,
+        params: types.DeleteFilesParams,
+    ) -> Optional[types.WorkspaceEdit]:
+        """Make a :lsp:`workspace/willDeleteFiles` request.
+
+        The did delete files notification is sent from the client to the server when
+        files were deleted from within the client.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("workspace/willDeleteFiles", params)
+
+    def workspace_will_rename_files(
+        self,
+        params: types.RenameFilesParams,
+        callback: Optional[Callable[[Optional[types.WorkspaceEdit]], None]] = None,
+    ) -> Future:
+        """Make a :lsp:`workspace/willRenameFiles` request.
+
+        The will rename files request is sent from the client to the server before files are actually
+        renamed as long as the rename is triggered from within the client.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return self.protocol.send_request("workspace/willRenameFiles", params, callback)
+
+    async def workspace_will_rename_files_async(
+        self,
+        params: types.RenameFilesParams,
+    ) -> Optional[types.WorkspaceEdit]:
+        """Make a :lsp:`workspace/willRenameFiles` request.
+
+        The will rename files request is sent from the client to the server before files are actually
+        renamed as long as the rename is triggered from within the client.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        return await self.protocol.send_request_async("workspace/willRenameFiles", params)
+
+    def cancel_request(self, params: types.CancelParams) -> None:
+        """Send a :lsp:`$/cancelRequest` notification.
+
+
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("$/cancelRequest", params)
+
+    def exit(self, params: None) -> None:
+        """Send a :lsp:`exit` notification.
+
+        The exit event is sent from the client to the server to
+        ask the server to exit its process.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("exit", params)
+
+    def initialized(self, params: types.InitializedParams) -> None:
+        """Send a :lsp:`initialized` notification.
+
+        The initialized notification is sent from the client to the
+        server after the client is fully initialized and the server
+        is allowed to send requests from the server to the client.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("initialized", params)
+
+    def notebook_document_did_change(self, params: types.DidChangeNotebookDocumentParams) -> None:
+        """Send a :lsp:`notebookDocument/didChange` notification.
+
+
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("notebookDocument/didChange", params)
+
+    def notebook_document_did_close(self, params: types.DidCloseNotebookDocumentParams) -> None:
+        """Send a :lsp:`notebookDocument/didClose` notification.
+
+        A notification sent when a notebook closes.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("notebookDocument/didClose", params)
+
+    def notebook_document_did_open(self, params: types.DidOpenNotebookDocumentParams) -> None:
+        """Send a :lsp:`notebookDocument/didOpen` notification.
+
+        A notification sent when a notebook opens.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("notebookDocument/didOpen", params)
+
+    def notebook_document_did_save(self, params: types.DidSaveNotebookDocumentParams) -> None:
+        """Send a :lsp:`notebookDocument/didSave` notification.
+
+        A notification sent when a notebook document is saved.
+
+        @since 3.17.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("notebookDocument/didSave", params)
+
+    def progress(self, params: types.ProgressParams) -> None:
+        """Send a :lsp:`$/progress` notification.
+
+
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("$/progress", params)
+
+    def set_trace(self, params: types.SetTraceParams) -> None:
+        """Send a :lsp:`$/setTrace` notification.
+
+
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("$/setTrace", params)
+
+    def text_document_did_change(self, params: types.DidChangeTextDocumentParams) -> None:
+        """Send a :lsp:`textDocument/didChange` notification.
+
+        The document change notification is sent from the client to the server to signal
+        changes to a text document.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("textDocument/didChange", params)
+
+    def text_document_did_close(self, params: types.DidCloseTextDocumentParams) -> None:
+        """Send a :lsp:`textDocument/didClose` notification.
+
+        The document close notification is sent from the client to the server when
+        the document got closed in the client. The document's truth now exists where
+        the document's uri points to (e.g. if the document's uri is a file uri the
+        truth now exists on disk). As with the open notification the close notification
+        is about managing the document's content. Receiving a close notification
+        doesn't mean that the document was open in an editor before. A close
+        notification requires a previous open notification to be sent.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("textDocument/didClose", params)
+
+    def text_document_did_open(self, params: types.DidOpenTextDocumentParams) -> None:
+        """Send a :lsp:`textDocument/didOpen` notification.
+
+        The document open notification is sent from the client to the server to signal
+        newly opened text documents. The document's truth is now managed by the client
+        and the server must not try to read the document's truth using the document's
+        uri. Open in this sense means it is managed by the client. It doesn't necessarily
+        mean that its content is presented in an editor. An open notification must not
+        be sent more than once without a corresponding close notification send before.
+        This means open and close notification must be balanced and the max open count
+        is one.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("textDocument/didOpen", params)
+
+    def text_document_did_save(self, params: types.DidSaveTextDocumentParams) -> None:
+        """Send a :lsp:`textDocument/didSave` notification.
+
+        The document save notification is sent from the client to the server when
+        the document got saved in the client.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("textDocument/didSave", params)
+
+    def text_document_will_save(self, params: types.WillSaveTextDocumentParams) -> None:
+        """Send a :lsp:`textDocument/willSave` notification.
+
+        A document will save notification is sent from the client to the server before
+        the document is actually saved.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("textDocument/willSave", params)
+
+    def window_work_done_progress_cancel(self, params: types.WorkDoneProgressCancelParams) -> None:
+        """Send a :lsp:`window/workDoneProgress/cancel` notification.
+
+        The `window/workDoneProgress/cancel` notification is sent from  the client to the server to cancel a progress
+        initiated on the server side.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("window/workDoneProgress/cancel", params)
+
+    def workspace_did_change_configuration(self, params: types.DidChangeConfigurationParams) -> None:
+        """Send a :lsp:`workspace/didChangeConfiguration` notification.
+
+        The configuration change notification is sent from the client to the server
+        when the client's configuration has changed. The notification contains
+        the changed configuration as defined by the language client.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("workspace/didChangeConfiguration", params)
+
+    def workspace_did_change_watched_files(self, params: types.DidChangeWatchedFilesParams) -> None:
+        """Send a :lsp:`workspace/didChangeWatchedFiles` notification.
+
+        The watched files notification is sent from the client to the server when
+        the client detects changes to file watched by the language client.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("workspace/didChangeWatchedFiles", params)
+
+    def workspace_did_change_workspace_folders(self, params: types.DidChangeWorkspaceFoldersParams) -> None:
+        """Send a :lsp:`workspace/didChangeWorkspaceFolders` notification.
+
+        The `workspace/didChangeWorkspaceFolders` notification is sent from the client to the server when the workspace
+        folder configuration changes.
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("workspace/didChangeWorkspaceFolders", params)
+
+    def workspace_did_create_files(self, params: types.CreateFilesParams) -> None:
+        """Send a :lsp:`workspace/didCreateFiles` notification.
+
+        The did create files notification is sent from the client to the server when
+        files were created from within the client.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("workspace/didCreateFiles", params)
+
+    def workspace_did_delete_files(self, params: types.DeleteFilesParams) -> None:
+        """Send a :lsp:`workspace/didDeleteFiles` notification.
+
+        The will delete files request is sent from the client to the server before files are actually
+        deleted as long as the deletion is triggered from within the client.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("workspace/didDeleteFiles", params)
+
+    def workspace_did_rename_files(self, params: types.RenameFilesParams) -> None:
+        """Send a :lsp:`workspace/didRenameFiles` notification.
+
+        The did rename files notification is sent from the client to the server when
+        files were renamed from within the client.
+
+        @since 3.16.0
+        """
+        if self.stopped:
+            raise RuntimeError("Client has been stopped.")
+
+        self.protocol.notify("workspace/didRenameFiles", params)
diff --git a/server/libs/pygls/progress.py b/server/libs/pygls/progress.py
new file mode 100644
index 0000000..a2f0e5c
--- /dev/null
+++ b/server/libs/pygls/progress.py
@@ -0,0 +1,79 @@
+import asyncio
+from concurrent.futures import Future
+from typing import Dict
+
+from lsprotocol.types import (
+    PROGRESS,
+    WINDOW_WORK_DONE_PROGRESS_CREATE,
+    ProgressParams,
+    ProgressToken,
+    WorkDoneProgressBegin,
+    WorkDoneProgressEnd,
+    WorkDoneProgressReport,
+    WorkDoneProgressCreateParams,
+)
+from pygls.protocol import LanguageServerProtocol
+
+
+class Progress:
+    """A class for working with client's progress bar.
+
+    Attributes:
+        _lsp(LanguageServerProtocol): Language server protocol instance
+        tokens(dict): Holds futures for work done progress tokens that are
+            already registered. These futures will be cancelled if the client
+            sends a cancel work done process notification.
+    """
+
+    def __init__(self, lsp: LanguageServerProtocol) -> None:
+        self._lsp = lsp
+
+        self.tokens: Dict[ProgressToken, Future] = {}
+
+    def _check_token_registered(self, token: ProgressToken) -> None:
+        if token in self.tokens:
+            raise Exception("Token is already registered!")
+
+    def _register_token(self, token: ProgressToken) -> None:
+        self.tokens[token] = Future()
+
+    def create(self, token: ProgressToken, callback=None) -> Future:
+        """Create a server initiated work done progress."""
+        self._check_token_registered(token)
+
+        def on_created(*args, **kwargs):
+            self._register_token(token)
+            if callback is not None:
+                callback(*args, **kwargs)
+
+        return self._lsp.send_request(
+            WINDOW_WORK_DONE_PROGRESS_CREATE,
+            WorkDoneProgressCreateParams(token=token),
+            on_created,
+        )
+
+    async def create_async(self, token: ProgressToken) -> asyncio.Future:
+        """Create a server initiated work done progress."""
+        self._check_token_registered(token)
+
+        result = await self._lsp.send_request_async(
+            WINDOW_WORK_DONE_PROGRESS_CREATE,
+            WorkDoneProgressCreateParams(token=token),
+        )
+        self._register_token(token)
+        return result
+
+    def begin(self, token: ProgressToken, value: WorkDoneProgressBegin) -> None:
+        """Notify beginning of work."""
+        # Register cancellation future for the case of client initiated progress
+        self.tokens.setdefault(token, Future())
+
+        return self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))
+
+    def report(self, token: ProgressToken, value: WorkDoneProgressReport) -> None:
+        """Notify progress of work."""
+        self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))
+
+    def end(self, token: ProgressToken, value: WorkDoneProgressEnd) -> None:
+        """Notify end of work."""
+        self._lsp.notify(PROGRESS, ProgressParams(token=token, value=value))
diff --git a/server/libs/pygls/protocol/__init__.py b/server/libs/pygls/protocol/__init__.py
new file mode 100644
index 0000000..1a30b48
--- /dev/null
+++ b/server/libs/pygls/protocol/__init__.py
@@ -0,0 +1,78 @@
+import json
+from typing import Any
+
+from collections import namedtuple
+
+from lsprotocol import converters
+
+from pygls.protocol.json_rpc import (
+    JsonRPCNotification,
+    JsonRPCProtocol,
+    JsonRPCRequestMessage,
+    JsonRPCResponseMessage,
+)
+from pygls.protocol.language_server import LanguageServerProtocol, lsp_method
+from pygls.protocol.lsp_meta import LSPMeta, call_user_feature
+
+
+def _dict_to_object(d: Any):
+    """Create nested objects (namedtuple) from dict."""
+
+    if d is None:
+        return None
+
+    if not isinstance(d, dict):
+        return d
+
+    type_name = d.pop("type_name", "Object")
+    return json.loads(
+        json.dumps(d),
+        object_hook=lambda p: namedtuple(type_name, p.keys(), rename=True)(*p.values()),
+    )
+
+
+def _params_field_structure_hook(obj, cls):
+    if "params" in obj:
+        obj["params"] = _dict_to_object(obj["params"])
+
+    return cls(**obj)
+
+
+def _result_field_structure_hook(obj, cls):
+    if "result" in obj:
+        obj["result"] = _dict_to_object(obj["result"])
+
+    return cls(**obj)
+
+
+def default_converter():
+    """Default converter factory function."""
+
+    converter = converters.get_converter()
+    converter.register_structure_hook(
+        JsonRPCRequestMessage, _params_field_structure_hook
+    )
+
+    converter.register_structure_hook(
+        JsonRPCResponseMessage, _result_field_structure_hook
+    )
+
+    converter.register_structure_hook(JsonRPCNotification, _params_field_structure_hook)
+
+    return converter
+
+
+__all__ = (
+    "JsonRPCProtocol",
+    "LanguageServerProtocol",
+    "JsonRPCRequestMessage",
+    "JsonRPCResponseMessage",
+    "JsonRPCNotification",
+    "LSPMeta",
+    "call_user_feature",
+    "_dict_to_object",
+    "_params_field_structure_hook",
+    "_result_field_structure_hook",
+    "default_converter",
+    "lsp_method",
+)
diff --git a/server/libs/pygls/protocol/json_rpc.py b/server/libs/pygls/protocol/json_rpc.py
new file mode 100644
index 0000000..75a4b34
--- /dev/null
+++ b/server/libs/pygls/protocol/json_rpc.py
@@ -0,0 +1,560 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved.                      #
+# See ThirdPartyNotices.txt in the project root for additional notices.    #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+from __future__ import annotations
+import asyncio
+import enum
+import json
+import logging
+import re
+import sys
+import uuid
+import traceback
+from concurrent.futures import Future
+from functools import partial
+from typing import (
+    Any,
+    Dict,
+    List,
+    Optional,
+    Type,
+    Union,
+    TYPE_CHECKING,
+)
+
+if TYPE_CHECKING:
+    from pygls.server import LanguageServer, WebSocketTransportAdapter
+
+
+import attrs
+from cattrs.errors import ClassValidationError
+
+from lsprotocol.types import (
+    CANCEL_REQUEST,
+    EXIT,
+    WORKSPACE_EXECUTE_COMMAND,
+    ResponseError,
+    ResponseErrorMessage,
+)
+
+from pygls.exceptions import (
+    JsonRpcException,
+    JsonRpcInternalError,
+    JsonRpcInvalidParams,
+    JsonRpcMethodNotFound,
+    JsonRpcRequestCancelled,
+    FeatureNotificationError,
+    FeatureRequestError,
+)
+from pygls.feature_manager import FeatureManager, is_thread_function
+
+logger = logging.getLogger(__name__)
+
+
+@attrs.define
+class JsonRPCNotification:
+    """A class that represents a generic json rpc notification message.
+    Used as a fallback for unknown types.
+    """
+
+    method: str
+    jsonrpc: str
+    params: Any
+
+
+@attrs.define
+class JsonRPCRequestMessage:
+    """A class that represents a generic json rpc request message.
+    Used as a fallback for unknown types.
+    """
+
+    id: Union[int, str]
+    method: str
+    jsonrpc: str
+    params: Any
+
+
+@attrs.define
+class JsonRPCResponseMessage:
+    """A class that represents a generic json rpc response message.
+    Used as a fallback for unknown types.
+    """
+
+    id: Union[int, str]
+    jsonrpc: str
+    result: Any
+
+
+class JsonRPCProtocol(asyncio.Protocol):
+    """Json RPC protocol implementation using on top of `asyncio.Protocol`.
+
+    Specification of the protocol can be found here:
+        https://www.jsonrpc.org/specification
+
+    This class provides bidirectional communication which is needed for LSP.
+    """
+
+    CHARSET = "utf-8"
+    CONTENT_TYPE = "application/vscode-jsonrpc"
+
+    MESSAGE_PATTERN = re.compile(
+        rb"^(?:[^\r\n]+\r\n)*"
+        + rb"Content-Length: (?P\d+)\r\n"
+        + rb"(?:[^\r\n]+\r\n)*\r\n"
+        + rb"(?P{.*)",
+        re.DOTALL,
+    )
+
+    VERSION = "2.0"
+
+    def __init__(self, server: LanguageServer, converter):
+        self._server = server
+        self._converter = converter
+
+        self._shutdown = False
+
+        # Book keeping for in-flight requests
+        self._request_futures: Dict[str, Future[Any]] = {}
+        self._result_types: Dict[str, Any] = {}
+
+        self.fm = FeatureManager(server, converter)
+        self.transport: Optional[
+            Union[asyncio.WriteTransport, WebSocketTransportAdapter]
+        ] = None
+        self._message_buf: List[bytes] = []
+
+        self._send_only_body = False
+
+    def __call__(self):
+        return self
+
+    def _execute_notification(self, handler, *params):
+        """Executes notification message handler."""
+        if asyncio.iscoroutinefunction(handler):
+            future = asyncio.ensure_future(handler(*params))
+            future.add_done_callback(self._execute_notification_callback)
+        else:
+            if is_thread_function(handler):
+                self._server.thread_pool.apply_async(handler, (*params,))
+            else:
+                handler(*params)
+
+    def _execute_notification_callback(self, future):
+        """Success callback used for coroutine notification message."""
+        if future.exception():
+            try:
+                raise future.exception()
+            except Exception:
+                error = JsonRpcInternalError.of(sys.exc_info())
+                logger.exception('Exception occurred in notification: "%s"', error)
+
+            # Revisit. Client does not support response with msg_id = None
+            # https://stackoverflow.com/questions/31091376/json-rpc-2-0-allow-notifications-to-have-an-error-response
+            # self._send_response(None, error=error)
+
+    def _execute_request(self, msg_id, handler, params):
+        """Executes request message handler."""
+
+        if asyncio.iscoroutinefunction(handler):
+            future = asyncio.ensure_future(handler(params))
+            self._request_futures[msg_id] = future
+            future.add_done_callback(partial(self._execute_request_callback, msg_id))
+        else:
+            # Can't be canceled
+            if is_thread_function(handler):
+                self._server.thread_pool.apply_async(
+                    handler,
+                    (params,),
+                    callback=partial(
+                        self._send_response,
+                        msg_id,
+                    ),
+                    error_callback=partial(self._execute_request_err_callback, msg_id),
+                )
+            else:
+                self._send_response(msg_id, handler(params))
+
+    def _execute_request_callback(self, msg_id, future):
+        """Success callback used for coroutine request message."""
+        try:
+            if not future.cancelled():
+                self._send_response(msg_id, result=future.result())
+            else:
+                self._send_response(
+                    msg_id,
+                    error=JsonRpcRequestCancelled(
+                        f'Request with id "{msg_id}" is canceled'
+                    ).to_response_error(),
+                )
+            self._request_futures.pop(msg_id, None)
+        except Exception:
+            error = JsonRpcInternalError.of(sys.exc_info())
+            logger.exception('Exception occurred for message "%s": %s', msg_id, error)
+            self._send_response(msg_id, error=error.to_response_error())
+
+    def _execute_request_err_callback(self, msg_id, exc):
+        """Error callback used for coroutine request message."""
+        exc_info = (type(exc), exc, None)
+        error = JsonRpcInternalError.of(exc_info)
+        logger.exception('Exception occurred for message "%s": %s', msg_id, error)
+        self._send_response(msg_id, error=error.to_response_error())
+
+    def _get_handler(self, feature_name):
+        """Returns builtin or used defined feature by name if exists."""
+        try:
+            return self.fm.builtin_features[feature_name]
+        except KeyError:
+            try:
+                return self.fm.features[feature_name]
+            except KeyError:
+                raise JsonRpcMethodNotFound.of(feature_name)
+
+    def _handle_cancel_notification(self, msg_id):
+        """Handles a cancel notification from the client."""
+        future = self._request_futures.pop(msg_id, None)
+
+        if not future:
+            logger.warning('Cancel notification for unknown message id "%s"', msg_id)
+            return
+
+        # Will only work if the request hasn't started executing
+        if future.cancel():
+            logger.info('Cancelled request with id "%s"', msg_id)
+
+    def _handle_notification(self, method_name, params):
+        """Handles a notification from the client."""
+        if method_name == CANCEL_REQUEST:
+            self._handle_cancel_notification(params.id)
+            return
+
+        try:
+            handler = self._get_handler(method_name)
+            self._execute_notification(handler, params)
+        except (KeyError, JsonRpcMethodNotFound):
+            logger.warning('Ignoring notification for unknown method "%s"', method_name)
+        except Exception as error:
+            logger.exception(
+                'Failed to handle notification "%s": %s',
+                method_name,
+                params,
+                exc_info=True,
+            )
+            self._server._report_server_error(error, FeatureNotificationError)
+
+    def _handle_request(self, msg_id, method_name, params):
+        """Handles a request from the client."""
+        try:
+            handler = self._get_handler(method_name)
+
+            # workspace/executeCommand is a special case
+            if method_name == WORKSPACE_EXECUTE_COMMAND:
+                handler(params, msg_id)
+            else:
+                self._execute_request(msg_id, handler, params)
+
+        except JsonRpcException as error:
+            logger.exception(
+                "Failed to handle request %s %s %s",
+                msg_id,
+                method_name,
+                params,
+                exc_info=True,
+            )
+            self._send_response(msg_id, None, error.to_response_error())
+            self._server._report_server_error(error, FeatureRequestError)
+        except Exception as error:
+            logger.exception(
+                "Failed to handle request %s %s %s",
+                msg_id,
+                method_name,
+                params,
+                exc_info=True,
+            )
+            err = JsonRpcInternalError.of(sys.exc_info()).to_response_error()
+            self._send_response(msg_id, None, err)
+            self._server._report_server_error(error, FeatureRequestError)
+
+    def _handle_response(self, msg_id, result=None, error=None):
+        """Handles a response from the client."""
+        future = self._request_futures.pop(msg_id, None)
+
+        if not future:
+            logger.warning('Received response to unknown message id "%s"', msg_id)
+            return
+
+        if error is not None:
+            logger.debug('Received error response to message "%s": %s', msg_id, error)
+            future.set_exception(JsonRpcException.from_error(error))
+        else:
+            logger.debug('Received result for message "%s": %s', msg_id, result)
+            future.set_result(result)
+
+    def _serialize_message(self, data):
+        """Function used to serialize data sent to the client."""
+
+        if hasattr(data, "__attrs_attrs__"):
+            return self._converter.unstructure(data)
+
+        if isinstance(data, enum.Enum):
+            return data.value
+
+        return data.__dict__
+
+    def _deserialize_message(self, data):
+        """Function used to deserialize data recevied from the client."""
+
+        if "jsonrpc" not in data:
+            return data
+
+        try:
+            if "id" in data:
+                if "error" in data:
+                    return self._converter.structure(data, ResponseErrorMessage)
+                elif "method" in data:
+                    request_type = (
+                        self.get_message_type(data["method"]) or JsonRPCRequestMessage
+                    )
+                    return self._converter.structure(data, request_type)
+                else:
+                    response_type = (
+                        self._result_types.pop(data["id"]) or JsonRPCResponseMessage
+                    )
+                    return self._converter.structure(data, response_type)
+
+            else:
+                method = data.get("method", "")
+                notification_type = self.get_message_type(method) or JsonRPCNotification
+                return self._converter.structure(data, notification_type)
+
+        except ClassValidationError as exc:
+            logger.error("Unable to deserialize message\n%s", traceback.format_exc())
+            raise JsonRpcInvalidParams() from exc
+
+        except Exception as exc:
+            logger.error("Unable to deserialize message\n%s", traceback.format_exc())
+            raise JsonRpcInternalError() from exc
+
+    def _procedure_handler(self, message):
+        """Delegates message to handlers depending on message type."""
+
+        if message.jsonrpc != JsonRPCProtocol.VERSION:
+            logger.warning('Unknown message "%s"', message)
+            return
+
+        if self._shutdown and getattr(message, "method", "") != EXIT:
+            logger.warning("Server shutting down. No more requests!")
+            return
+
+        if hasattr(message, "method"):
+            if hasattr(message, "id"):
+                logger.debug("Request message received.")
+                self._handle_request(message.id, message.method, message.params)
+            else:
+                logger.debug("Notification message received.")
+                self._handle_notification(message.method, message.params)
+        else:
+            if hasattr(message, "error"):
+                logger.debug("Error message received.")
+                self._handle_response(message.id, None, message.error)
+            else:
+                logger.debug("Response message received.")
+                self._handle_response(message.id, message.result)
+
+    def _send_data(self, data):
+        """Sends data to the client."""
+        if not data:
+            return
+
+        if self.transport is None:
+            logger.error("Unable to send data, no available transport!")
+            return
+
+        try:
+            body = json.dumps(data, default=self._serialize_message)
+            logger.info("Sending data: %s", body)
+
+            if self._send_only_body:
+                # Mypy/Pyright seem to think `write()` wants `"bytes | bytearray | memoryview"`
+                # But runtime errors with anything but `str`.
+                self.transport.write(body)  # type: ignore
+                return
+
+            header = (
+                f"Content-Length: {len(body)}\r\n"
+                f"Content-Type: {self.CONTENT_TYPE}; charset={self.CHARSET}\r\n\r\n"
+            ).encode(self.CHARSET)
+
+            self.transport.write(header + body.encode(self.CHARSET))
+        except Exception as error:
+            logger.exception("Error sending data", exc_info=True)
+            self._server._report_server_error(error, JsonRpcInternalError)
+
+    def _send_response(
+        self, msg_id, result=None, error: Union[ResponseError, None] = None
+    ):
+        """Sends a JSON RPC response to the client.
+
+        Args:
+            msg_id(str): Id from request
+            result(any): Result returned by handler
+            error(any): Error returned by handler
+        """
+
+        if error is not None:
+            response = ResponseErrorMessage(id=msg_id, error=error)
+
+        else:
+            response_type = self._result_types.pop(msg_id, JsonRPCResponseMessage)
+            response = response_type(
+                id=msg_id, result=result, jsonrpc=JsonRPCProtocol.VERSION
+            )
+
+        self._send_data(response)
+
+    def connection_lost(self, exc):
+        """Method from base class, called when connection is lost, in which case we
+        want to shutdown the server's process as well.
+        """
+        logger.error("Connection to the client is lost! Shutting down the server.")
+        sys.exit(1)
+
+    def connection_made(  # type: ignore # see: https://github.com/python/typeshed/issues/3021
+        self,
+        transport: asyncio.Transport,
+    ):
+        """Method from base class, called when connection is established"""
+        self.transport = transport
+
+    def data_received(self, data: bytes):
+        try:
+            self._data_received(data)
+        except Exception as error:
+            logger.exception("Error receiving data", exc_info=True)
+            self._server._report_server_error(error, JsonRpcInternalError)
+
+    def _data_received(self, data: bytes):
+        """Method from base class, called when server receives the data"""
+        logger.debug("Received %r", data)
+
+        while len(data):
+            # Append the incoming chunk to the message buffer
+            self._message_buf.append(data)
+
+            # Look for the body of the message
+            message = b"".join(self._message_buf)
+            found = JsonRPCProtocol.MESSAGE_PATTERN.fullmatch(message)
+
+            body = found.group("body") if found else b""
+            length = int(found.group("length")) if found else 1
+
+            if len(body) < length:
+                # Message is incomplete; bail until more data arrives
+                return
+
+            # Message is complete;
+            # extract the body and any remaining data,
+            # and reset the buffer for the next message
+            body, data = body[:length], body[length:]
+            self._message_buf = []
+
+            # Parse the body
+            self._procedure_handler(
+                json.loads(
+                    body.decode(self.CHARSET), object_hook=self._deserialize_message
+                )
+            )
+
+    def get_message_type(self, method: str) -> Optional[Type]:
+        """Return the type definition of the message associated with the given method."""
+        return None
+
+    def get_result_type(self, method: str) -> Optional[Type]:
+        """Return the type definition of the result associated with the given method."""
+        return None
+
+    def notify(self, method: str, params=None):
+        """Sends a JSON RPC notification to the client."""
+
+        logger.debug("Sending notification: '%s' %s", method, params)
+
+        notification_type = self.get_message_type(method) or JsonRPCNotification
+        notification = notification_type(
+            method=method, params=params, jsonrpc=JsonRPCProtocol.VERSION
+        )
+
+        self._send_data(notification)
+
+    def send_request(self, method, params=None, callback=None, msg_id=None):
+        """Sends a JSON RPC request to the client.
+
+        Args:
+            method(str): The method name of the message to send
+            params(any): The payload of the message
+
+        Returns:
+            Future that will be resolved once a response has been received
+        """
+
+        if msg_id is None:
+            msg_id = str(uuid.uuid4())
+
+        request_type = self.get_message_type(method) or JsonRPCRequestMessage
+        logger.debug('Sending request with id "%s": %s %s', msg_id, method, params)
+
+        request = request_type(
+            id=msg_id,
+            method=method,
+            params=params,
+            jsonrpc=JsonRPCProtocol.VERSION,
+        )
+
+        future = Future()  # type: ignore[var-annotated]
+        # If callback function is given, call it when result is received
+        if callback:
+
+            def wrapper(future: Future):
+                result = future.result()
+                logger.info("Client response for %s received: %s", params, result)
+                callback(result)
+
+            future.add_done_callback(wrapper)
+
+        self._request_futures[msg_id] = future
+        self._result_types[msg_id] = self.get_result_type(method)
+
+        self._send_data(request)
+
+        return future
+
+    def send_request_async(self, method, params=None, msg_id=None):
+        """Calls `send_request` and wraps `concurrent.futures.Future` with
+        `asyncio.Future` so it can be used with `await` keyword.
+
+        Args:
+            method(str): The method name of the message to send
+            params(any): The payload of the message
+            msg_id(str|int): Optional, message id
+
+        Returns:
+            `asyncio.Future` that can be awaited
+        """
+        return asyncio.wrap_future(
+            self.send_request(method, params=params, msg_id=msg_id)
+        )
+
+    def thread(self):
+        """Decorator that mark function to execute it in a thread."""
+        return self.fm.thread()
diff --git a/server/libs/pygls/protocol/language_server.py b/server/libs/pygls/protocol/language_server.py
new file mode 100644
index 0000000..42b1319
--- /dev/null
+++ b/server/libs/pygls/protocol/language_server.py
@@ -0,0 +1,569 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved.                      #
+# See ThirdPartyNotices.txt in the project root for additional notices.    #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+from __future__ import annotations
+import asyncio
+import json
+import logging
+import sys
+from concurrent.futures import Future
+from functools import lru_cache
+from itertools import zip_longest
+from typing import (
+    Callable,
+    List,
+    Optional,
+    Type,
+    TypeVar,
+    Union,
+)
+
+
+from pygls.capabilities import ServerCapabilitiesBuilder
+from pygls.lsp import ConfigCallbackType, ShowDocumentCallbackType
+from lsprotocol.types import (
+    CLIENT_REGISTER_CAPABILITY,
+    CLIENT_UNREGISTER_CAPABILITY,
+    EXIT,
+    INITIALIZE,
+    INITIALIZED,
+    METHOD_TO_TYPES,
+    NOTEBOOK_DOCUMENT_DID_CHANGE,
+    NOTEBOOK_DOCUMENT_DID_CLOSE,
+    NOTEBOOK_DOCUMENT_DID_OPEN,
+    LOG_TRACE,
+    SET_TRACE,
+    SHUTDOWN,
+    TEXT_DOCUMENT_DID_CHANGE,
+    TEXT_DOCUMENT_DID_CLOSE,
+    TEXT_DOCUMENT_DID_OPEN,
+    TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS,
+    WINDOW_LOG_MESSAGE,
+    WINDOW_SHOW_DOCUMENT,
+    WINDOW_SHOW_MESSAGE,
+    WINDOW_WORK_DONE_PROGRESS_CANCEL,
+    WORKSPACE_APPLY_EDIT,
+    WORKSPACE_CONFIGURATION,
+    WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS,
+    WORKSPACE_EXECUTE_COMMAND,
+    WORKSPACE_SEMANTIC_TOKENS_REFRESH,
+)
+from lsprotocol.types import (
+    ApplyWorkspaceEditParams,
+    Diagnostic,
+    DidChangeNotebookDocumentParams,
+    DidChangeTextDocumentParams,
+    DidChangeWorkspaceFoldersParams,
+    DidCloseNotebookDocumentParams,
+    DidCloseTextDocumentParams,
+    DidOpenNotebookDocumentParams,
+    DidOpenTextDocumentParams,
+    ExecuteCommandParams,
+    InitializeParams,
+    InitializeResult,
+    LogMessageParams,
+    LogTraceParams,
+    MessageType,
+    PublishDiagnosticsParams,
+    RegistrationParams,
+    SetTraceParams,
+    ShowDocumentParams,
+    ShowMessageParams,
+    TraceValues,
+    UnregistrationParams,
+    WorkspaceApplyEditResponse,
+    WorkspaceEdit,
+    InitializeResultServerInfoType,
+    WorkspaceConfigurationParams,
+    WorkDoneProgressCancelParams,
+)
+from pygls.protocol.json_rpc import JsonRPCProtocol
+from pygls.protocol.lsp_meta import LSPMeta
+from pygls.uris import from_fs_path
+from pygls.workspace import Workspace
+
+
+F = TypeVar("F", bound=Callable)
+
+logger = logging.getLogger(__name__)
+
+
+def lsp_method(method_name: str) -> Callable[[F], F]:
+    def decorator(f: F) -> F:
+        f.method_name = method_name  # type: ignore[attr-defined]
+        return f
+
+    return decorator
+
+
+class LanguageServerProtocol(JsonRPCProtocol, metaclass=LSPMeta):
+    """A class that represents language server protocol.
+
+    It contains implementations for generic LSP features.
+
+    Attributes:
+        workspace(Workspace): In memory workspace
+    """
+
+    def __init__(self, server, converter):
+        super().__init__(server, converter)
+
+        self._workspace: Optional[Workspace] = None
+        self.trace = None
+
+        from pygls.progress import Progress
+
+        self.progress = Progress(self)
+
+        self.server_info = InitializeResultServerInfoType(
+            name=server.name,
+            version=server.version,
+        )
+
+        self._register_builtin_features()
+
+    def _register_builtin_features(self):
+        """Registers generic LSP features from this class."""
+        for name in dir(self):
+            if name in {"workspace"}:
+                continue
+
+            attr = getattr(self, name)
+            if callable(attr) and hasattr(attr, "method_name"):
+                self.fm.add_builtin_feature(attr.method_name, attr)
+
+    @property
+    def workspace(self) -> Workspace:
+        if self._workspace is None:
+            raise RuntimeError(
+                "The workspace is not available - has the server been initialized?"
+            )
+
+        return self._workspace
+
+    @lru_cache()
+    def get_message_type(self, method: str) -> Optional[Type]:
+        """Return LSP type definitions, as provided by `lsprotocol`"""
+        return METHOD_TO_TYPES.get(method, (None,))[0]
+
+    @lru_cache()
+    def get_result_type(self, method: str) -> Optional[Type]:
+        return METHOD_TO_TYPES.get(method, (None, None))[1]
+
+    def apply_edit(
+        self, edit: WorkspaceEdit, label: Optional[str] = None
+    ) -> WorkspaceApplyEditResponse:
+        """Sends apply edit request to the client."""
+        return self.send_request(
+            WORKSPACE_APPLY_EDIT, ApplyWorkspaceEditParams(edit=edit, label=label)
+        )
+
+    def apply_edit_async(
+        self, edit: WorkspaceEdit, label: Optional[str] = None
+    ) -> WorkspaceApplyEditResponse:
+        """Sends apply edit request to the client. Should be called with `await`"""
+        return self.send_request_async(
+            WORKSPACE_APPLY_EDIT, ApplyWorkspaceEditParams(edit=edit, label=label)
+        )
+
+    @lsp_method(EXIT)
+    def lsp_exit(self, *args) -> None:
+        """Stops the server process."""
+        if self.transport is not None:
+            self.transport.close()
+
+        sys.exit(0 if self._shutdown else 1)
+
+    @lsp_method(INITIALIZE)
+    def lsp_initialize(self, params: InitializeParams) -> InitializeResult:
+        """Method that initializes language server.
+        It will compute and return server capabilities based on
+        registered features.
+        """
+        logger.info("Language server initialized %s", params)
+
+        self._server.process_id = params.process_id
+
+        text_document_sync_kind = self._server._text_document_sync_kind
+        notebook_document_sync = self._server._notebook_document_sync
+
+        # Initialize server capabilities
+        self.client_capabilities = params.capabilities
+        self.server_capabilities = ServerCapabilitiesBuilder(
+            self.client_capabilities,
+            set({**self.fm.features, **self.fm.builtin_features}.keys()),
+            self.fm.feature_options,
+            list(self.fm.commands.keys()),
+            text_document_sync_kind,
+            notebook_document_sync,
+        ).build()
+        logger.debug(
+            "Server capabilities: %s",
+            json.dumps(self.server_capabilities, default=self._serialize_message),
+        )
+
+        root_path = params.root_path
+        root_uri = params.root_uri
+        if root_path is not None and root_uri is None:
+            root_uri = from_fs_path(root_path)
+
+        # Initialize the workspace
+        workspace_folders = params.workspace_folders or []
+        self._workspace = Workspace(
+            root_uri,
+            text_document_sync_kind,
+            workspace_folders,
+            self.server_capabilities.position_encoding,
+        )
+
+        self.trace = TraceValues.Off
+
+        return InitializeResult(
+            capabilities=self.server_capabilities,
+            server_info=self.server_info,
+        )
+
+    @lsp_method(INITIALIZED)
+    def lsp_initialized(self, *args) -> None:
+        """Notification received when client and server are connected."""
+        pass
+
+    @lsp_method(SHUTDOWN)
+    def lsp_shutdown(self, *args) -> None:
+        """Request from client which asks server to shutdown."""
+        for future in self._request_futures.values():
+            future.cancel()
+
+        self._shutdown = True
+        return None
+
+    @lsp_method(TEXT_DOCUMENT_DID_CHANGE)
+    def lsp_text_document__did_change(
+        self, params: DidChangeTextDocumentParams
+    ) -> None:
+        """Updates document's content.
+        (Incremental(from server capabilities); not configurable for now)
+        """
+        for change in params.content_changes:
+            self.workspace.update_text_document(params.text_document, change)
+
+    @lsp_method(TEXT_DOCUMENT_DID_CLOSE)
+    def lsp_text_document__did_close(self, params: DidCloseTextDocumentParams) -> None:
+        """Removes document from workspace."""
+        self.workspace.remove_text_document(params.text_document.uri)
+
+    @lsp_method(TEXT_DOCUMENT_DID_OPEN)
+    def lsp_text_document__did_open(self, params: DidOpenTextDocumentParams) -> None:
+        """Puts document to the workspace."""
+        self.workspace.put_text_document(params.text_document)
+
+    @lsp_method(NOTEBOOK_DOCUMENT_DID_OPEN)
+    def lsp_notebook_document__did_open(
+        self, params: DidOpenNotebookDocumentParams
+    ) -> None:
+        """Put a notebook document into the workspace"""
+        self.workspace.put_notebook_document(params)
+
+    @lsp_method(NOTEBOOK_DOCUMENT_DID_CHANGE)
+    def lsp_notebook_document__did_change(
+        self, params: DidChangeNotebookDocumentParams
+    ) -> None:
+        """Update a notebook's contents"""
+        self.workspace.update_notebook_document(params)
+
+    @lsp_method(NOTEBOOK_DOCUMENT_DID_CLOSE)
+    def lsp_notebook_document__did_close(
+        self, params: DidCloseNotebookDocumentParams
+    ) -> None:
+        """Remove a notebook document from the workspace."""
+        self.workspace.remove_notebook_document(params)
+
+    @lsp_method(SET_TRACE)
+    def lsp_set_trace(self, params: SetTraceParams) -> None:
+        """Changes server trace value."""
+        self.trace = params.value
+
+    @lsp_method(WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS)
+    def lsp_workspace__did_change_workspace_folders(
+        self, params: DidChangeWorkspaceFoldersParams
+    ) -> None:
+        """Adds/Removes folders from the workspace."""
+        logger.info("Workspace folders changed: %s", params)
+
+        added_folders = params.event.added or []
+        removed_folders = params.event.removed or []
+
+        for f_add, f_remove in zip_longest(added_folders, removed_folders):
+            if f_add:
+                self.workspace.add_folder(f_add)
+            if f_remove:
+                self.workspace.remove_folder(f_remove.uri)
+
+    @lsp_method(WORKSPACE_EXECUTE_COMMAND)
+    def lsp_workspace__execute_command(
+        self, params: ExecuteCommandParams, msg_id: str
+    ) -> None:
+        """Executes commands with passed arguments and returns a value."""
+        cmd_handler = self.fm.commands[params.command]
+        self._execute_request(msg_id, cmd_handler, params.arguments)
+
+    @lsp_method(WINDOW_WORK_DONE_PROGRESS_CANCEL)
+    def lsp_work_done_progress_cancel(
+        self, params: WorkDoneProgressCancelParams
+    ) -> None:
+        """Received a progress cancellation from client."""
+        future = self.progress.tokens.get(params.token)
+        if future is None:
+            logger.warning(
+                "Ignoring work done progress cancel for unknown token %s", params.token
+            )
+        else:
+            future.cancel()
+
+    def get_configuration(
+        self,
+        params: WorkspaceConfigurationParams,
+        callback: Optional[ConfigCallbackType] = None,
+    ) -> Future:
+        """Sends configuration request to the client.
+
+        Args:
+            params(WorkspaceConfigurationParams): WorkspaceConfigurationParams from lsp specs
+            callback(callable): Callabe which will be called after
+                                response from the client is received
+        Returns:
+            concurrent.futures.Future object that will be resolved once a
+            response has been received
+        """
+        return self.send_request(WORKSPACE_CONFIGURATION, params, callback)
+
+    def get_configuration_async(
+        self, params: WorkspaceConfigurationParams
+    ) -> asyncio.Future:
+        """Calls `get_configuration` method but designed to use with coroutines
+
+        Args:
+            params(WorkspaceConfigurationParams): WorkspaceConfigurationParams from lsp specs
+        Returns:
+            asyncio.Future that can be awaited
+        """
+        return asyncio.wrap_future(self.get_configuration(params))
+
+    def log_trace(self, message: str, verbose: Optional[str] = None) -> None:
+        """Sends trace notification to the client."""
+        if self.trace == TraceValues.Off:
+            return
+
+        params = LogTraceParams(message=message)
+        if verbose and self.trace == TraceValues.Verbose:
+            params.verbose = verbose
+
+        self.notify(LOG_TRACE, params)
+
+    def _publish_diagnostics_deprecator(
+        self,
+        params_or_uri: Union[str, PublishDiagnosticsParams],
+        diagnostics: Optional[List[Diagnostic]],
+        version: Optional[int],
+        **kwargs,
+    ) -> PublishDiagnosticsParams:
+        if isinstance(params_or_uri, str):
+            message = "DEPRECATION: "
+            "`publish_diagnostics("
+            "self, doc_uri: str, diagnostics: List[Diagnostic], version: Optional[int] = None)`"
+            "will be replaced with `publish_diagnostics(self, params: PublishDiagnosticsParams)`"
+            logging.warning(message)
+
+            params = self._construct_publish_diagnostic_type(
+                params_or_uri, diagnostics, version, **kwargs
+            )
+        else:
+            params = params_or_uri
+        return params
+
+    def _construct_publish_diagnostic_type(
+        self,
+        uri: str,
+        diagnostics: Optional[List[Diagnostic]],
+        version: Optional[int],
+        **kwargs,
+    ) -> PublishDiagnosticsParams:
+        if diagnostics is None:
+            diagnostics = []
+
+        args = {
+            **{"uri": uri, "diagnostics": diagnostics, "version": version},
+            **kwargs,
+        }
+
+        params = PublishDiagnosticsParams(**args)  # type:ignore
+        return params
+
+    def publish_diagnostics(
+        self,
+        params_or_uri: Union[str, PublishDiagnosticsParams],
+        diagnostics: Optional[List[Diagnostic]] = None,
+        version: Optional[int] = None,
+        **kwargs,
+    ):
+        """Sends diagnostic notification to the client.
+
+        .. deprecated:: 1.0.1
+
+           Passing ``(uri, diagnostics, version)`` as arguments is deprecated.
+           Pass an instance of :class:`~lsprotocol.types.PublishDiagnosticParams`
+           instead.
+
+        Parameters
+        ----------
+        params_or_uri
+           The :class:`~lsprotocol.types.PublishDiagnosticParams` to send to the client.
+
+        diagnostics
+           *Deprecated*. The diagnostics to publish
+
+        version
+           *Deprecated*: The version number
+        """
+        params = self._publish_diagnostics_deprecator(
+            params_or_uri, diagnostics, version, **kwargs
+        )
+        self.notify(TEXT_DOCUMENT_PUBLISH_DIAGNOSTICS, params)
+
+    def register_capability(
+        self, params: RegistrationParams, callback: Optional[Callable[[], None]] = None
+    ) -> Future:
+        """Register a new capability on the client.
+
+        Args:
+            params(RegistrationParams): RegistrationParams from lsp specs
+            callback(callable): Callabe which will be called after
+                                response from the client is received
+        Returns:
+            concurrent.futures.Future object that will be resolved once a
+            response has been received
+        """
+        return self.send_request(CLIENT_REGISTER_CAPABILITY, params, callback)
+
+    def register_capability_async(self, params: RegistrationParams) -> asyncio.Future:
+        """Register a new capability on the client.
+
+        Args:
+            params(RegistrationParams): RegistrationParams from lsp specs
+
+        Returns:
+            asyncio.Future object that will be resolved once a
+            response has been received
+        """
+        return asyncio.wrap_future(self.register_capability(params, None))
+
+    def semantic_tokens_refresh(
+        self, callback: Optional[Callable[[], None]] = None
+    ) -> Future:
+        """Requesting a refresh of all semantic tokens.
+
+        Args:
+            callback(callable): Callabe which will be called after
+                                response from the client is received
+
+        Returns:
+            concurrent.futures.Future object that will be resolved once a
+            response has been received
+        """
+        return self.send_request(WORKSPACE_SEMANTIC_TOKENS_REFRESH, callback=callback)
+
+    def semantic_tokens_refresh_async(self) -> asyncio.Future:
+        """Requesting a refresh of all semantic tokens.
+
+        Returns:
+            asyncio.Future object that will be resolved once a
+            response has been received
+        """
+        return asyncio.wrap_future(self.semantic_tokens_refresh(None))
+
+    def show_document(
+        self,
+        params: ShowDocumentParams,
+        callback: Optional[ShowDocumentCallbackType] = None,
+    ) -> Future:
+        """Display a particular document in the user interface.
+
+        Args:
+            params(ShowDocumentParams): ShowDocumentParams from lsp specs
+            callback(callable): Callabe which will be called after
+                                response from the client is received
+
+        Returns:
+            concurrent.futures.Future object that will be resolved once a
+            response has been received
+        """
+        return self.send_request(WINDOW_SHOW_DOCUMENT, params, callback)
+
+    def show_document_async(self, params: ShowDocumentParams) -> asyncio.Future:
+        """Display a particular document in the user interface.
+
+        Args:
+            params(ShowDocumentParams): ShowDocumentParams from lsp specs
+
+        Returns:
+            asyncio.Future object that will be resolved once a
+            response has been received
+        """
+        return asyncio.wrap_future(self.show_document(params, None))
+
+    def show_message(self, message, msg_type=MessageType.Info):
+        """Sends message to the client to display message."""
+        self.notify(
+            WINDOW_SHOW_MESSAGE, ShowMessageParams(type=msg_type, message=message)
+        )
+
+    def show_message_log(self, message, msg_type=MessageType.Log):
+        """Sends message to the client's output channel."""
+        self.notify(
+            WINDOW_LOG_MESSAGE, LogMessageParams(type=msg_type, message=message)
+        )
+
+    def unregister_capability(
+        self,
+        params: UnregistrationParams,
+        callback: Optional[Callable[[], None]] = None,
+    ) -> Future:
+        """Unregister a new capability on the client.
+
+        Args:
+            params(UnregistrationParams): UnregistrationParams from lsp specs
+            callback(callable): Callabe which will be called after
+                                response from the client is received
+        Returns:
+            concurrent.futures.Future object that will be resolved once a
+            response has been received
+        """
+        return self.send_request(CLIENT_UNREGISTER_CAPABILITY, params, callback)
+
+    def unregister_capability_async(
+        self, params: UnregistrationParams
+    ) -> asyncio.Future:
+        """Unregister a new capability on the client.
+
+        Args:
+            params(UnregistrationParams): UnregistrationParams from lsp specs
+            callback(callable): Callabe which will be called after
+                                response from the client is received
+        Returns:
+            asyncio.Future object that will be resolved once a
+            response has been received
+        """
+        return asyncio.wrap_future(self.unregister_capability(params, None))
diff --git a/server/libs/pygls/protocol/lsp_meta.py b/server/libs/pygls/protocol/lsp_meta.py
new file mode 100644
index 0000000..0dc52db
--- /dev/null
+++ b/server/libs/pygls/protocol/lsp_meta.py
@@ -0,0 +1,51 @@
+import functools
+import logging
+from pygls.constants import ATTR_FEATURE_TYPE
+from pygls.feature_manager import assign_help_attrs
+
+
+logger = logging.getLogger(__name__)
+
+
+def call_user_feature(base_func, method_name):
+    """Wraps generic LSP features and calls user registered feature
+    immediately after it.
+    """
+
+    @functools.wraps(base_func)
+    def decorator(self, *args, **kwargs):
+        ret_val = base_func(self, *args, **kwargs)
+
+        try:
+            user_func = self.fm.features[method_name]
+            self._execute_notification(user_func, *args, **kwargs)
+        except KeyError:
+            pass
+        except Exception:
+            logger.exception(
+                'Failed to handle user defined notification "%s": %s', method_name, args
+            )
+
+        return ret_val
+
+    return decorator
+
+
+class LSPMeta(type):
+    """Wraps LSP built-in features (`lsp_` naming convention).
+
+    Built-in features cannot be overridden but user defined features with
+    the same LSP name will be called after them.
+    """
+
+    def __new__(mcs, cls_name, cls_bases, cls):
+        for attr_name, attr_val in cls.items():
+            if callable(attr_val) and hasattr(attr_val, "method_name"):
+                method_name = attr_val.method_name
+                wrapped = call_user_feature(attr_val, method_name)
+                assign_help_attrs(wrapped, method_name, ATTR_FEATURE_TYPE)
+                cls[attr_name] = wrapped
+
+                logger.debug('Added decorator for lsp method: "%s"', attr_name)
+
+        return super().__new__(mcs, cls_name, cls_bases, cls)
diff --git a/server/libs/pygls/py.typed b/server/libs/pygls/py.typed
new file mode 100644
index 0000000..a9beb24
--- /dev/null
+++ b/server/libs/pygls/py.typed
@@ -0,0 +1,2 @@
+# Marker file for PEP 561. The pygls package uses inline types.
+
diff --git a/server/libs/pygls/server.py b/server/libs/pygls/server.py
new file mode 100644
index 0000000..7717b84
--- /dev/null
+++ b/server/libs/pygls/server.py
@@ -0,0 +1,616 @@
+############################################################################
+# Copyright(c) Open Law Library. All rights reserved.                      #
+# See ThirdPartyNotices.txt in the project root for additional notices.    #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import asyncio
+import json
+import logging
+import re
+import sys
+from concurrent.futures import Future, ThreadPoolExecutor
+from threading import Event
+from typing import (
+    Any,
+    Callable,
+    List,
+    Optional,
+    TextIO,
+    Type,
+    TypeVar,
+    Union,
+)
+
+import cattrs
+from pygls import IS_PYODIDE
+from pygls.lsp import ConfigCallbackType, ShowDocumentCallbackType
+from pygls.exceptions import (
+    FeatureNotificationError,
+    JsonRpcInternalError,
+    PyglsError,
+    JsonRpcException,
+    FeatureRequestError,
+)
+from lsprotocol.types import (
+    ClientCapabilities,
+    Diagnostic,
+    MessageType,
+    NotebookDocumentSyncOptions,
+    RegistrationParams,
+    ServerCapabilities,
+    ShowDocumentParams,
+    TextDocumentSyncKind,
+    UnregistrationParams,
+    WorkspaceApplyEditResponse,
+    WorkspaceEdit,
+    WorkspaceConfigurationParams,
+)
+from pygls.progress import Progress
+from pygls.protocol import JsonRPCProtocol, LanguageServerProtocol, default_converter
+from pygls.workspace import Workspace
+
+if not IS_PYODIDE:
+    from multiprocessing.pool import ThreadPool
+
+
+logger = logging.getLogger(__name__)
+
+F = TypeVar("F", bound=Callable)
+
+ServerErrors = Union[
+    PyglsError,
+    JsonRpcException,
+    Type[JsonRpcInternalError],
+    Type[FeatureNotificationError],
+    Type[FeatureRequestError],
+]
+
+
+async def aio_readline(loop, executor, stop_event, rfile, proxy):
+    """Reads data from stdin in separate thread (asynchronously)."""
+
+    CONTENT_LENGTH_PATTERN = re.compile(rb"^Content-Length: (\d+)\r\n$")
+
+    # Initialize message buffer
+    message = []
+    content_length = 0
+
+    while not stop_event.is_set() and not rfile.closed:
+        # Read a header line
+        header = await loop.run_in_executor(executor, rfile.readline)
+        if not header:
+            break
+        message.append(header)
+
+        # Extract content length if possible
+        if not content_length:
+            match = CONTENT_LENGTH_PATTERN.fullmatch(header)
+            if match:
+                content_length = int(match.group(1))
+                logger.debug("Content length: %s", content_length)
+
+        # Check if all headers have been read (as indicated by an empty line \r\n)
+        if content_length and not header.strip():
+            # Read body
+            body = await loop.run_in_executor(executor, rfile.read, content_length)
+            if not body:
+                break
+            message.append(body)
+
+            # Pass message to language server protocol
+            proxy(b"".join(message))
+
+            # Reset the buffer
+            message = []
+            content_length = 0
+
+
+class StdOutTransportAdapter:
+    """Protocol adapter which overrides write method.
+
+    Write method sends data to stdout.
+    """
+
+    def __init__(self, rfile, wfile):
+        self.rfile = rfile
+        self.wfile = wfile
+
+    def close(self):
+        self.rfile.close()
+        self.wfile.close()
+
+    def write(self, data):
+        self.wfile.write(data)
+        self.wfile.flush()
+
+
+class PyodideTransportAdapter:
+    """Protocol adapter which overrides write method.
+
+    Write method sends data to stdout.
+    """
+
+    def __init__(self, wfile):
+        self.wfile = wfile
+
+    def close(self):
+        self.wfile.close()
+
+    def write(self, data):
+        self.wfile.write(data)
+        self.wfile.flush()
+
+
+class WebSocketTransportAdapter:
+    """Protocol adapter which calls write method.
+
+    Write method sends data via the WebSocket interface.
+    """
+
+    def __init__(self, ws, loop):
+        self._ws = ws
+        self._loop = loop
+
+    def close(self) -> None:
+        """Stop the WebSocket server."""
+        self._ws.close()
+
+    def write(self, data: Any) -> None:
+        """Create a task to write specified data into a WebSocket."""
+        asyncio.ensure_future(self._ws.send(data))
+
+
+class Server:
+    """Base server class
+
+    Parameters
+    ----------
+    protocol_cls
+       Protocol implementation that must be derive from :class:`~pygls.protocol.JsonRPCProtocol`
+
+    converter_factory
+       Factory function to use when constructing a cattrs converter.
+
+    loop
+       The asyncio event loop
+
+    max_workers
+       Maximum number of workers for `ThreadPool` and `ThreadPoolExecutor`
+
+    """
+
+    def __init__(
+        self,
+        protocol_cls: Type[JsonRPCProtocol],
+        converter_factory: Callable[[], cattrs.Converter],
+        loop: Optional[asyncio.AbstractEventLoop] = None,
+        max_workers: int = 2,
+        sync_kind: TextDocumentSyncKind = TextDocumentSyncKind.Incremental,
+    ):
+        if not issubclass(protocol_cls, asyncio.Protocol):
+            raise TypeError("Protocol class should be subclass of asyncio.Protocol")
+
+        self._max_workers = max_workers
+        self._server = None
+        self._stop_event: Optional[Event] = None
+        self._thread_pool: Optional[ThreadPool] = None
+        self._thread_pool_executor: Optional[ThreadPoolExecutor] = None
+
+        if sync_kind is not None:
+            self.text_document_sync_kind = sync_kind
+
+        if loop is None:
+            loop = asyncio.new_event_loop()
+            asyncio.set_event_loop(loop)
+            self._owns_loop = True
+        else:
+            self._owns_loop = False
+
+        self.loop = loop
+
+        # TODO: Will move this to `LanguageServer` soon
+        self.lsp = protocol_cls(self, converter_factory())  # type: ignore
+
+    def shutdown(self):
+        """Shutdown server."""
+        logger.info("Shutting down the server")
+
+        if self._stop_event is not None:
+            self._stop_event.set()
+
+        if self._thread_pool:
+            self._thread_pool.terminate()
+            self._thread_pool.join()
+
+        if self._thread_pool_executor:
+            self._thread_pool_executor.shutdown()
+
+        if self._server:
+            self._server.close()
+            self.loop.run_until_complete(self._server.wait_closed())
+
+        if self._owns_loop and not self.loop.is_closed():
+            logger.info("Closing the event loop.")
+            self.loop.close()
+
+    def start_io(self, stdin: Optional[TextIO] = None, stdout: Optional[TextIO] = None):
+        """Starts IO server."""
+        logger.info("Starting IO server")
+
+        self._stop_event = Event()
+        transport = StdOutTransportAdapter(
+            stdin or sys.stdin.buffer, stdout or sys.stdout.buffer
+        )
+        self.lsp.connection_made(transport)  # type: ignore[arg-type]
+
+        try:
+            self.loop.run_until_complete(
+                aio_readline(
+                    self.loop,
+                    self.thread_pool_executor,
+                    self._stop_event,
+                    stdin or sys.stdin.buffer,
+                    self.lsp.data_received,
+                )
+            )
+        except BrokenPipeError:
+            logger.error("Connection to the client is lost! Shutting down the server.")
+        except (KeyboardInterrupt, SystemExit):
+            pass
+        finally:
+            self.shutdown()
+
+    def start_pyodide(self):
+        logger.info("Starting Pyodide server")
+
+        # Note: We don't actually start anything running as the main event
+        # loop will be handled by the web platform.
+        transport = PyodideTransportAdapter(sys.stdout)
+        self.lsp.connection_made(transport)  # type: ignore[arg-type]
+        self.lsp._send_only_body = True  # Don't send headers within the payload
+
+    def start_tcp(self, host: str, port: int) -> None:
+        """Starts TCP server."""
+        logger.info("Starting TCP server on %s:%s", host, port)
+
+        self._stop_event = Event()
+        self._server = self.loop.run_until_complete(  # type: ignore[assignment]
+            self.loop.create_server(self.lsp, host, port)
+        )
+        try:
+            self.loop.run_forever()
+        except (KeyboardInterrupt, SystemExit):
+            pass
+        finally:
+            self.shutdown()
+
+    def start_ws(self, host: str, port: int) -> None:
+        """Starts WebSocket server."""
+        try:
+            from websockets.server import serve
+        except ImportError:
+            logger.error("Run `pip install pygls[ws]` to install `websockets`.")
+            sys.exit(1)
+
+        logger.info("Starting WebSocket server on {}:{}".format(host, port))
+
+        self._stop_event = Event()
+        self.lsp._send_only_body = True  # Don't send headers within the payload
+
+        async def connection_made(websocket, _):
+            """Handle new connection wrapped in the WebSocket."""
+            self.lsp.transport = WebSocketTransportAdapter(websocket, self.loop)
+            async for message in websocket:
+                self.lsp._procedure_handler(
+                    json.loads(message, object_hook=self.lsp._deserialize_message)
+                )
+
+        start_server = serve(connection_made, host, port, loop=self.loop)
+        self._server = start_server.ws_server  # type: ignore[assignment]
+        self.loop.run_until_complete(start_server)
+
+        try:
+            self.loop.run_forever()
+        except (KeyboardInterrupt, SystemExit):
+            pass
+        finally:
+            self._stop_event.set()
+            self.shutdown()
+
+    if not IS_PYODIDE:
+
+        @property
+        def thread_pool(self) -> ThreadPool:
+            """Returns thread pool instance (lazy initialization)."""
+            if not self._thread_pool:
+                self._thread_pool = ThreadPool(processes=self._max_workers)
+
+            return self._thread_pool
+
+        @property
+        def thread_pool_executor(self) -> ThreadPoolExecutor:
+            """Returns thread pool instance (lazy initialization)."""
+            if not self._thread_pool_executor:
+                self._thread_pool_executor = ThreadPoolExecutor(
+                    max_workers=self._max_workers
+                )
+
+            return self._thread_pool_executor
+
+
+class LanguageServer(Server):
+    """The default LanguageServer
+
+    This class can be extended and it can be passed as a first argument to
+    registered commands/features.
+
+    .. |ServerInfo| replace:: :class:`~lsprotocol.types.InitializeResultServerInfoType`
+
+    Parameters
+    ----------
+    name
+       Name of the server, used to populate |ServerInfo| which is sent to
+       the client during initialization
+
+    version
+       Version of the server, used to populate |ServerInfo| which is sent to
+       the client during initialization
+
+    protocol_cls
+       The :class:`~pygls.protocol.LanguageServerProtocol` class definition, or any
+       subclass of it.
+
+    max_workers
+       Maximum number of workers for ``ThreadPool`` and ``ThreadPoolExecutor``
+
+    text_document_sync_kind
+       Text document synchronization method
+
+       None
+          No synchronization
+
+       :attr:`~lsprotocol.types.TextDocumentSyncKind.Full`
+          Send entire document text with each update
+
+       :attr:`~lsprotocol.types.TextDocumentSyncKind.Incremental`
+          Send only the region of text that changed with each update
+
+    notebook_document_sync
+       Advertise :lsp:`NotebookDocument` support to the client.
+    """
+
+    lsp: LanguageServerProtocol
+
+    default_error_message = (
+        "Unexpected error in LSP server, see server's logs for details"
+    )
+    """
+    The default error message sent to the user's editor when this server encounters an uncaught
+    exception.
+    """
+
+    def __init__(
+        self,
+        name: str,
+        version: str,
+        loop=None,
+        protocol_cls: Type[LanguageServerProtocol] = LanguageServerProtocol,
+        converter_factory=default_converter,
+        text_document_sync_kind: TextDocumentSyncKind = TextDocumentSyncKind.Incremental,
+        notebook_document_sync: Optional[NotebookDocumentSyncOptions] = None,
+        max_workers: int = 2,
+    ):
+        if not issubclass(protocol_cls, LanguageServerProtocol):
+            raise TypeError(
+                "Protocol class should be subclass of LanguageServerProtocol"
+            )
+
+        self.name = name
+        self.version = version
+        self._text_document_sync_kind = text_document_sync_kind
+        self._notebook_document_sync = notebook_document_sync
+        self.process_id: Optional[Union[int, None]] = None
+        super().__init__(protocol_cls, converter_factory, loop, max_workers)
+
+    def apply_edit(
+        self, edit: WorkspaceEdit, label: Optional[str] = None
+    ) -> WorkspaceApplyEditResponse:
+        """Sends apply edit request to the client."""
+        return self.lsp.apply_edit(edit, label)
+
+    def apply_edit_async(
+        self, edit: WorkspaceEdit, label: Optional[str] = None
+    ) -> WorkspaceApplyEditResponse:
+        """Sends apply edit request to the client. Should be called with `await`"""
+        return self.lsp.apply_edit_async(edit, label)
+
+    def command(self, command_name: str) -> Callable[[F], F]:
+        """Decorator used to register custom commands.
+
+        Example
+        -------
+        ::
+
+           @ls.command('myCustomCommand')
+           def my_cmd(ls, a, b, c):
+               pass
+        """
+        return self.lsp.fm.command(command_name)
+
+    @property
+    def client_capabilities(self) -> ClientCapabilities:
+        """The client's capabilities."""
+        return self.lsp.client_capabilities
+
+    def feature(
+        self,
+        feature_name: str,
+        options: Optional[Any] = None,
+    ) -> Callable[[F], F]:
+        """Decorator used to register LSP features.
+
+        Example
+        -------
+        ::
+
+           @ls.feature('textDocument/completion', CompletionOptions(trigger_characters=['.']))
+           def completions(ls, params: CompletionParams):
+               return CompletionList(is_incomplete=False, items=[CompletionItem("Completion 1")])
+        """
+        return self.lsp.fm.feature(feature_name, options)
+
+    def get_configuration(
+        self,
+        params: WorkspaceConfigurationParams,
+        callback: Optional[ConfigCallbackType] = None,
+    ) -> Future:
+        """Gets the configuration settings from the client."""
+        return self.lsp.get_configuration(params, callback)
+
+    def get_configuration_async(
+        self, params: WorkspaceConfigurationParams
+    ) -> asyncio.Future:
+        """Gets the configuration settings from the client. Should be called with `await`"""
+        return self.lsp.get_configuration_async(params)
+
+    def log_trace(self, message: str, verbose: Optional[str] = None) -> None:
+        """Sends trace notification to the client."""
+        self.lsp.log_trace(message, verbose)
+
+    @property
+    def progress(self) -> Progress:
+        """Gets the object to manage client's progress bar."""
+        return self.lsp.progress
+
+    def publish_diagnostics(
+        self,
+        uri: str,
+        diagnostics: Optional[List[Diagnostic]] = None,
+        version: Optional[int] = None,
+        **kwargs
+    ):
+        """
+        Sends diagnostic notification to the client.
+        """
+        params = self.lsp._construct_publish_diagnostic_type(
+            uri, diagnostics, version, **kwargs
+        )
+        self.lsp.publish_diagnostics(params, **kwargs)
+
+    def register_capability(
+        self, params: RegistrationParams, callback: Optional[Callable[[], None]] = None
+    ) -> Future:
+        """Register a new capability on the client."""
+        return self.lsp.register_capability(params, callback)
+
+    def register_capability_async(self, params: RegistrationParams) -> asyncio.Future:
+        """Register a new capability on the client. Should be called with `await`"""
+        return self.lsp.register_capability_async(params)
+
+    def semantic_tokens_refresh(
+        self, callback: Optional[Callable[[], None]] = None
+    ) -> Future:
+        """Request a refresh of all semantic tokens."""
+        return self.lsp.semantic_tokens_refresh(callback)
+
+    def semantic_tokens_refresh_async(self) -> asyncio.Future:
+        """Request a refresh of all semantic tokens. Should be called with `await`"""
+        return self.lsp.semantic_tokens_refresh_async()
+
+    def send_notification(self, method: str, params: object = None) -> None:
+        """Sends notification to the client."""
+        self.lsp.notify(method, params)
+
+    @property
+    def server_capabilities(self) -> ServerCapabilities:
+        """Return server capabilities."""
+        return self.lsp.server_capabilities
+
+    def show_document(
+        self,
+        params: ShowDocumentParams,
+        callback: Optional[ShowDocumentCallbackType] = None,
+    ) -> Future:
+        """Display a particular document in the user interface."""
+        return self.lsp.show_document(params, callback)
+
+    def show_document_async(self, params: ShowDocumentParams) -> asyncio.Future:
+        """Display a particular document in the user interface. Should be called with `await`"""
+        return self.lsp.show_document_async(params)
+
+    def show_message(self, message, msg_type=MessageType.Info) -> None:
+        """Sends message to the client to display message."""
+        self.lsp.show_message(message, msg_type)
+
+    def show_message_log(self, message, msg_type=MessageType.Log) -> None:
+        """Sends message to the client's output channel."""
+        self.lsp.show_message_log(message, msg_type)
+
+    def _report_server_error(
+        self,
+        error: Exception,
+        source: ServerErrors,
+    ):
+        # Prevent recursive error reporting
+        try:
+            self.report_server_error(error, source)
+        except Exception:
+            logger.warning("Failed to report error to client")
+
+    def report_server_error(self, error: Exception, source: ServerErrors):
+        """
+        Sends error to the client for displaying.
+
+        By default this fucntion does not handle LSP request errors. This is because LSP requests
+        require direct responses and so already have a mechanism for including unexpected errors
+        in the response body.
+
+        All other errors are "out of band" in the sense that the client isn't explicitly waiting
+        for them. For example diagnostics are returned as notifications, not responses to requests,
+        and so can seemingly be sent at random. Also for example consider JSON RPC serialization
+        and deserialization, if a payload cannot be parsed then the whole request/response cycle
+        cannot be completed and so one of these "out of band" error messages is sent.
+
+        These "out of band" error messages are not a requirement of the LSP spec. Pygls simply
+        offers this behaviour as a recommended default. It is perfectly reasonble to override this
+        default.
+        """
+
+        if source == FeatureRequestError:
+            return
+
+        self.show_message(self.default_error_message, msg_type=MessageType.Error)
+
+    def thread(self) -> Callable[[F], F]:
+        """Decorator that mark function to execute it in a thread."""
+        return self.lsp.thread()
+
+    def unregister_capability(
+        self,
+        params: UnregistrationParams,
+        callback: Optional[Callable[[], None]] = None,
+    ) -> Future:
+        """Unregister a new capability on the client."""
+        return self.lsp.unregister_capability(params, callback)
+
+    def unregister_capability_async(
+        self, params: UnregistrationParams
+    ) -> asyncio.Future:
+        """Unregister a new capability on the client. Should be called with `await`"""
+        return self.lsp.unregister_capability_async(params)
+
+    @property
+    def workspace(self) -> Workspace:
+        """Returns in-memory workspace."""
+        return self.lsp.workspace
diff --git a/server/libs/pygls/uris.py b/server/libs/pygls/uris.py
new file mode 100644
index 0000000..8c40f70
--- /dev/null
+++ b/server/libs/pygls/uris.py
@@ -0,0 +1,184 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc.                 #
+# Original work licensed under the MIT License.                            #
+# See ThirdPartyNotices.txt in the project root for license information.   #
+# All modifications Copyright (c) Open Law Library. All rights reserved.   #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+"""
+A collection of URI utilities with logic built on the VSCode URI library.
+
+https://github.com/Microsoft/vscode-uri/blob/e59cab84f5df6265aed18ae5f43552d3eef13bb9/lib/index.ts
+"""
+from typing import Optional, Tuple
+
+import re
+from urllib import parse
+
+from pygls import IS_WIN
+
+RE_DRIVE_LETTER_PATH = re.compile(r"^\/[a-zA-Z]:")
+
+URLParts = Tuple[str, str, str, str, str, str]
+
+
+def _normalize_win_path(path: str):
+    netloc = ""
+
+    # normalize to fwd-slashes on windows,
+    # on other systems bwd-slashes are valid
+    # filename character, eg /f\oo/ba\r.txt
+    if IS_WIN:
+        path = path.replace("\\", "/")
+
+    # check for authority as used in UNC shares
+    # or use the path as given
+    if path[:2] == "//":
+        idx = path.index("/", 2)
+        if idx == -1:
+            netloc = path[2:]
+        else:
+            netloc = path[2:idx]
+            path = path[idx:]
+
+    # Ensure that path starts with a slash
+    # or that it is at least a slash
+    if not path.startswith("/"):
+        path = "/" + path
+
+    # Normalize drive paths to lower case
+    if RE_DRIVE_LETTER_PATH.match(path):
+        path = path[0] + path[1].lower() + path[2:]
+
+    return path, netloc
+
+
+def from_fs_path(path: str):
+    """Returns a URI for the given filesystem path."""
+    try:
+        scheme = "file"
+        params, query, fragment = "", "", ""
+        path, netloc = _normalize_win_path(path)
+        return urlunparse((scheme, netloc, path, params, query, fragment))
+    except (AttributeError, TypeError):
+        return None
+
+
+def to_fs_path(uri: str):
+    """
+    Returns the filesystem path of the given URI.
+
+    Will handle UNC paths and normalize windows drive letters to lower-case.
+    Also uses the platform specific path separator. Will *not* validate the
+    path for invalid characters and semantics.
+    Will *not* look at the scheme of this URI.
+    """
+    try:
+        # scheme://netloc/path;parameters?query#fragment
+        scheme, netloc, path, _, _, _ = urlparse(uri)
+
+        if netloc and path and scheme == "file":
+            # unc path: file://shares/c$/far/boo
+            value = f"//{netloc}{path}"
+
+        elif RE_DRIVE_LETTER_PATH.match(path):
+            # windows drive letter: file:///C:/far/boo
+            value = path[1].lower() + path[2:]
+
+        else:
+            # Other path
+            value = path
+
+        if IS_WIN:
+            value = value.replace("/", "\\")
+
+        return value
+    except TypeError:
+        return None
+
+
+def uri_scheme(uri: str):
+    try:
+        return urlparse(uri)[0]
+    except (TypeError, IndexError):
+        return None
+
+
+# TODO: Use `URLParts` type
+def uri_with(
+    uri: str,
+    scheme: Optional[str] = None,
+    netloc: Optional[str] = None,
+    path: Optional[str] = None,
+    params: Optional[str] = None,
+    query: Optional[str] = None,
+    fragment: Optional[str] = None,
+):
+    """
+    Return a URI with the given part(s) replaced.
+    Parts are decoded / encoded.
+    """
+    old_scheme, old_netloc, old_path, old_params, old_query, old_fragment = urlparse(
+        uri
+    )
+
+    if path is None:
+        raise Exception("`path` must not be None")
+
+    path, _ = _normalize_win_path(path)
+    return urlunparse(
+        (
+            scheme or old_scheme,
+            netloc or old_netloc,
+            path or old_path,
+            params or old_params,
+            query or old_query,
+            fragment or old_fragment,
+        )
+    )
+
+
+def urlparse(uri: str):
+    """Parse and decode the parts of a URI."""
+    scheme, netloc, path, params, query, fragment = parse.urlparse(uri)
+    return (
+        parse.unquote(scheme),
+        parse.unquote(netloc),
+        parse.unquote(path),
+        parse.unquote(params),
+        parse.unquote(query),
+        parse.unquote(fragment),
+    )
+
+
+def urlunparse(parts: URLParts) -> str:
+    """Unparse and encode parts of a URI."""
+    scheme, netloc, path, params, query, fragment = parts
+
+    # Avoid encoding the windows drive letter colon
+    if RE_DRIVE_LETTER_PATH.match(path):
+        quoted_path = path[:3] + parse.quote(path[3:])
+    else:
+        quoted_path = parse.quote(path)
+
+    return parse.urlunparse(
+        (
+            parse.quote(scheme),
+            parse.quote(netloc),
+            quoted_path,
+            parse.quote(params),
+            parse.quote(query),
+            parse.quote(fragment),
+        )
+    )
diff --git a/server/libs/pygls/workspace/__init__.py b/server/libs/pygls/workspace/__init__.py
new file mode 100644
index 0000000..53e9b1f
--- /dev/null
+++ b/server/libs/pygls/workspace/__init__.py
@@ -0,0 +1,97 @@
+from typing import List
+import warnings
+
+from lsprotocol import types
+
+from .workspace import Workspace
+from .text_document import TextDocument
+from .position_codec import PositionCodec
+
+# For backwards compatibility
+Document = TextDocument
+
+
+def utf16_unit_offset(chars: str):
+    warnings.warn(
+        "'utf16_unit_offset' has been deprecated, instead use "
+        "'PositionCodec.utf16_unit_offset' via 'workspace.position_codec' "
+        "or 'text_document.position_codec'",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    _codec = PositionCodec()
+    return _codec.utf16_unit_offset(chars)
+
+
+def utf16_num_units(chars: str):
+    warnings.warn(
+        "'utf16_num_units' has been deprecated, instead use "
+        "'PositionCodec.client_num_units' via 'workspace.position_codec' "
+        "or 'text_document.position_codec'",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    _codec = PositionCodec()
+    return _codec.client_num_units(chars)
+
+
+def position_from_utf16(lines: List[str], position: types.Position):
+    warnings.warn(
+        "'position_from_utf16' has been deprecated, instead use "
+        "'PositionCodec.position_from_client_units' via "
+        "'workspace.position_codec' or 'text_document.position_codec'",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    _codec = PositionCodec()
+    return _codec.position_from_client_units(lines, position)
+
+
+def position_to_utf16(lines: List[str], position: types.Position):
+    warnings.warn(
+        "'position_to_utf16' has been deprecated, instead use "
+        "'PositionCodec.position_to_client_units' via "
+        "'workspace.position_codec' or 'text_document.position_codec'",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    _codec = PositionCodec()
+    return _codec.position_to_client_units(lines, position)
+
+
+def range_from_utf16(lines: List[str], range: types.Range):
+    warnings.warn(
+        "'range_from_utf16' has been deprecated, instead use "
+        "'PositionCodec.range_from_client_units' via "
+        "'workspace.position_codec' or 'text_document.position_codec'",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    _codec = PositionCodec()
+    return _codec.range_from_client_units(lines, range)
+
+
+def range_to_utf16(lines: List[str], range: types.Range):
+    warnings.warn(
+        "'range_to_utf16' has been deprecated, instead use "
+        "'PositionCodec.range_to_client_units' via 'workspace.position_codec' "
+        "or 'text_document.position_codec'",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    _codec = PositionCodec()
+    return _codec.range_to_client_units(lines, range)
+
+
+__all__ = (
+    "Workspace",
+    "TextDocument",
+    "PositionCodec",
+    "Document",
+    "utf16_unit_offset",
+    "utf16_num_units",
+    "position_from_utf16",
+    "position_to_utf16",
+    "range_from_utf16",
+    "range_to_utf16",
+)
diff --git a/server/libs/pygls/workspace/position_codec.py b/server/libs/pygls/workspace/position_codec.py
new file mode 100644
index 0000000..b182c62
--- /dev/null
+++ b/server/libs/pygls/workspace/position_codec.py
@@ -0,0 +1,206 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc.                 #
+# Original work licensed under the MIT License.                            #
+# See ThirdPartyNotices.txt in the project root for license information.   #
+# All modifications Copyright (c) Open Law Library. All rights reserved.   #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import logging
+from typing import List, Optional, Union
+
+from lsprotocol import types
+
+
+log = logging.getLogger(__name__)
+
+
+class PositionCodec:
+    def __init__(
+        self,
+        encoding: Optional[
+            Union[types.PositionEncodingKind, str]
+        ] = types.PositionEncodingKind.Utf16,
+    ):
+        self.encoding = encoding
+
+    @classmethod
+    def is_char_beyond_multilingual_plane(cls, char: str) -> bool:
+        return ord(char) > 0xFFFF
+
+    def utf16_unit_offset(self, chars: str):
+        """
+        Calculate the number of characters which need two utf-16 code units.
+
+        Arguments:
+            chars (str): The string to count occurrences of utf-16 code units for.
+        """
+        return sum(self.is_char_beyond_multilingual_plane(ch) for ch in chars)
+
+    def client_num_units(self, chars: str):
+        """
+        Calculate the length of `str` in client-supported UTF-[32|16|8] code units.
+
+        Arguments:
+            chars (str): The string to return the length in UTF-[32|16|8] code units for.
+        """
+        utf32_units = len(chars)
+        if self.encoding == types.PositionEncodingKind.Utf32:
+            return utf32_units
+
+        if self.encoding == types.PositionEncodingKind.Utf8:
+            return utf32_units + (self.utf16_unit_offset(chars) * 2)
+
+        return utf32_units + self.utf16_unit_offset(chars)
+
+    def position_from_client_units(
+        self, lines: List[str], position: types.Position
+    ) -> types.Position:
+        """
+        Convert the position.character from UTF-[32|16|8] code units to UTF-32.
+
+        A python application can't use the character member of `Position`
+        directly. As per specification it is represented as a zero-based line and
+        character offset based on posible a UTF-[32|16|8] string representation.
+
+        All characters whose code point exceeds the Basic Multilingual Plane are
+        represented by 2 UTF-16 or 4 UTF-8 code units.
+
+        The offset of the closing quotation mark in x="😋" is
+        - 7 in UTF-8 representation
+        - 5 in UTF-16 representation
+        - 4 in UTF-32 representation
+
+        see: https://github.com/microsoft/language-server-protocol/issues/376
+
+        Arguments:
+            lines (list):
+                The content of the document which the position refers to.
+            position (Position):
+                The line and character offset in UTF-[32|16|8] code units.
+
+        Returns:
+            The position with `character` being converted to UTF-32 code units.
+        """
+        if len(lines) == 0:
+            return types.Position(0, 0)
+        if position.line >= len(lines):
+            return types.Position(len(lines) - 1, self.client_num_units(lines[-1]))
+
+        _line = lines[position.line]
+        _line = _line.replace("\r\n", "\n")  # TODO: it's a bit of a hack
+        _client_len = self.client_num_units(_line)
+        _utf32_len = len(_line)
+
+        if _client_len == 0:
+            return types.Position(position.line, 0)
+
+        _client_end_of_line = self.client_num_units(_line)
+        if position.character > _client_end_of_line:
+            position.character = _client_end_of_line - 1
+
+        _client_index = 0
+        utf32_index = 0
+        while True:
+            _is_searching_queried_position = _client_index < position.character
+            _is_before_end_of_line = utf32_index < _utf32_len
+            _is_searching_for_position = (
+                _is_searching_queried_position and _is_before_end_of_line
+            )
+            if not _is_searching_for_position:
+                break
+
+            _current_char = _line[utf32_index]
+            _is_double_width = PositionCodec.is_char_beyond_multilingual_plane(
+                _current_char
+            )
+            if _is_double_width:
+                if self.encoding == types.PositionEncodingKind.Utf32:
+                    _client_index += 1
+                if self.encoding == types.PositionEncodingKind.Utf8:
+                    _client_index += 4
+                _client_index += 2
+            else:
+                _client_index += 1
+            utf32_index += 1
+
+        position = types.Position(line=position.line, character=utf32_index)
+        return position
+
+    def position_to_client_units(
+        self, lines: List[str], position: types.Position
+    ) -> types.Position:
+        """
+        Convert the position.character from its internal UTF-32 representation
+        to client-supported UTF-[32|16|8] code units.
+
+        Arguments:
+            lines (list):
+                The content of the document which the position refers to.
+            position (Position):
+                The line and character offset in UTF-32 code units.
+
+        Returns:
+            The position with `character` being converted to UTF-[32|16|8] code units.
+        """
+        try:
+            character = self.client_num_units(
+                lines[position.line][: position.character]
+            )
+            return types.Position(
+                line=position.line,
+                character=character,
+            )
+        except IndexError:
+            return types.Position(line=len(lines), character=0)
+
+    def range_from_client_units(
+        self, lines: List[str], range: types.Range
+    ) -> types.Range:
+        """
+        Convert range.[start|end].character from UTF-[32|16|8] code units to UTF-32.
+
+        Arguments:
+            lines (list):
+                The content of the document which the range refers to.
+            range (Range):
+                The line and character offset in UTF-[32|16|8] code units.
+
+        Returns:
+            The range with `character` offsets being converted to UTF-32 code units.
+        """
+        range_new = types.Range(
+            start=self.position_from_client_units(lines, range.start),
+            end=self.position_from_client_units(lines, range.end),
+        )
+        return range_new
+
+    def range_to_client_units(
+        self, lines: List[str], range: types.Range
+    ) -> types.Range:
+        """
+        Convert range.[start|end].character from UTF-32 to UTF-[32|16|8] code units.
+
+        Arguments:
+            lines (list):
+                The content of the document which the range refers to.
+            range (Range):
+                The line and character offset in  code units.
+
+        Returns:
+            The range with `character` offsets being converted to UTF-[32|16|8] code units.
+        """
+        return types.Range(
+            start=self.position_to_client_units(lines, range.start),
+            end=self.position_to_client_units(lines, range.end),
+        )
diff --git a/server/libs/pygls/workspace/text_document.py b/server/libs/pygls/workspace/text_document.py
new file mode 100644
index 0000000..d62c6aa
--- /dev/null
+++ b/server/libs/pygls/workspace/text_document.py
@@ -0,0 +1,238 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc.                 #
+# Original work licensed under the MIT License.                            #
+# See ThirdPartyNotices.txt in the project root for license information.   #
+# All modifications Copyright (c) Open Law Library. All rights reserved.   #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import io
+import logging
+import os
+import re
+from typing import List, Optional, Pattern
+
+from lsprotocol import types
+
+from pygls.uris import to_fs_path
+from .position_codec import PositionCodec
+
+# TODO: this is not the best e.g. we capture numbers
+RE_END_WORD = re.compile("^[A-Za-z_0-9]*")
+RE_START_WORD = re.compile("[A-Za-z_0-9]*$")
+
+logger = logging.getLogger(__name__)
+
+
+class TextDocument(object):
+    def __init__(
+        self,
+        uri: str,
+        source: Optional[str] = None,
+        version: Optional[int] = None,
+        language_id: Optional[str] = None,
+        local: bool = True,
+        sync_kind: types.TextDocumentSyncKind = types.TextDocumentSyncKind.Incremental,
+        position_codec: Optional[PositionCodec] = None,
+    ):
+        self.uri = uri
+        self.version = version
+        path = to_fs_path(uri)
+        if path is None:
+            raise Exception("`path` cannot be None")
+        self.path = path
+        self.language_id = language_id
+        self.filename: Optional[str] = os.path.basename(self.path)
+
+        self._local = local
+        self._source = source
+
+        self._is_sync_kind_full = sync_kind == types.TextDocumentSyncKind.Full
+        self._is_sync_kind_incremental = (
+            sync_kind == types.TextDocumentSyncKind.Incremental
+        )
+        self._is_sync_kind_none = sync_kind == types.TextDocumentSyncKind.None_
+
+        self._position_codec = position_codec if position_codec else PositionCodec()
+
+    def __str__(self):
+        return str(self.uri)
+
+    @property
+    def position_codec(self) -> PositionCodec:
+        return self._position_codec
+
+    def _apply_incremental_change(
+        self, change: types.TextDocumentContentChangeEvent_Type1
+    ) -> None:
+        """Apply an ``Incremental`` text change to the document"""
+        lines = self.lines
+        text = change.text
+        change_range = change.range
+
+        range = self._position_codec.range_from_client_units(lines, change_range)
+        start_line = range.start.line
+        start_col = range.start.character
+        end_line = range.end.line
+        end_col = range.end.character
+
+        # Check for an edit occurring at the very end of the file
+        if start_line == len(lines):
+            self._source = self.source + text
+            return
+
+        new = io.StringIO()
+
+        # Iterate over the existing document until we hit the edit range,
+        # at which point we write the new text, then loop until we hit
+        # the end of the range and continue writing.
+        for i, line in enumerate(lines):
+            if i < start_line:
+                new.write(line)
+                continue
+
+            if i > end_line:
+                new.write(line)
+                continue
+
+            if i == start_line:
+                new.write(line[:start_col])
+                new.write(text)
+
+            if i == end_line:
+                new.write(line[end_col:])
+
+        self._source = new.getvalue()
+
+    def _apply_full_change(self, change: types.TextDocumentContentChangeEvent) -> None:
+        """Apply a ``Full`` text change to the document."""
+        self._source = change.text
+
+    def _apply_none_change(self, _: types.TextDocumentContentChangeEvent) -> None:
+        """Apply a ``None`` text change to the document
+
+        Currently does nothing, provided for consistency.
+        """
+        pass
+
+    def apply_change(self, change: types.TextDocumentContentChangeEvent) -> None:
+        """Apply a text change to a document, considering TextDocumentSyncKind
+
+        Performs either
+        :attr:`~lsprotocol.types.TextDocumentSyncKind.Incremental`,
+        :attr:`~lsprotocol.types.TextDocumentSyncKind.Full`, or no synchronization
+        based on both the client request and server capabilities.
+
+        .. admonition:: ``Incremental`` versus ``Full`` synchronization
+
+           Even if a server accepts ``Incremantal`` SyncKinds, clients may request
+           a ``Full`` SyncKind. In LSP 3.x, clients make this request by omitting
+           both Range and RangeLength from their request. Consequently, the
+           attributes "range" and "rangeLength" will be missing from ``Full``
+           content update client requests in the pygls Python library.
+
+        """
+        if isinstance(change, types.TextDocumentContentChangeEvent_Type1):
+            if self._is_sync_kind_incremental:
+                self._apply_incremental_change(change)
+                return
+            # Log an error, but still perform full update to preserve existing
+            # assumptions in test_document/test_document_full_edit. Test breaks
+            # otherwise, and fixing the tests would require a broader fix to
+            # protocol.py.
+            logger.error(
+                "Unsupported client-provided TextDocumentContentChangeEvent. "
+                "Please update / submit a Pull Request to your LSP client."
+            )
+
+        if self._is_sync_kind_none:
+            self._apply_none_change(change)
+        else:
+            self._apply_full_change(change)
+
+    @property
+    def lines(self) -> List[str]:
+        return self.source.splitlines(True)
+
+    def offset_at_position(self, client_position: types.Position) -> int:
+        """Return the character offset pointed at by the given client_position."""
+        lines = self.lines
+        server_position = self._position_codec.position_from_client_units(
+            lines, client_position
+        )
+        row, col = server_position.line, server_position.character
+        return col + sum(
+            self._position_codec.client_num_units(line) for line in lines[:row]
+        )
+
+    @property
+    def source(self) -> str:
+        if self._source is None:
+            with io.open(self.path, "r", encoding="utf-8") as f:
+                return f.read()
+        return self._source
+
+    def word_at_position(
+        self,
+        client_position: types.Position,
+        re_start_word: Pattern[str] = RE_START_WORD,
+        re_end_word: Pattern[str] = RE_END_WORD,
+    ) -> str:
+        """Return the word at position.
+
+        The word is constructed in two halves, the first half is found by taking
+        the first match of ``re_start_word`` on the line up until
+        ``position.character``.
+
+        The second half is found by taking ``position.character`` up until the
+        last match of ``re_end_word`` on the line.
+
+        :func:`python:re.findall` is used to find the matches.
+
+        Parameters
+        ----------
+        position
+           The line and character offset.
+
+        re_start_word
+           The regular expression for extracting the word backward from
+           position. The default pattern is ``[A-Za-z_0-9]*$``.
+
+        re_end_word
+           The regular expression for extracting the word forward from
+           position. The default pattern is ``^[A-Za-z_0-9]*``.
+
+        Returns
+        -------
+        str
+           The word (obtained by concatenating the two matches) at position.
+        """
+        lines = self.lines
+        if client_position.line >= len(lines):
+            return ""
+
+        server_position = self._position_codec.position_from_client_units(
+            lines, client_position
+        )
+        row, col = server_position.line, server_position.character
+        line = lines[row]
+        # Split word in two
+        start = line[:col]
+        end = line[col:]
+
+        # Take end of start and start of end to find word
+        # These are guaranteed to match, even if they match the empty string
+        m_start = re_start_word.findall(start)
+        m_end = re_end_word.findall(end)
+
+        return m_start[0] + m_end[-1]
diff --git a/server/libs/pygls/workspace/workspace.py b/server/libs/pygls/workspace/workspace.py
new file mode 100644
index 0000000..405a798
--- /dev/null
+++ b/server/libs/pygls/workspace/workspace.py
@@ -0,0 +1,323 @@
+############################################################################
+# Original work Copyright 2017 Palantir Technologies, Inc.                 #
+# Original work licensed under the MIT License.                            #
+# See ThirdPartyNotices.txt in the project root for license information.   #
+# All modifications Copyright (c) Open Law Library. All rights reserved.   #
+#                                                                          #
+# Licensed under the Apache License, Version 2.0 (the "License")           #
+# you may not use this file except in compliance with the License.         #
+# You may obtain a copy of the License at                                  #
+#                                                                          #
+#     http: // www.apache.org/licenses/LICENSE-2.0                         #
+#                                                                          #
+# Unless required by applicable law or agreed to in writing, software      #
+# distributed under the License is distributed on an "AS IS" BASIS,        #
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
+# See the License for the specific language governing permissions and      #
+# limitations under the License.                                           #
+############################################################################
+import copy
+import logging
+import os
+import warnings
+from typing import Dict, List, Optional, Union
+
+from lsprotocol import types
+from lsprotocol.types import (
+    PositionEncodingKind,
+    TextDocumentSyncKind,
+    WorkspaceFolder,
+)
+from pygls.uris import to_fs_path, uri_scheme
+from pygls.workspace.text_document import TextDocument
+from pygls.workspace.position_codec import PositionCodec
+
+logger = logging.getLogger(__name__)
+
+
+class Workspace(object):
+    def __init__(
+        self,
+        root_uri: Optional[str],
+        sync_kind: TextDocumentSyncKind = TextDocumentSyncKind.Incremental,
+        workspace_folders: Optional[List[WorkspaceFolder]] = None,
+        position_encoding: Optional[
+            Union[PositionEncodingKind, str]
+        ] = PositionEncodingKind.Utf16,
+    ):
+        self._root_uri = root_uri
+        if self._root_uri is not None:
+            self._root_uri_scheme = uri_scheme(self._root_uri)
+            root_path = to_fs_path(self._root_uri)
+            if root_path is None:
+                raise Exception("Couldn't get `root_path` from `root_uri`")
+            self._root_path = root_path
+        else:
+            self._root_path = None
+        self._sync_kind = sync_kind
+        self._text_documents: Dict[str, TextDocument] = {}
+        self._notebook_documents: Dict[str, types.NotebookDocument] = {}
+
+        # Used to lookup notebooks which contain a given cell.
+        self._cell_in_notebook: Dict[str, str] = {}
+        self._folders: Dict[str, WorkspaceFolder] = {}
+        self._docs: Dict[str, TextDocument] = {}
+        self._position_encoding = position_encoding
+        self._position_codec = PositionCodec(encoding=position_encoding)
+
+        if workspace_folders is not None:
+            for folder in workspace_folders:
+                self.add_folder(folder)
+
+    @property
+    def position_encoding(self) -> Optional[Union[PositionEncodingKind, str]]:
+        return self._position_encoding
+
+    @property
+    def position_codec(self) -> PositionCodec:
+        return self._position_codec
+
+    def _create_text_document(
+        self,
+        doc_uri: str,
+        source: Optional[str] = None,
+        version: Optional[int] = None,
+        language_id: Optional[str] = None,
+    ) -> TextDocument:
+        return TextDocument(
+            doc_uri,
+            source=source,
+            version=version,
+            language_id=language_id,
+            sync_kind=self._sync_kind,
+            position_codec=self._position_codec,
+        )
+
+    def add_folder(self, folder: WorkspaceFolder):
+        self._folders[folder.uri] = folder
+
+    @property
+    def documents(self):
+        warnings.warn(
+            "'workspace.documents' has been deprecated, use "
+            "'workspace.text_documents' instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.text_documents
+
+    @property
+    def notebook_documents(self):
+        return self._notebook_documents
+
+    @property
+    def text_documents(self):
+        return self._text_documents
+
+    @property
+    def folders(self):
+        return self._folders
+
+    def get_notebook_document(
+        self, *, notebook_uri: Optional[str] = None, cell_uri: Optional[str] = None
+    ) -> Optional[types.NotebookDocument]:
+        """Return the notebook corresponding with the given uri.
+
+        If both ``notebook_uri`` and ``cell_uri`` are given, ``notebook_uri`` takes
+        precedence.
+
+        Parameters
+        ----------
+        notebook_uri
+           If given, return the notebook document with the given uri.
+
+        cell_uri
+           If given, return the notebook document which contains a cell with the
+           given uri
+
+        Returns
+        -------
+        Optional[NotebookDocument]
+           The requested notebook document if found, ``None`` otherwise.
+        """
+        if notebook_uri is not None:
+            return self._notebook_documents.get(notebook_uri)
+
+        if cell_uri is not None:
+            notebook_uri = self._cell_in_notebook.get(cell_uri)
+            if notebook_uri is None:
+                return None
+
+            return self._notebook_documents.get(notebook_uri)
+
+        return None
+
+    def get_text_document(self, doc_uri: str) -> TextDocument:
+        """
+        Return a managed document if-present,
+        else create one pointing at disk.
+
+        See https://github.com/Microsoft/language-server-protocol/issues/177
+        """
+        return self._text_documents.get(doc_uri) or self._create_text_document(doc_uri)
+
+    def is_local(self):
+        return (
+            self._root_uri_scheme == "" or self._root_uri_scheme == "file"
+        ) and os.path.exists(self._root_path)
+
+    def put_notebook_document(self, params: types.DidOpenNotebookDocumentParams):
+        notebook = params.notebook_document
+
+        # Create a fresh instance to ensure our copy cannot be accidentally modified.
+        self._notebook_documents[notebook.uri] = copy.deepcopy(notebook)
+
+        for cell_document in params.cell_text_documents:
+            self.put_text_document(cell_document, notebook_uri=notebook.uri)
+
+    def put_text_document(
+        self,
+        text_document: types.TextDocumentItem,
+        notebook_uri: Optional[str] = None,
+    ):
+        """Add a text document to the workspace.
+
+        Parameters
+        ----------
+        text_document
+           The text document to add
+
+        notebook_uri
+           If set, indicates that this text document represents a cell in a notebook
+           document
+        """
+        doc_uri = text_document.uri
+
+        self._text_documents[doc_uri] = self._create_text_document(
+            doc_uri,
+            source=text_document.text,
+            version=text_document.version,
+            language_id=text_document.language_id,
+        )
+
+        if notebook_uri:
+            self._cell_in_notebook[doc_uri] = notebook_uri
+
+    def remove_notebook_document(self, params: types.DidCloseNotebookDocumentParams):
+        notebook_uri = params.notebook_document.uri
+        self._notebook_documents.pop(notebook_uri, None)
+
+        for cell_document in params.cell_text_documents:
+            self.remove_text_document(cell_document.uri)
+
+    def remove_text_document(self, doc_uri: str):
+        self._text_documents.pop(doc_uri, None)
+        self._cell_in_notebook.pop(doc_uri, None)
+
+    def remove_folder(self, folder_uri: str):
+        self._folders.pop(folder_uri, None)
+        try:
+            del self._folders[folder_uri]
+        except KeyError:
+            pass
+
+    @property
+    def root_path(self):
+        return self._root_path
+
+    @property
+    def root_uri(self):
+        return self._root_uri
+
+    def update_notebook_document(self, params: types.DidChangeNotebookDocumentParams):
+        uri = params.notebook_document.uri
+        notebook = self._notebook_documents[uri]
+        notebook.version = params.notebook_document.version
+
+        if params.change.metadata:
+            notebook.metadata = params.change.metadata
+
+        cell_changes = params.change.cells
+        if cell_changes is None:
+            return
+
+        # Process changes to any cell metadata.
+        nb_cells = {cell.document: cell for cell in notebook.cells}
+        for new_data in cell_changes.data or []:
+            nb_cell = nb_cells.get(new_data.document)
+            if nb_cell is None:
+                logger.warning(
+                    "Ignoring metadata for '%s': not in notebook.", new_data.document
+                )
+                continue
+
+            nb_cell.kind = new_data.kind
+            nb_cell.metadata = new_data.metadata
+            nb_cell.execution_summary = new_data.execution_summary
+
+        # Process changes to the notebook's structure
+        structure = cell_changes.structure
+        if structure:
+            cells = notebook.cells
+            new_cells = structure.array.cells or []
+
+            # Re-order the cells
+            before = cells[: structure.array.start]
+            after = cells[(structure.array.start + structure.array.delete_count) :]
+            notebook.cells = [*before, *new_cells, *after]
+
+            for new_cell in structure.did_open or []:
+                self.put_text_document(new_cell, notebook_uri=uri)
+
+            for removed_cell in structure.did_close or []:
+                self.remove_text_document(removed_cell.uri)
+
+        # Process changes to the text content of existing cells.
+        for text in cell_changes.text_content or []:
+            for change in text.changes:
+                self.update_text_document(text.document, change)
+
+    def update_text_document(
+        self,
+        text_doc: types.VersionedTextDocumentIdentifier,
+        change: types.TextDocumentContentChangeEvent,
+    ):
+        doc_uri = text_doc.uri
+        self._text_documents[doc_uri].apply_change(change)
+        self._text_documents[doc_uri].version = text_doc.version
+
+    def get_document(self, *args, **kwargs):
+        warnings.warn(
+            "'workspace.get_document' has been deprecated, use "
+            "'workspace.get_text_document' instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.get_text_document(*args, **kwargs)
+
+    def remove_document(self, *args, **kwargs):
+        warnings.warn(
+            "'workspace.remove_document' has been deprecated, use "
+            "'workspace.remove_text_document' instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.remove_text_document(*args, **kwargs)
+
+    def put_document(self, *args, **kwargs):
+        warnings.warn(
+            "'workspace.put_document' has been deprecated, use "
+            "'workspace.put_text_document' instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.put_text_document(*args, **kwargs)
+
+    def update_document(self, *args, **kwargs):
+        warnings.warn(
+            "'workspace.update_document' has been deprecated, use "
+            "'workspace.update_text_document' instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self.update_text_document(*args, **kwargs)
diff --git a/server/libs/typing_extensions-4.14.1.dist-info/INSTALLER b/server/libs/typing_extensions-4.14.1.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/server/libs/typing_extensions-4.14.1.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/server/libs/typing_extensions-4.14.1.dist-info/METADATA b/server/libs/typing_extensions-4.14.1.dist-info/METADATA
new file mode 100644
index 0000000..b1fe93f
--- /dev/null
+++ b/server/libs/typing_extensions-4.14.1.dist-info/METADATA
@@ -0,0 +1,68 @@
+Metadata-Version: 2.4
+Name: typing_extensions
+Version: 4.14.1
+Summary: Backported and Experimental Type Hints for Python 3.9+
+Keywords: annotations,backport,checker,checking,function,hinting,hints,type,typechecking,typehinting,typehints,typing
+Author-email: "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" 
+Requires-Python: >=3.9
+Description-Content-Type: text/markdown
+License-Expression: PSF-2.0
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Topic :: Software Development
+License-File: LICENSE
+Project-URL: Bug Tracker, https://github.com/python/typing_extensions/issues
+Project-URL: Changes, https://github.com/python/typing_extensions/blob/main/CHANGELOG.md
+Project-URL: Documentation, https://typing-extensions.readthedocs.io/
+Project-URL: Home, https://github.com/python/typing_extensions
+Project-URL: Q & A, https://github.com/python/typing/discussions
+Project-URL: Repository, https://github.com/python/typing_extensions
+
+# Typing Extensions
+
+[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing)
+
+[Documentation](https://typing-extensions.readthedocs.io/en/latest/#) –
+[PyPI](https://pypi.org/project/typing-extensions/)
+
+## Overview
+
+The `typing_extensions` module serves two related purposes:
+
+- Enable use of new type system features on older Python versions. For example,
+  `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows
+  users on previous Python versions to use it too.
+- Enable experimentation with new type system PEPs before they are accepted and
+  added to the `typing` module.
+
+`typing_extensions` is treated specially by static type checkers such as
+mypy and pyright. Objects defined in `typing_extensions` are treated the same
+way as equivalent forms in `typing`.
+
+`typing_extensions` uses
+[Semantic Versioning](https://semver.org/). The
+major version will be incremented only for backwards-incompatible changes.
+Therefore, it's safe to depend
+on `typing_extensions` like this: `typing_extensions >=x.y, <(x+1)`,
+where `x.y` is the first version that includes all features you need.
+
+## Included items
+
+See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a
+complete listing of module contents.
+
+## Contributing
+
+See [CONTRIBUTING.md](https://github.com/python/typing_extensions/blob/main/CONTRIBUTING.md)
+for how to contribute to `typing_extensions`.
+
diff --git a/server/libs/typing_extensions-4.14.1.dist-info/RECORD b/server/libs/typing_extensions-4.14.1.dist-info/RECORD
new file mode 100644
index 0000000..a371f3a
--- /dev/null
+++ b/server/libs/typing_extensions-4.14.1.dist-info/RECORD
@@ -0,0 +1,8 @@
+__pycache__/typing_extensions.cpython-311.pyc,,
+typing_extensions-4.14.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+typing_extensions-4.14.1.dist-info/METADATA,sha256=8LS3enF0w3KyL4WYimlFfskcnkARg-sv_L6tHbPMS5s,2995
+typing_extensions-4.14.1.dist-info/RECORD,,
+typing_extensions-4.14.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+typing_extensions-4.14.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
+typing_extensions-4.14.1.dist-info/licenses/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
+typing_extensions.py,sha256=Fh0lt5ZCgnzs7tyAhHOAfL0Zr829KYUxiR543ClwVgw,157408
diff --git a/server/libs/typing_extensions-4.14.1.dist-info/REQUESTED b/server/libs/typing_extensions-4.14.1.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/server/libs/typing_extensions-4.14.1.dist-info/WHEEL b/server/libs/typing_extensions-4.14.1.dist-info/WHEEL
new file mode 100644
index 0000000..d8b9936
--- /dev/null
+++ b/server/libs/typing_extensions-4.14.1.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.12.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/server/libs/typing_extensions-4.14.1.dist-info/licenses/LICENSE b/server/libs/typing_extensions-4.14.1.dist-info/licenses/LICENSE
new file mode 100644
index 0000000..f26bcf4
--- /dev/null
+++ b/server/libs/typing_extensions-4.14.1.dist-info/licenses/LICENSE
@@ -0,0 +1,279 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC.  Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team.  In October of the same
+year, the PythonLabs team moved to Digital Creations, which became
+Zope Corporation.  In 2001, the Python Software Foundation (PSF, see
+https://www.python.org/psf/) was formed, a non-profit organization
+created specifically to own Python-related Intellectual Property.
+Zope Corporation was a sponsoring member of the PSF.
+
+All Python releases are Open Source (see https://opensource.org for
+the Open Source Definition).  Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+    Release         Derived     Year        Owner       GPL-
+                    from                                compatible? (1)
+
+    0.9.0 thru 1.2              1991-1995   CWI         yes
+    1.3 thru 1.5.2  1.2         1995-1999   CNRI        yes
+    1.6             1.5.2       2000        CNRI        no
+    2.0             1.6         2000        BeOpen.com  no
+    1.6.1           1.6         2001        CNRI        yes (2)
+    2.1             2.0+1.6.1   2001        PSF         no
+    2.0.1           2.0+1.6.1   2001        PSF         yes
+    2.1.1           2.1+2.0.1   2001        PSF         yes
+    2.1.2           2.1.1       2002        PSF         yes
+    2.1.3           2.1.2       2002        PSF         yes
+    2.2 and above   2.1.1       2001-now    PSF         yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+    the GPL.  All Python licenses, unlike the GPL, let you distribute
+    a modified version without making your changes open source.  The
+    GPL-compatible licenses make it possible to combine Python with
+    other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+    because its license has a choice of law clause.  According to
+    CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+    is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+Python software and documentation are licensed under the
+Python Software Foundation License Version 2.
+
+Starting with Python 3.8.6, examples, recipes, and other code in
+the documentation are dual licensed under the PSF License Version 2
+and the Zero-Clause BSD license.
+
+Some software incorporated into Python is under different licenses.
+The licenses are listed with code falling under that license.
+
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee.  This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis.  BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions.  Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee.  This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party.  As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee.  Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement.  This Agreement together with
+Python 1.6.1 may be located on the internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013.  This
+Agreement may also be obtained from a proxy server on the internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement.  Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee.  This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+        ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands.  All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
+----------------------------------------------------------------------
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/server/libs/typing_extensions.py b/server/libs/typing_extensions.py
new file mode 100644
index 0000000..efa09d5
--- /dev/null
+++ b/server/libs/typing_extensions.py
@@ -0,0 +1,4244 @@
+import abc
+import builtins
+import collections
+import collections.abc
+import contextlib
+import enum
+import functools
+import inspect
+import io
+import keyword
+import operator
+import sys
+import types as _types
+import typing
+import warnings
+
+if sys.version_info >= (3, 14):
+    import annotationlib
+
+__all__ = [
+    # Super-special typing primitives.
+    'Any',
+    'ClassVar',
+    'Concatenate',
+    'Final',
+    'LiteralString',
+    'ParamSpec',
+    'ParamSpecArgs',
+    'ParamSpecKwargs',
+    'Self',
+    'Type',
+    'TypeVar',
+    'TypeVarTuple',
+    'Unpack',
+
+    # ABCs (from collections.abc).
+    'Awaitable',
+    'AsyncIterator',
+    'AsyncIterable',
+    'Coroutine',
+    'AsyncGenerator',
+    'AsyncContextManager',
+    'Buffer',
+    'ChainMap',
+
+    # Concrete collection types.
+    'ContextManager',
+    'Counter',
+    'Deque',
+    'DefaultDict',
+    'NamedTuple',
+    'OrderedDict',
+    'TypedDict',
+
+    # Structural checks, a.k.a. protocols.
+    'SupportsAbs',
+    'SupportsBytes',
+    'SupportsComplex',
+    'SupportsFloat',
+    'SupportsIndex',
+    'SupportsInt',
+    'SupportsRound',
+    'Reader',
+    'Writer',
+
+    # One-off things.
+    'Annotated',
+    'assert_never',
+    'assert_type',
+    'clear_overloads',
+    'dataclass_transform',
+    'deprecated',
+    'Doc',
+    'evaluate_forward_ref',
+    'get_overloads',
+    'final',
+    'Format',
+    'get_annotations',
+    'get_args',
+    'get_origin',
+    'get_original_bases',
+    'get_protocol_members',
+    'get_type_hints',
+    'IntVar',
+    'is_protocol',
+    'is_typeddict',
+    'Literal',
+    'NewType',
+    'overload',
+    'override',
+    'Protocol',
+    'Sentinel',
+    'reveal_type',
+    'runtime',
+    'runtime_checkable',
+    'Text',
+    'TypeAlias',
+    'TypeAliasType',
+    'TypeForm',
+    'TypeGuard',
+    'TypeIs',
+    'TYPE_CHECKING',
+    'Never',
+    'NoReturn',
+    'ReadOnly',
+    'Required',
+    'NotRequired',
+    'NoDefault',
+    'NoExtraItems',
+
+    # Pure aliases, have always been in typing
+    'AbstractSet',
+    'AnyStr',
+    'BinaryIO',
+    'Callable',
+    'Collection',
+    'Container',
+    'Dict',
+    'ForwardRef',
+    'FrozenSet',
+    'Generator',
+    'Generic',
+    'Hashable',
+    'IO',
+    'ItemsView',
+    'Iterable',
+    'Iterator',
+    'KeysView',
+    'List',
+    'Mapping',
+    'MappingView',
+    'Match',
+    'MutableMapping',
+    'MutableSequence',
+    'MutableSet',
+    'Optional',
+    'Pattern',
+    'Reversible',
+    'Sequence',
+    'Set',
+    'Sized',
+    'TextIO',
+    'Tuple',
+    'Union',
+    'ValuesView',
+    'cast',
+    'no_type_check',
+    'no_type_check_decorator',
+]
+
+# for backward compatibility
+PEP_560 = True
+GenericMeta = type
+_PEP_696_IMPLEMENTED = sys.version_info >= (3, 13, 0, "beta")
+
+# Added with bpo-45166 to 3.10.1+ and some 3.9 versions
+_FORWARD_REF_HAS_CLASS = "__forward_is_class__" in typing.ForwardRef.__slots__
+
+# The functions below are modified copies of typing internal helpers.
+# They are needed by _ProtocolMeta and they provide support for PEP 646.
+
+
+class _Sentinel:
+    def __repr__(self):
+        return ""
+
+
+_marker = _Sentinel()
+
+
+if sys.version_info >= (3, 10):
+    def _should_collect_from_parameters(t):
+        return isinstance(
+            t, (typing._GenericAlias, _types.GenericAlias, _types.UnionType)
+        )
+else:
+    def _should_collect_from_parameters(t):
+        return isinstance(t, (typing._GenericAlias, _types.GenericAlias))
+
+
+NoReturn = typing.NoReturn
+
+# Some unconstrained type variables.  These are used by the container types.
+# (These are not for export.)
+T = typing.TypeVar('T')  # Any type.
+KT = typing.TypeVar('KT')  # Key type.
+VT = typing.TypeVar('VT')  # Value type.
+T_co = typing.TypeVar('T_co', covariant=True)  # Any type covariant containers.
+T_contra = typing.TypeVar('T_contra', contravariant=True)  # Ditto contravariant.
+
+
+if sys.version_info >= (3, 11):
+    from typing import Any
+else:
+
+    class _AnyMeta(type):
+        def __instancecheck__(self, obj):
+            if self is Any:
+                raise TypeError("typing_extensions.Any cannot be used with isinstance()")
+            return super().__instancecheck__(obj)
+
+        def __repr__(self):
+            if self is Any:
+                return "typing_extensions.Any"
+            return super().__repr__()
+
+    class Any(metaclass=_AnyMeta):
+        """Special type indicating an unconstrained type.
+        - Any is compatible with every type.
+        - Any assumed to have all methods.
+        - All values assumed to be instances of Any.
+        Note that all the above statements are true from the point of view of
+        static type checkers. At runtime, Any should not be used with instance
+        checks.
+        """
+        def __new__(cls, *args, **kwargs):
+            if cls is Any:
+                raise TypeError("Any cannot be instantiated")
+            return super().__new__(cls, *args, **kwargs)
+
+
+ClassVar = typing.ClassVar
+
+# Vendored from cpython typing._SpecialFrom
+# Having a separate class means that instances will not be rejected by
+# typing._type_check.
+class _SpecialForm(typing._Final, _root=True):
+    __slots__ = ('_name', '__doc__', '_getitem')
+
+    def __init__(self, getitem):
+        self._getitem = getitem
+        self._name = getitem.__name__
+        self.__doc__ = getitem.__doc__
+
+    def __getattr__(self, item):
+        if item in {'__name__', '__qualname__'}:
+            return self._name
+
+        raise AttributeError(item)
+
+    def __mro_entries__(self, bases):
+        raise TypeError(f"Cannot subclass {self!r}")
+
+    def __repr__(self):
+        return f'typing_extensions.{self._name}'
+
+    def __reduce__(self):
+        return self._name
+
+    def __call__(self, *args, **kwds):
+        raise TypeError(f"Cannot instantiate {self!r}")
+
+    def __or__(self, other):
+        return typing.Union[self, other]
+
+    def __ror__(self, other):
+        return typing.Union[other, self]
+
+    def __instancecheck__(self, obj):
+        raise TypeError(f"{self} cannot be used with isinstance()")
+
+    def __subclasscheck__(self, cls):
+        raise TypeError(f"{self} cannot be used with issubclass()")
+
+    @typing._tp_cache
+    def __getitem__(self, parameters):
+        return self._getitem(self, parameters)
+
+
+# Note that inheriting from this class means that the object will be
+# rejected by typing._type_check, so do not use it if the special form
+# is arguably valid as a type by itself.
+class _ExtensionsSpecialForm(typing._SpecialForm, _root=True):
+    def __repr__(self):
+        return 'typing_extensions.' + self._name
+
+
+Final = typing.Final
+
+if sys.version_info >= (3, 11):
+    final = typing.final
+else:
+    # @final exists in 3.8+, but we backport it for all versions
+    # before 3.11 to keep support for the __final__ attribute.
+    # See https://bugs.python.org/issue46342
+    def final(f):
+        """This decorator can be used to indicate to type checkers that
+        the decorated method cannot be overridden, and decorated class
+        cannot be subclassed. For example:
+
+            class Base:
+                @final
+                def done(self) -> None:
+                    ...
+            class Sub(Base):
+                def done(self) -> None:  # Error reported by type checker
+                    ...
+            @final
+            class Leaf:
+                ...
+            class Other(Leaf):  # Error reported by type checker
+                ...
+
+        There is no runtime checking of these properties. The decorator
+        sets the ``__final__`` attribute to ``True`` on the decorated object
+        to allow runtime introspection.
+        """
+        try:
+            f.__final__ = True
+        except (AttributeError, TypeError):
+            # Skip the attribute silently if it is not writable.
+            # AttributeError happens if the object has __slots__ or a
+            # read-only property, TypeError if it's a builtin class.
+            pass
+        return f
+
+
+def IntVar(name):
+    return typing.TypeVar(name)
+
+
+# A Literal bug was fixed in 3.11.0, 3.10.1 and 3.9.8
+if sys.version_info >= (3, 10, 1):
+    Literal = typing.Literal
+else:
+    def _flatten_literal_params(parameters):
+        """An internal helper for Literal creation: flatten Literals among parameters"""
+        params = []
+        for p in parameters:
+            if isinstance(p, _LiteralGenericAlias):
+                params.extend(p.__args__)
+            else:
+                params.append(p)
+        return tuple(params)
+
+    def _value_and_type_iter(params):
+        for p in params:
+            yield p, type(p)
+
+    class _LiteralGenericAlias(typing._GenericAlias, _root=True):
+        def __eq__(self, other):
+            if not isinstance(other, _LiteralGenericAlias):
+                return NotImplemented
+            these_args_deduped = set(_value_and_type_iter(self.__args__))
+            other_args_deduped = set(_value_and_type_iter(other.__args__))
+            return these_args_deduped == other_args_deduped
+
+        def __hash__(self):
+            return hash(frozenset(_value_and_type_iter(self.__args__)))
+
+    class _LiteralForm(_ExtensionsSpecialForm, _root=True):
+        def __init__(self, doc: str):
+            self._name = 'Literal'
+            self._doc = self.__doc__ = doc
+
+        def __getitem__(self, parameters):
+            if not isinstance(parameters, tuple):
+                parameters = (parameters,)
+
+            parameters = _flatten_literal_params(parameters)
+
+            val_type_pairs = list(_value_and_type_iter(parameters))
+            try:
+                deduped_pairs = set(val_type_pairs)
+            except TypeError:
+                # unhashable parameters
+                pass
+            else:
+                # similar logic to typing._deduplicate on Python 3.9+
+                if len(deduped_pairs) < len(val_type_pairs):
+                    new_parameters = []
+                    for pair in val_type_pairs:
+                        if pair in deduped_pairs:
+                            new_parameters.append(pair[0])
+                            deduped_pairs.remove(pair)
+                    assert not deduped_pairs, deduped_pairs
+                    parameters = tuple(new_parameters)
+
+            return _LiteralGenericAlias(self, parameters)
+
+    Literal = _LiteralForm(doc="""\
+                           A type that can be used to indicate to type checkers
+                           that the corresponding value has a value literally equivalent
+                           to the provided parameter. For example:
+
+                               var: Literal[4] = 4
+
+                           The type checker understands that 'var' is literally equal to
+                           the value 4 and no other value.
+
+                           Literal[...] cannot be subclassed. There is no runtime
+                           checking verifying that the parameter is actually a value
+                           instead of a type.""")
+
+
+_overload_dummy = typing._overload_dummy
+
+
+if hasattr(typing, "get_overloads"):  # 3.11+
+    overload = typing.overload
+    get_overloads = typing.get_overloads
+    clear_overloads = typing.clear_overloads
+else:
+    # {module: {qualname: {firstlineno: func}}}
+    _overload_registry = collections.defaultdict(
+        functools.partial(collections.defaultdict, dict)
+    )
+
+    def overload(func):
+        """Decorator for overloaded functions/methods.
+
+        In a stub file, place two or more stub definitions for the same
+        function in a row, each decorated with @overload.  For example:
+
+        @overload
+        def utf8(value: None) -> None: ...
+        @overload
+        def utf8(value: bytes) -> bytes: ...
+        @overload
+        def utf8(value: str) -> bytes: ...
+
+        In a non-stub file (i.e. a regular .py file), do the same but
+        follow it with an implementation.  The implementation should *not*
+        be decorated with @overload.  For example:
+
+        @overload
+        def utf8(value: None) -> None: ...
+        @overload
+        def utf8(value: bytes) -> bytes: ...
+        @overload
+        def utf8(value: str) -> bytes: ...
+        def utf8(value):
+            # implementation goes here
+
+        The overloads for a function can be retrieved at runtime using the
+        get_overloads() function.
+        """
+        # classmethod and staticmethod
+        f = getattr(func, "__func__", func)
+        try:
+            _overload_registry[f.__module__][f.__qualname__][
+                f.__code__.co_firstlineno
+            ] = func
+        except AttributeError:
+            # Not a normal function; ignore.
+            pass
+        return _overload_dummy
+
+    def get_overloads(func):
+        """Return all defined overloads for *func* as a sequence."""
+        # classmethod and staticmethod
+        f = getattr(func, "__func__", func)
+        if f.__module__ not in _overload_registry:
+            return []
+        mod_dict = _overload_registry[f.__module__]
+        if f.__qualname__ not in mod_dict:
+            return []
+        return list(mod_dict[f.__qualname__].values())
+
+    def clear_overloads():
+        """Clear all overloads in the registry."""
+        _overload_registry.clear()
+
+
+# This is not a real generic class.  Don't use outside annotations.
+Type = typing.Type
+
+# Various ABCs mimicking those in collections.abc.
+# A few are simply re-exported for completeness.
+Awaitable = typing.Awaitable
+Coroutine = typing.Coroutine
+AsyncIterable = typing.AsyncIterable
+AsyncIterator = typing.AsyncIterator
+Deque = typing.Deque
+DefaultDict = typing.DefaultDict
+OrderedDict = typing.OrderedDict
+Counter = typing.Counter
+ChainMap = typing.ChainMap
+Text = typing.Text
+TYPE_CHECKING = typing.TYPE_CHECKING
+
+
+if sys.version_info >= (3, 13, 0, "beta"):
+    from typing import AsyncContextManager, AsyncGenerator, ContextManager, Generator
+else:
+    def _is_dunder(attr):
+        return attr.startswith('__') and attr.endswith('__')
+
+
+    class _SpecialGenericAlias(typing._SpecialGenericAlias, _root=True):
+        def __init__(self, origin, nparams, *, inst=True, name=None, defaults=()):
+            super().__init__(origin, nparams, inst=inst, name=name)
+            self._defaults = defaults
+
+        def __setattr__(self, attr, val):
+            allowed_attrs = {'_name', '_inst', '_nparams', '_defaults'}
+            if _is_dunder(attr) or attr in allowed_attrs:
+                object.__setattr__(self, attr, val)
+            else:
+                setattr(self.__origin__, attr, val)
+
+        @typing._tp_cache
+        def __getitem__(self, params):
+            if not isinstance(params, tuple):
+                params = (params,)
+            msg = "Parameters to generic types must be types."
+            params = tuple(typing._type_check(p, msg) for p in params)
+            if (
+                self._defaults
+                and len(params) < self._nparams
+                and len(params) + len(self._defaults) >= self._nparams
+            ):
+                params = (*params, *self._defaults[len(params) - self._nparams:])
+            actual_len = len(params)
+
+            if actual_len != self._nparams:
+                if self._defaults:
+                    expected = f"at least {self._nparams - len(self._defaults)}"
+                else:
+                    expected = str(self._nparams)
+                if not self._nparams:
+                    raise TypeError(f"{self} is not a generic class")
+                raise TypeError(
+                    f"Too {'many' if actual_len > self._nparams else 'few'}"
+                    f" arguments for {self};"
+                    f" actual {actual_len}, expected {expected}"
+                )
+            return self.copy_with(params)
+
+    _NoneType = type(None)
+    Generator = _SpecialGenericAlias(
+        collections.abc.Generator, 3, defaults=(_NoneType, _NoneType)
+    )
+    AsyncGenerator = _SpecialGenericAlias(
+        collections.abc.AsyncGenerator, 2, defaults=(_NoneType,)
+    )
+    ContextManager = _SpecialGenericAlias(
+        contextlib.AbstractContextManager,
+        2,
+        name="ContextManager",
+        defaults=(typing.Optional[bool],)
+    )
+    AsyncContextManager = _SpecialGenericAlias(
+        contextlib.AbstractAsyncContextManager,
+        2,
+        name="AsyncContextManager",
+        defaults=(typing.Optional[bool],)
+    )
+
+
+_PROTO_ALLOWLIST = {
+    'collections.abc': [
+        'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable',
+        'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer',
+    ],
+    'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'],
+    'typing_extensions': ['Buffer'],
+}
+
+
+_EXCLUDED_ATTRS = frozenset(typing.EXCLUDED_ATTRIBUTES) | {
+    "__match_args__", "__protocol_attrs__", "__non_callable_proto_members__",
+    "__final__",
+}
+
+
+def _get_protocol_attrs(cls):
+    attrs = set()
+    for base in cls.__mro__[:-1]:  # without object
+        if base.__name__ in {'Protocol', 'Generic'}:
+            continue
+        annotations = getattr(base, '__annotations__', {})
+        for attr in (*base.__dict__, *annotations):
+            if (not attr.startswith('_abc_') and attr not in _EXCLUDED_ATTRS):
+                attrs.add(attr)
+    return attrs
+
+
+def _caller(depth=1, default='__main__'):
+    try:
+        return sys._getframemodulename(depth + 1) or default
+    except AttributeError:  # For platforms without _getframemodulename()
+        pass
+    try:
+        return sys._getframe(depth + 1).f_globals.get('__name__', default)
+    except (AttributeError, ValueError):  # For platforms without _getframe()
+        pass
+    return None
+
+
+# `__match_args__` attribute was removed from protocol members in 3.13,
+# we want to backport this change to older Python versions.
+if sys.version_info >= (3, 13):
+    Protocol = typing.Protocol
+else:
+    def _allow_reckless_class_checks(depth=2):
+        """Allow instance and class checks for special stdlib modules.
+        The abc and functools modules indiscriminately call isinstance() and
+        issubclass() on the whole MRO of a user class, which may contain protocols.
+        """
+        return _caller(depth) in {'abc', 'functools', None}
+
+    def _no_init(self, *args, **kwargs):
+        if type(self)._is_protocol:
+            raise TypeError('Protocols cannot be instantiated')
+
+    def _type_check_issubclass_arg_1(arg):
+        """Raise TypeError if `arg` is not an instance of `type`
+        in `issubclass(arg, )`.
+
+        In most cases, this is verified by type.__subclasscheck__.
+        Checking it again unnecessarily would slow down issubclass() checks,
+        so, we don't perform this check unless we absolutely have to.
+
+        For various error paths, however,
+        we want to ensure that *this* error message is shown to the user
+        where relevant, rather than a typing.py-specific error message.
+        """
+        if not isinstance(arg, type):
+            # Same error message as for issubclass(1, int).
+            raise TypeError('issubclass() arg 1 must be a class')
+
+    # Inheriting from typing._ProtocolMeta isn't actually desirable,
+    # but is necessary to allow typing.Protocol and typing_extensions.Protocol
+    # to mix without getting TypeErrors about "metaclass conflict"
+    class _ProtocolMeta(type(typing.Protocol)):
+        # This metaclass is somewhat unfortunate,
+        # but is necessary for several reasons...
+        #
+        # NOTE: DO NOT call super() in any methods in this class
+        # That would call the methods on typing._ProtocolMeta on Python <=3.11
+        # and those are slow
+        def __new__(mcls, name, bases, namespace, **kwargs):
+            if name == "Protocol" and len(bases) < 2:
+                pass
+            elif {Protocol, typing.Protocol} & set(bases):
+                for base in bases:
+                    if not (
+                        base in {object, typing.Generic, Protocol, typing.Protocol}
+                        or base.__name__ in _PROTO_ALLOWLIST.get(base.__module__, [])
+                        or is_protocol(base)
+                    ):
+                        raise TypeError(
+                            f"Protocols can only inherit from other protocols, "
+                            f"got {base!r}"
+                        )
+            return abc.ABCMeta.__new__(mcls, name, bases, namespace, **kwargs)
+
+        def __init__(cls, *args, **kwargs):
+            abc.ABCMeta.__init__(cls, *args, **kwargs)
+            if getattr(cls, "_is_protocol", False):
+                cls.__protocol_attrs__ = _get_protocol_attrs(cls)
+
+        def __subclasscheck__(cls, other):
+            if cls is Protocol:
+                return type.__subclasscheck__(cls, other)
+            if (
+                getattr(cls, '_is_protocol', False)
+                and not _allow_reckless_class_checks()
+            ):
+                if not getattr(cls, '_is_runtime_protocol', False):
+                    _type_check_issubclass_arg_1(other)
+                    raise TypeError(
+                        "Instance and class checks can only be used with "
+                        "@runtime_checkable protocols"
+                    )
+                if (
+                    # this attribute is set by @runtime_checkable:
+                    cls.__non_callable_proto_members__
+                    and cls.__dict__.get("__subclasshook__") is _proto_hook
+                ):
+                    _type_check_issubclass_arg_1(other)
+                    non_method_attrs = sorted(cls.__non_callable_proto_members__)
+                    raise TypeError(
+                        "Protocols with non-method members don't support issubclass()."
+                        f" Non-method members: {str(non_method_attrs)[1:-1]}."
+                    )
+            return abc.ABCMeta.__subclasscheck__(cls, other)
+
+        def __instancecheck__(cls, instance):
+            # We need this method for situations where attributes are
+            # assigned in __init__.
+            if cls is Protocol:
+                return type.__instancecheck__(cls, instance)
+            if not getattr(cls, "_is_protocol", False):
+                # i.e., it's a concrete subclass of a protocol
+                return abc.ABCMeta.__instancecheck__(cls, instance)
+
+            if (
+                not getattr(cls, '_is_runtime_protocol', False) and
+                not _allow_reckless_class_checks()
+            ):
+                raise TypeError("Instance and class checks can only be used with"
+                                " @runtime_checkable protocols")
+
+            if abc.ABCMeta.__instancecheck__(cls, instance):
+                return True
+
+            for attr in cls.__protocol_attrs__:
+                try:
+                    val = inspect.getattr_static(instance, attr)
+                except AttributeError:
+                    break
+                # this attribute is set by @runtime_checkable:
+                if val is None and attr not in cls.__non_callable_proto_members__:
+                    break
+            else:
+                return True
+
+            return False
+
+        def __eq__(cls, other):
+            # Hack so that typing.Generic.__class_getitem__
+            # treats typing_extensions.Protocol
+            # as equivalent to typing.Protocol
+            if abc.ABCMeta.__eq__(cls, other) is True:
+                return True
+            return cls is Protocol and other is typing.Protocol
+
+        # This has to be defined, or the abc-module cache
+        # complains about classes with this metaclass being unhashable,
+        # if we define only __eq__!
+        def __hash__(cls) -> int:
+            return type.__hash__(cls)
+
+    @classmethod
+    def _proto_hook(cls, other):
+        if not cls.__dict__.get('_is_protocol', False):
+            return NotImplemented
+
+        for attr in cls.__protocol_attrs__:
+            for base in other.__mro__:
+                # Check if the members appears in the class dictionary...
+                if attr in base.__dict__:
+                    if base.__dict__[attr] is None:
+                        return NotImplemented
+                    break
+
+                # ...or in annotations, if it is a sub-protocol.
+                annotations = getattr(base, '__annotations__', {})
+                if (
+                    isinstance(annotations, collections.abc.Mapping)
+                    and attr in annotations
+                    and is_protocol(other)
+                ):
+                    break
+            else:
+                return NotImplemented
+        return True
+
+    class Protocol(typing.Generic, metaclass=_ProtocolMeta):
+        __doc__ = typing.Protocol.__doc__
+        __slots__ = ()
+        _is_protocol = True
+        _is_runtime_protocol = False
+
+        def __init_subclass__(cls, *args, **kwargs):
+            super().__init_subclass__(*args, **kwargs)
+
+            # Determine if this is a protocol or a concrete subclass.
+            if not cls.__dict__.get('_is_protocol', False):
+                cls._is_protocol = any(b is Protocol for b in cls.__bases__)
+
+            # Set (or override) the protocol subclass hook.
+            if '__subclasshook__' not in cls.__dict__:
+                cls.__subclasshook__ = _proto_hook
+
+            # Prohibit instantiation for protocol classes
+            if cls._is_protocol and cls.__init__ is Protocol.__init__:
+                cls.__init__ = _no_init
+
+
+if sys.version_info >= (3, 13):
+    runtime_checkable = typing.runtime_checkable
+else:
+    def runtime_checkable(cls):
+        """Mark a protocol class as a runtime protocol.
+
+        Such protocol can be used with isinstance() and issubclass().
+        Raise TypeError if applied to a non-protocol class.
+        This allows a simple-minded structural check very similar to
+        one trick ponies in collections.abc such as Iterable.
+
+        For example::
+
+            @runtime_checkable
+            class Closable(Protocol):
+                def close(self): ...
+
+            assert isinstance(open('/some/file'), Closable)
+
+        Warning: this will check only the presence of the required methods,
+        not their type signatures!
+        """
+        if not issubclass(cls, typing.Generic) or not getattr(cls, '_is_protocol', False):
+            raise TypeError(f'@runtime_checkable can be only applied to protocol classes,'
+                            f' got {cls!r}')
+        cls._is_runtime_protocol = True
+
+        # typing.Protocol classes on <=3.11 break if we execute this block,
+        # because typing.Protocol classes on <=3.11 don't have a
+        # `__protocol_attrs__` attribute, and this block relies on the
+        # `__protocol_attrs__` attribute. Meanwhile, typing.Protocol classes on 3.12.2+
+        # break if we *don't* execute this block, because *they* assume that all
+        # protocol classes have a `__non_callable_proto_members__` attribute
+        # (which this block sets)
+        if isinstance(cls, _ProtocolMeta) or sys.version_info >= (3, 12, 2):
+            # PEP 544 prohibits using issubclass()
+            # with protocols that have non-method members.
+            # See gh-113320 for why we compute this attribute here,
+            # rather than in `_ProtocolMeta.__init__`
+            cls.__non_callable_proto_members__ = set()
+            for attr in cls.__protocol_attrs__:
+                try:
+                    is_callable = callable(getattr(cls, attr, None))
+                except Exception as e:
+                    raise TypeError(
+                        f"Failed to determine whether protocol member {attr!r} "
+                        "is a method member"
+                    ) from e
+                else:
+                    if not is_callable:
+                        cls.__non_callable_proto_members__.add(attr)
+
+        return cls
+
+
+# The "runtime" alias exists for backwards compatibility.
+runtime = runtime_checkable
+
+
+# Our version of runtime-checkable protocols is faster on Python <=3.11
+if sys.version_info >= (3, 12):
+    SupportsInt = typing.SupportsInt
+    SupportsFloat = typing.SupportsFloat
+    SupportsComplex = typing.SupportsComplex
+    SupportsBytes = typing.SupportsBytes
+    SupportsIndex = typing.SupportsIndex
+    SupportsAbs = typing.SupportsAbs
+    SupportsRound = typing.SupportsRound
+else:
+    @runtime_checkable
+    class SupportsInt(Protocol):
+        """An ABC with one abstract method __int__."""
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def __int__(self) -> int:
+            pass
+
+    @runtime_checkable
+    class SupportsFloat(Protocol):
+        """An ABC with one abstract method __float__."""
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def __float__(self) -> float:
+            pass
+
+    @runtime_checkable
+    class SupportsComplex(Protocol):
+        """An ABC with one abstract method __complex__."""
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def __complex__(self) -> complex:
+            pass
+
+    @runtime_checkable
+    class SupportsBytes(Protocol):
+        """An ABC with one abstract method __bytes__."""
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def __bytes__(self) -> bytes:
+            pass
+
+    @runtime_checkable
+    class SupportsIndex(Protocol):
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def __index__(self) -> int:
+            pass
+
+    @runtime_checkable
+    class SupportsAbs(Protocol[T_co]):
+        """
+        An ABC with one abstract method __abs__ that is covariant in its return type.
+        """
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def __abs__(self) -> T_co:
+            pass
+
+    @runtime_checkable
+    class SupportsRound(Protocol[T_co]):
+        """
+        An ABC with one abstract method __round__ that is covariant in its return type.
+        """
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def __round__(self, ndigits: int = 0) -> T_co:
+            pass
+
+
+if hasattr(io, "Reader") and hasattr(io, "Writer"):
+    Reader = io.Reader
+    Writer = io.Writer
+else:
+    @runtime_checkable
+    class Reader(Protocol[T_co]):
+        """Protocol for simple I/O reader instances.
+
+        This protocol only supports blocking I/O.
+        """
+
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def read(self, size: int = ..., /) -> T_co:
+            """Read data from the input stream and return it.
+
+            If *size* is specified, at most *size* items (bytes/characters) will be
+            read.
+            """
+
+    @runtime_checkable
+    class Writer(Protocol[T_contra]):
+        """Protocol for simple I/O writer instances.
+
+        This protocol only supports blocking I/O.
+        """
+
+        __slots__ = ()
+
+        @abc.abstractmethod
+        def write(self, data: T_contra, /) -> int:
+            """Write *data* to the output stream and return the number of items written."""  # noqa: E501
+
+
+_NEEDS_SINGLETONMETA = (
+    not hasattr(typing, "NoDefault") or not hasattr(typing, "NoExtraItems")
+)
+
+if _NEEDS_SINGLETONMETA:
+    class SingletonMeta(type):
+        def __setattr__(cls, attr, value):
+            # TypeError is consistent with the behavior of NoneType
+            raise TypeError(
+                f"cannot set {attr!r} attribute of immutable type {cls.__name__!r}"
+            )
+
+
+if hasattr(typing, "NoDefault"):
+    NoDefault = typing.NoDefault
+else:
+    class NoDefaultType(metaclass=SingletonMeta):
+        """The type of the NoDefault singleton."""
+
+        __slots__ = ()
+
+        def __new__(cls):
+            return globals().get("NoDefault") or object.__new__(cls)
+
+        def __repr__(self):
+            return "typing_extensions.NoDefault"
+
+        def __reduce__(self):
+            return "NoDefault"
+
+    NoDefault = NoDefaultType()
+    del NoDefaultType
+
+if hasattr(typing, "NoExtraItems"):
+    NoExtraItems = typing.NoExtraItems
+else:
+    class NoExtraItemsType(metaclass=SingletonMeta):
+        """The type of the NoExtraItems singleton."""
+
+        __slots__ = ()
+
+        def __new__(cls):
+            return globals().get("NoExtraItems") or object.__new__(cls)
+
+        def __repr__(self):
+            return "typing_extensions.NoExtraItems"
+
+        def __reduce__(self):
+            return "NoExtraItems"
+
+    NoExtraItems = NoExtraItemsType()
+    del NoExtraItemsType
+
+if _NEEDS_SINGLETONMETA:
+    del SingletonMeta
+
+
+# Update this to something like >=3.13.0b1 if and when
+# PEP 728 is implemented in CPython
+_PEP_728_IMPLEMENTED = False
+
+if _PEP_728_IMPLEMENTED:
+    # The standard library TypedDict in Python 3.9.0/1 does not honour the "total"
+    # keyword with old-style TypedDict().  See https://bugs.python.org/issue42059
+    # The standard library TypedDict below Python 3.11 does not store runtime
+    # information about optional and required keys when using Required or NotRequired.
+    # Generic TypedDicts are also impossible using typing.TypedDict on Python <3.11.
+    # Aaaand on 3.12 we add __orig_bases__ to TypedDict
+    # to enable better runtime introspection.
+    # On 3.13 we deprecate some odd ways of creating TypedDicts.
+    # Also on 3.13, PEP 705 adds the ReadOnly[] qualifier.
+    # PEP 728 (still pending) makes more changes.
+    TypedDict = typing.TypedDict
+    _TypedDictMeta = typing._TypedDictMeta
+    is_typeddict = typing.is_typeddict
+else:
+    # 3.10.0 and later
+    _TAKES_MODULE = "module" in inspect.signature(typing._type_check).parameters
+
+    def _get_typeddict_qualifiers(annotation_type):
+        while True:
+            annotation_origin = get_origin(annotation_type)
+            if annotation_origin is Annotated:
+                annotation_args = get_args(annotation_type)
+                if annotation_args:
+                    annotation_type = annotation_args[0]
+                else:
+                    break
+            elif annotation_origin is Required:
+                yield Required
+                annotation_type, = get_args(annotation_type)
+            elif annotation_origin is NotRequired:
+                yield NotRequired
+                annotation_type, = get_args(annotation_type)
+            elif annotation_origin is ReadOnly:
+                yield ReadOnly
+                annotation_type, = get_args(annotation_type)
+            else:
+                break
+
+    class _TypedDictMeta(type):
+
+        def __new__(cls, name, bases, ns, *, total=True, closed=None,
+                    extra_items=NoExtraItems):
+            """Create new typed dict class object.
+
+            This method is called when TypedDict is subclassed,
+            or when TypedDict is instantiated. This way
+            TypedDict supports all three syntax forms described in its docstring.
+            Subclasses and instances of TypedDict return actual dictionaries.
+            """
+            for base in bases:
+                if type(base) is not _TypedDictMeta and base is not typing.Generic:
+                    raise TypeError('cannot inherit from both a TypedDict type '
+                                    'and a non-TypedDict base class')
+            if closed is not None and extra_items is not NoExtraItems:
+                raise TypeError(f"Cannot combine closed={closed!r} and extra_items")
+
+            if any(issubclass(b, typing.Generic) for b in bases):
+                generic_base = (typing.Generic,)
+            else:
+                generic_base = ()
+
+            ns_annotations = ns.pop('__annotations__', None)
+
+            # typing.py generally doesn't let you inherit from plain Generic, unless
+            # the name of the class happens to be "Protocol"
+            tp_dict = type.__new__(_TypedDictMeta, "Protocol", (*generic_base, dict), ns)
+            tp_dict.__name__ = name
+            if tp_dict.__qualname__ == "Protocol":
+                tp_dict.__qualname__ = name
+
+            if not hasattr(tp_dict, '__orig_bases__'):
+                tp_dict.__orig_bases__ = bases
+
+            annotations = {}
+            own_annotate = None
+            if ns_annotations is not None:
+                own_annotations = ns_annotations
+            elif sys.version_info >= (3, 14):
+                if hasattr(annotationlib, "get_annotate_from_class_namespace"):
+                    own_annotate = annotationlib.get_annotate_from_class_namespace(ns)
+                else:
+                    # 3.14.0a7 and earlier
+                    own_annotate = ns.get("__annotate__")
+                if own_annotate is not None:
+                    own_annotations = annotationlib.call_annotate_function(
+                        own_annotate, Format.FORWARDREF, owner=tp_dict
+                    )
+                else:
+                    own_annotations = {}
+            else:
+                own_annotations = {}
+            msg = "TypedDict('Name', {f0: t0, f1: t1, ...}); each t must be a type"
+            if _TAKES_MODULE:
+                own_checked_annotations = {
+                    n: typing._type_check(tp, msg, module=tp_dict.__module__)
+                    for n, tp in own_annotations.items()
+                }
+            else:
+                own_checked_annotations = {
+                    n: typing._type_check(tp, msg)
+                    for n, tp in own_annotations.items()
+                }
+            required_keys = set()
+            optional_keys = set()
+            readonly_keys = set()
+            mutable_keys = set()
+            extra_items_type = extra_items
+
+            for base in bases:
+                base_dict = base.__dict__
+
+                if sys.version_info <= (3, 14):
+                    annotations.update(base_dict.get('__annotations__', {}))
+                required_keys.update(base_dict.get('__required_keys__', ()))
+                optional_keys.update(base_dict.get('__optional_keys__', ()))
+                readonly_keys.update(base_dict.get('__readonly_keys__', ()))
+                mutable_keys.update(base_dict.get('__mutable_keys__', ()))
+
+            # This was specified in an earlier version of PEP 728. Support
+            # is retained for backwards compatibility, but only for Python
+            # 3.13 and lower.
+            if (closed and sys.version_info < (3, 14)
+                       and "__extra_items__" in own_checked_annotations):
+                annotation_type = own_checked_annotations.pop("__extra_items__")
+                qualifiers = set(_get_typeddict_qualifiers(annotation_type))
+                if Required in qualifiers:
+                    raise TypeError(
+                        "Special key __extra_items__ does not support "
+                        "Required"
+                    )
+                if NotRequired in qualifiers:
+                    raise TypeError(
+                        "Special key __extra_items__ does not support "
+                        "NotRequired"
+                    )
+                extra_items_type = annotation_type
+
+            annotations.update(own_checked_annotations)
+            for annotation_key, annotation_type in own_checked_annotations.items():
+                qualifiers = set(_get_typeddict_qualifiers(annotation_type))
+
+                if Required in qualifiers:
+                    required_keys.add(annotation_key)
+                elif NotRequired in qualifiers:
+                    optional_keys.add(annotation_key)
+                elif total:
+                    required_keys.add(annotation_key)
+                else:
+                    optional_keys.add(annotation_key)
+                if ReadOnly in qualifiers:
+                    mutable_keys.discard(annotation_key)
+                    readonly_keys.add(annotation_key)
+                else:
+                    mutable_keys.add(annotation_key)
+                    readonly_keys.discard(annotation_key)
+
+            if sys.version_info >= (3, 14):
+                def __annotate__(format):
+                    annos = {}
+                    for base in bases:
+                        if base is Generic:
+                            continue
+                        base_annotate = base.__annotate__
+                        if base_annotate is None:
+                            continue
+                        base_annos = annotationlib.call_annotate_function(
+                            base_annotate, format, owner=base)
+                        annos.update(base_annos)
+                    if own_annotate is not None:
+                        own = annotationlib.call_annotate_function(
+                            own_annotate, format, owner=tp_dict)
+                        if format != Format.STRING:
+                            own = {
+                                n: typing._type_check(tp, msg, module=tp_dict.__module__)
+                                for n, tp in own.items()
+                            }
+                    elif format == Format.STRING:
+                        own = annotationlib.annotations_to_string(own_annotations)
+                    elif format in (Format.FORWARDREF, Format.VALUE):
+                        own = own_checked_annotations
+                    else:
+                        raise NotImplementedError(format)
+                    annos.update(own)
+                    return annos
+
+                tp_dict.__annotate__ = __annotate__
+            else:
+                tp_dict.__annotations__ = annotations
+            tp_dict.__required_keys__ = frozenset(required_keys)
+            tp_dict.__optional_keys__ = frozenset(optional_keys)
+            tp_dict.__readonly_keys__ = frozenset(readonly_keys)
+            tp_dict.__mutable_keys__ = frozenset(mutable_keys)
+            tp_dict.__total__ = total
+            tp_dict.__closed__ = closed
+            tp_dict.__extra_items__ = extra_items_type
+            return tp_dict
+
+        __call__ = dict  # static method
+
+        def __subclasscheck__(cls, other):
+            # Typed dicts are only for static structural subtyping.
+            raise TypeError('TypedDict does not support instance and class checks')
+
+        __instancecheck__ = __subclasscheck__
+
+    _TypedDict = type.__new__(_TypedDictMeta, 'TypedDict', (), {})
+
+    def _create_typeddict(
+        typename,
+        fields,
+        /,
+        *,
+        typing_is_inline,
+        total,
+        closed,
+        extra_items,
+        **kwargs,
+    ):
+        if fields is _marker or fields is None:
+            if fields is _marker:
+                deprecated_thing = (
+                    "Failing to pass a value for the 'fields' parameter"
+                )
+            else:
+                deprecated_thing = "Passing `None` as the 'fields' parameter"
+
+            example = f"`{typename} = TypedDict({typename!r}, {{}})`"
+            deprecation_msg = (
+                f"{deprecated_thing} is deprecated and will be disallowed in "
+                "Python 3.15. To create a TypedDict class with 0 fields "
+                "using the functional syntax, pass an empty dictionary, e.g. "
+            ) + example + "."
+            warnings.warn(deprecation_msg, DeprecationWarning, stacklevel=2)
+            # Support a field called "closed"
+            if closed is not False and closed is not True and closed is not None:
+                kwargs["closed"] = closed
+                closed = None
+            # Or "extra_items"
+            if extra_items is not NoExtraItems:
+                kwargs["extra_items"] = extra_items
+                extra_items = NoExtraItems
+            fields = kwargs
+        elif kwargs:
+            raise TypeError("TypedDict takes either a dict or keyword arguments,"
+                            " but not both")
+        if kwargs:
+            if sys.version_info >= (3, 13):
+                raise TypeError("TypedDict takes no keyword arguments")
+            warnings.warn(
+                "The kwargs-based syntax for TypedDict definitions is deprecated "
+                "in Python 3.11, will be removed in Python 3.13, and may not be "
+                "understood by third-party type checkers.",
+                DeprecationWarning,
+                stacklevel=2,
+            )
+
+        ns = {'__annotations__': dict(fields)}
+        module = _caller(depth=4 if typing_is_inline else 2)
+        if module is not None:
+            # Setting correct module is necessary to make typed dict classes
+            # pickleable.
+            ns['__module__'] = module
+
+        td = _TypedDictMeta(typename, (), ns, total=total, closed=closed,
+                            extra_items=extra_items)
+        td.__orig_bases__ = (TypedDict,)
+        return td
+
+    class _TypedDictSpecialForm(_SpecialForm, _root=True):
+        def __call__(
+            self,
+            typename,
+            fields=_marker,
+            /,
+            *,
+            total=True,
+            closed=None,
+            extra_items=NoExtraItems,
+            **kwargs
+        ):
+            return _create_typeddict(
+                typename,
+                fields,
+                typing_is_inline=False,
+                total=total,
+                closed=closed,
+                extra_items=extra_items,
+                **kwargs,
+            )
+
+        def __mro_entries__(self, bases):
+            return (_TypedDict,)
+
+    @_TypedDictSpecialForm
+    def TypedDict(self, args):
+        """A simple typed namespace. At runtime it is equivalent to a plain dict.
+
+        TypedDict creates a dictionary type such that a type checker will expect all
+        instances to have a certain set of keys, where each key is
+        associated with a value of a consistent type. This expectation
+        is not checked at runtime.
+
+        Usage::
+
+            class Point2D(TypedDict):
+                x: int
+                y: int
+                label: str
+
+            a: Point2D = {'x': 1, 'y': 2, 'label': 'good'}  # OK
+            b: Point2D = {'z': 3, 'label': 'bad'}           # Fails type check
+
+            assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')
+
+        The type info can be accessed via the Point2D.__annotations__ dict, and
+        the Point2D.__required_keys__ and Point2D.__optional_keys__ frozensets.
+        TypedDict supports an additional equivalent form::
+
+            Point2D = TypedDict('Point2D', {'x': int, 'y': int, 'label': str})
+
+        By default, all keys must be present in a TypedDict. It is possible
+        to override this by specifying totality::
+
+            class Point2D(TypedDict, total=False):
+                x: int
+                y: int
+
+        This means that a Point2D TypedDict can have any of the keys omitted. A type
+        checker is only expected to support a literal False or True as the value of
+        the total argument. True is the default, and makes all items defined in the
+        class body be required.
+
+        The Required and NotRequired special forms can also be used to mark
+        individual keys as being required or not required::
+
+            class Point2D(TypedDict):
+                x: int  # the "x" key must always be present (Required is the default)
+                y: NotRequired[int]  # the "y" key can be omitted
+
+        See PEP 655 for more details on Required and NotRequired.
+        """
+        # This runs when creating inline TypedDicts:
+        if not isinstance(args, dict):
+            raise TypeError(
+                "TypedDict[...] should be used with a single dict argument"
+            )
+
+        return _create_typeddict(
+            "",
+            args,
+            typing_is_inline=True,
+            total=True,
+            closed=True,
+            extra_items=NoExtraItems,
+        )
+
+    _TYPEDDICT_TYPES = (typing._TypedDictMeta, _TypedDictMeta)
+
+    def is_typeddict(tp):
+        """Check if an annotation is a TypedDict class
+
+        For example::
+            class Film(TypedDict):
+                title: str
+                year: int
+
+            is_typeddict(Film)  # => True
+            is_typeddict(Union[list, str])  # => False
+        """
+        return isinstance(tp, _TYPEDDICT_TYPES)
+
+
+if hasattr(typing, "assert_type"):
+    assert_type = typing.assert_type
+
+else:
+    def assert_type(val, typ, /):
+        """Assert (to the type checker) that the value is of the given type.
+
+        When the type checker encounters a call to assert_type(), it
+        emits an error if the value is not of the specified type::
+
+            def greet(name: str) -> None:
+                assert_type(name, str)  # ok
+                assert_type(name, int)  # type checker error
+
+        At runtime this returns the first argument unchanged and otherwise
+        does nothing.
+        """
+        return val
+
+
+if hasattr(typing, "ReadOnly"):  # 3.13+
+    get_type_hints = typing.get_type_hints
+else:  # <=3.13
+    # replaces _strip_annotations()
+    def _strip_extras(t):
+        """Strips Annotated, Required and NotRequired from a given type."""
+        if isinstance(t, typing._AnnotatedAlias):
+            return _strip_extras(t.__origin__)
+        if hasattr(t, "__origin__") and t.__origin__ in (Required, NotRequired, ReadOnly):
+            return _strip_extras(t.__args__[0])
+        if isinstance(t, typing._GenericAlias):
+            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+            if stripped_args == t.__args__:
+                return t
+            return t.copy_with(stripped_args)
+        if hasattr(_types, "GenericAlias") and isinstance(t, _types.GenericAlias):
+            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+            if stripped_args == t.__args__:
+                return t
+            return _types.GenericAlias(t.__origin__, stripped_args)
+        if hasattr(_types, "UnionType") and isinstance(t, _types.UnionType):
+            stripped_args = tuple(_strip_extras(a) for a in t.__args__)
+            if stripped_args == t.__args__:
+                return t
+            return functools.reduce(operator.or_, stripped_args)
+
+        return t
+
+    def get_type_hints(obj, globalns=None, localns=None, include_extras=False):
+        """Return type hints for an object.
+
+        This is often the same as obj.__annotations__, but it handles
+        forward references encoded as string literals, adds Optional[t] if a
+        default value equal to None is set and recursively replaces all
+        'Annotated[T, ...]', 'Required[T]' or 'NotRequired[T]' with 'T'
+        (unless 'include_extras=True').
+
+        The argument may be a module, class, method, or function. The annotations
+        are returned as a dictionary. For classes, annotations include also
+        inherited members.
+
+        TypeError is raised if the argument is not of a type that can contain
+        annotations, and an empty dictionary is returned if no annotations are
+        present.
+
+        BEWARE -- the behavior of globalns and localns is counterintuitive
+        (unless you are familiar with how eval() and exec() work).  The
+        search order is locals first, then globals.
+
+        - If no dict arguments are passed, an attempt is made to use the
+          globals from obj (or the respective module's globals for classes),
+          and these are also used as the locals.  If the object does not appear
+          to have globals, an empty dictionary is used.
+
+        - If one dict argument is passed, it is used for both globals and
+          locals.
+
+        - If two dict arguments are passed, they specify globals and
+          locals, respectively.
+        """
+        hint = typing.get_type_hints(
+            obj, globalns=globalns, localns=localns, include_extras=True
+        )
+        if sys.version_info < (3, 11):
+            _clean_optional(obj, hint, globalns, localns)
+        if include_extras:
+            return hint
+        return {k: _strip_extras(t) for k, t in hint.items()}
+
+    _NoneType = type(None)
+
+    def _could_be_inserted_optional(t):
+        """detects Union[..., None] pattern"""
+        if not isinstance(t, typing._UnionGenericAlias):
+            return False
+        # Assume if last argument is not None they are user defined
+        if t.__args__[-1] is not _NoneType:
+            return False
+        return True
+
+    # < 3.11
+    def _clean_optional(obj, hints, globalns=None, localns=None):
+        # reverts injected Union[..., None] cases from typing.get_type_hints
+        # when a None default value is used.
+        # see https://github.com/python/typing_extensions/issues/310
+        if not hints or isinstance(obj, type):
+            return
+        defaults = typing._get_defaults(obj)  # avoid accessing __annotations___
+        if not defaults:
+            return
+        original_hints = obj.__annotations__
+        for name, value in hints.items():
+            # Not a Union[..., None] or replacement conditions not fullfilled
+            if (not _could_be_inserted_optional(value)
+                or name not in defaults
+                or defaults[name] is not None
+            ):
+                continue
+            original_value = original_hints[name]
+            # value=NoneType should have caused a skip above but check for safety
+            if original_value is None:
+                original_value = _NoneType
+            # Forward reference
+            if isinstance(original_value, str):
+                if globalns is None:
+                    if isinstance(obj, _types.ModuleType):
+                        globalns = obj.__dict__
+                    else:
+                        nsobj = obj
+                        # Find globalns for the unwrapped object.
+                        while hasattr(nsobj, '__wrapped__'):
+                            nsobj = nsobj.__wrapped__
+                        globalns = getattr(nsobj, '__globals__', {})
+                    if localns is None:
+                        localns = globalns
+                elif localns is None:
+                    localns = globalns
+
+                original_value = ForwardRef(
+                    original_value,
+                    is_argument=not isinstance(obj, _types.ModuleType)
+                )
+            original_evaluated = typing._eval_type(original_value, globalns, localns)
+            # Compare if values differ. Note that even if equal
+            # value might be cached by typing._tp_cache contrary to original_evaluated
+            if original_evaluated != value or (
+                # 3.10: ForwardRefs of UnionType might be turned into _UnionGenericAlias
+                hasattr(_types, "UnionType")
+                and isinstance(original_evaluated, _types.UnionType)
+                and not isinstance(value, _types.UnionType)
+            ):
+                hints[name] = original_evaluated
+
+# Python 3.9 has get_origin() and get_args() but those implementations don't support
+# ParamSpecArgs and ParamSpecKwargs, so only Python 3.10's versions will do.
+if sys.version_info[:2] >= (3, 10):
+    get_origin = typing.get_origin
+    get_args = typing.get_args
+# 3.9
+else:
+    def get_origin(tp):
+        """Get the unsubscripted version of a type.
+
+        This supports generic types, Callable, Tuple, Union, Literal, Final, ClassVar
+        and Annotated. Return None for unsupported types. Examples::
+
+            get_origin(Literal[42]) is Literal
+            get_origin(int) is None
+            get_origin(ClassVar[int]) is ClassVar
+            get_origin(Generic) is Generic
+            get_origin(Generic[T]) is Generic
+            get_origin(Union[T, int]) is Union
+            get_origin(List[Tuple[T, T]][int]) == list
+            get_origin(P.args) is P
+        """
+        if isinstance(tp, typing._AnnotatedAlias):
+            return Annotated
+        if isinstance(tp, (typing._BaseGenericAlias, _types.GenericAlias,
+                           ParamSpecArgs, ParamSpecKwargs)):
+            return tp.__origin__
+        if tp is typing.Generic:
+            return typing.Generic
+        return None
+
+    def get_args(tp):
+        """Get type arguments with all substitutions performed.
+
+        For unions, basic simplifications used by Union constructor are performed.
+        Examples::
+            get_args(Dict[str, int]) == (str, int)
+            get_args(int) == ()
+            get_args(Union[int, Union[T, int], str][int]) == (int, str)
+            get_args(Union[int, Tuple[T, int]][str]) == (int, Tuple[str, int])
+            get_args(Callable[[], T][int]) == ([], int)
+        """
+        if isinstance(tp, typing._AnnotatedAlias):
+            return (tp.__origin__, *tp.__metadata__)
+        if isinstance(tp, (typing._GenericAlias, _types.GenericAlias)):
+            res = tp.__args__
+            if get_origin(tp) is collections.abc.Callable and res[0] is not Ellipsis:
+                res = (list(res[:-1]), res[-1])
+            return res
+        return ()
+
+
+# 3.10+
+if hasattr(typing, 'TypeAlias'):
+    TypeAlias = typing.TypeAlias
+# 3.9
+else:
+    @_ExtensionsSpecialForm
+    def TypeAlias(self, parameters):
+        """Special marker indicating that an assignment should
+        be recognized as a proper type alias definition by type
+        checkers.
+
+        For example::
+
+            Predicate: TypeAlias = Callable[..., bool]
+
+        It's invalid when used anywhere except as in the example above.
+        """
+        raise TypeError(f"{self} is not subscriptable")
+
+
+def _set_default(type_param, default):
+    type_param.has_default = lambda: default is not NoDefault
+    type_param.__default__ = default
+
+
+def _set_module(typevarlike):
+    # for pickling:
+    def_mod = _caller(depth=2)
+    if def_mod != 'typing_extensions':
+        typevarlike.__module__ = def_mod
+
+
+class _DefaultMixin:
+    """Mixin for TypeVarLike defaults."""
+
+    __slots__ = ()
+    __init__ = _set_default
+
+
+# Classes using this metaclass must provide a _backported_typevarlike ClassVar
+class _TypeVarLikeMeta(type):
+    def __instancecheck__(cls, __instance: Any) -> bool:
+        return isinstance(__instance, cls._backported_typevarlike)
+
+
+if _PEP_696_IMPLEMENTED:
+    from typing import TypeVar
+else:
+    # Add default and infer_variance parameters from PEP 696 and 695
+    class TypeVar(metaclass=_TypeVarLikeMeta):
+        """Type variable."""
+
+        _backported_typevarlike = typing.TypeVar
+
+        def __new__(cls, name, *constraints, bound=None,
+                    covariant=False, contravariant=False,
+                    default=NoDefault, infer_variance=False):
+            if hasattr(typing, "TypeAliasType"):
+                # PEP 695 implemented (3.12+), can pass infer_variance to typing.TypeVar
+                typevar = typing.TypeVar(name, *constraints, bound=bound,
+                                         covariant=covariant, contravariant=contravariant,
+                                         infer_variance=infer_variance)
+            else:
+                typevar = typing.TypeVar(name, *constraints, bound=bound,
+                                         covariant=covariant, contravariant=contravariant)
+                if infer_variance and (covariant or contravariant):
+                    raise ValueError("Variance cannot be specified with infer_variance.")
+                typevar.__infer_variance__ = infer_variance
+
+            _set_default(typevar, default)
+            _set_module(typevar)
+
+            def _tvar_prepare_subst(alias, args):
+                if (
+                    typevar.has_default()
+                    and alias.__parameters__.index(typevar) == len(args)
+                ):
+                    args += (typevar.__default__,)
+                return args
+
+            typevar.__typing_prepare_subst__ = _tvar_prepare_subst
+            return typevar
+
+        def __init_subclass__(cls) -> None:
+            raise TypeError(f"type '{__name__}.TypeVar' is not an acceptable base type")
+
+
+# Python 3.10+ has PEP 612
+if hasattr(typing, 'ParamSpecArgs'):
+    ParamSpecArgs = typing.ParamSpecArgs
+    ParamSpecKwargs = typing.ParamSpecKwargs
+# 3.9
+else:
+    class _Immutable:
+        """Mixin to indicate that object should not be copied."""
+        __slots__ = ()
+
+        def __copy__(self):
+            return self
+
+        def __deepcopy__(self, memo):
+            return self
+
+    class ParamSpecArgs(_Immutable):
+        """The args for a ParamSpec object.
+
+        Given a ParamSpec object P, P.args is an instance of ParamSpecArgs.
+
+        ParamSpecArgs objects have a reference back to their ParamSpec:
+
+        P.args.__origin__ is P
+
+        This type is meant for runtime introspection and has no special meaning to
+        static type checkers.
+        """
+        def __init__(self, origin):
+            self.__origin__ = origin
+
+        def __repr__(self):
+            return f"{self.__origin__.__name__}.args"
+
+        def __eq__(self, other):
+            if not isinstance(other, ParamSpecArgs):
+                return NotImplemented
+            return self.__origin__ == other.__origin__
+
+    class ParamSpecKwargs(_Immutable):
+        """The kwargs for a ParamSpec object.
+
+        Given a ParamSpec object P, P.kwargs is an instance of ParamSpecKwargs.
+
+        ParamSpecKwargs objects have a reference back to their ParamSpec:
+
+        P.kwargs.__origin__ is P
+
+        This type is meant for runtime introspection and has no special meaning to
+        static type checkers.
+        """
+        def __init__(self, origin):
+            self.__origin__ = origin
+
+        def __repr__(self):
+            return f"{self.__origin__.__name__}.kwargs"
+
+        def __eq__(self, other):
+            if not isinstance(other, ParamSpecKwargs):
+                return NotImplemented
+            return self.__origin__ == other.__origin__
+
+
+if _PEP_696_IMPLEMENTED:
+    from typing import ParamSpec
+
+# 3.10+
+elif hasattr(typing, 'ParamSpec'):
+
+    # Add default parameter - PEP 696
+    class ParamSpec(metaclass=_TypeVarLikeMeta):
+        """Parameter specification."""
+
+        _backported_typevarlike = typing.ParamSpec
+
+        def __new__(cls, name, *, bound=None,
+                    covariant=False, contravariant=False,
+                    infer_variance=False, default=NoDefault):
+            if hasattr(typing, "TypeAliasType"):
+                # PEP 695 implemented, can pass infer_variance to typing.TypeVar
+                paramspec = typing.ParamSpec(name, bound=bound,
+                                             covariant=covariant,
+                                             contravariant=contravariant,
+                                             infer_variance=infer_variance)
+            else:
+                paramspec = typing.ParamSpec(name, bound=bound,
+                                             covariant=covariant,
+                                             contravariant=contravariant)
+                paramspec.__infer_variance__ = infer_variance
+
+            _set_default(paramspec, default)
+            _set_module(paramspec)
+
+            def _paramspec_prepare_subst(alias, args):
+                params = alias.__parameters__
+                i = params.index(paramspec)
+                if i == len(args) and paramspec.has_default():
+                    args = [*args, paramspec.__default__]
+                if i >= len(args):
+                    raise TypeError(f"Too few arguments for {alias}")
+                # Special case where Z[[int, str, bool]] == Z[int, str, bool] in PEP 612.
+                if len(params) == 1 and not typing._is_param_expr(args[0]):
+                    assert i == 0
+                    args = (args,)
+                # Convert lists to tuples to help other libraries cache the results.
+                elif isinstance(args[i], list):
+                    args = (*args[:i], tuple(args[i]), *args[i + 1:])
+                return args
+
+            paramspec.__typing_prepare_subst__ = _paramspec_prepare_subst
+            return paramspec
+
+        def __init_subclass__(cls) -> None:
+            raise TypeError(f"type '{__name__}.ParamSpec' is not an acceptable base type")
+
+# 3.9
+else:
+
+    # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
+    class ParamSpec(list, _DefaultMixin):
+        """Parameter specification variable.
+
+        Usage::
+
+           P = ParamSpec('P')
+
+        Parameter specification variables exist primarily for the benefit of static
+        type checkers.  They are used to forward the parameter types of one
+        callable to another callable, a pattern commonly found in higher order
+        functions and decorators.  They are only valid when used in ``Concatenate``,
+        or s the first argument to ``Callable``. In Python 3.10 and higher,
+        they are also supported in user-defined Generics at runtime.
+        See class Generic for more information on generic types.  An
+        example for annotating a decorator::
+
+           T = TypeVar('T')
+           P = ParamSpec('P')
+
+           def add_logging(f: Callable[P, T]) -> Callable[P, T]:
+               '''A type-safe decorator to add logging to a function.'''
+               def inner(*args: P.args, **kwargs: P.kwargs) -> T:
+                   logging.info(f'{f.__name__} was called')
+                   return f(*args, **kwargs)
+               return inner
+
+           @add_logging
+           def add_two(x: float, y: float) -> float:
+               '''Add two numbers together.'''
+               return x + y
+
+        Parameter specification variables defined with covariant=True or
+        contravariant=True can be used to declare covariant or contravariant
+        generic types.  These keyword arguments are valid, but their actual semantics
+        are yet to be decided.  See PEP 612 for details.
+
+        Parameter specification variables can be introspected. e.g.:
+
+           P.__name__ == 'T'
+           P.__bound__ == None
+           P.__covariant__ == False
+           P.__contravariant__ == False
+
+        Note that only parameter specification variables defined in global scope can
+        be pickled.
+        """
+
+        # Trick Generic __parameters__.
+        __class__ = typing.TypeVar
+
+        @property
+        def args(self):
+            return ParamSpecArgs(self)
+
+        @property
+        def kwargs(self):
+            return ParamSpecKwargs(self)
+
+        def __init__(self, name, *, bound=None, covariant=False, contravariant=False,
+                     infer_variance=False, default=NoDefault):
+            list.__init__(self, [self])
+            self.__name__ = name
+            self.__covariant__ = bool(covariant)
+            self.__contravariant__ = bool(contravariant)
+            self.__infer_variance__ = bool(infer_variance)
+            if bound:
+                self.__bound__ = typing._type_check(bound, 'Bound must be a type.')
+            else:
+                self.__bound__ = None
+            _DefaultMixin.__init__(self, default)
+
+            # for pickling:
+            def_mod = _caller()
+            if def_mod != 'typing_extensions':
+                self.__module__ = def_mod
+
+        def __repr__(self):
+            if self.__infer_variance__:
+                prefix = ''
+            elif self.__covariant__:
+                prefix = '+'
+            elif self.__contravariant__:
+                prefix = '-'
+            else:
+                prefix = '~'
+            return prefix + self.__name__
+
+        def __hash__(self):
+            return object.__hash__(self)
+
+        def __eq__(self, other):
+            return self is other
+
+        def __reduce__(self):
+            return self.__name__
+
+        # Hack to get typing._type_check to pass.
+        def __call__(self, *args, **kwargs):
+            pass
+
+
+# 3.9
+if not hasattr(typing, 'Concatenate'):
+    # Inherits from list as a workaround for Callable checks in Python < 3.9.2.
+
+    # 3.9.0-1
+    if not hasattr(typing, '_type_convert'):
+        def _type_convert(arg, module=None, *, allow_special_forms=False):
+            """For converting None to type(None), and strings to ForwardRef."""
+            if arg is None:
+                return type(None)
+            if isinstance(arg, str):
+                if sys.version_info <= (3, 9, 6):
+                    return ForwardRef(arg)
+                if sys.version_info <= (3, 9, 7):
+                    return ForwardRef(arg, module=module)
+                return ForwardRef(arg, module=module, is_class=allow_special_forms)
+            return arg
+    else:
+        _type_convert = typing._type_convert
+
+    class _ConcatenateGenericAlias(list):
+
+        # Trick Generic into looking into this for __parameters__.
+        __class__ = typing._GenericAlias
+
+        def __init__(self, origin, args):
+            super().__init__(args)
+            self.__origin__ = origin
+            self.__args__ = args
+
+        def __repr__(self):
+            _type_repr = typing._type_repr
+            return (f'{_type_repr(self.__origin__)}'
+                    f'[{", ".join(_type_repr(arg) for arg in self.__args__)}]')
+
+        def __hash__(self):
+            return hash((self.__origin__, self.__args__))
+
+        # Hack to get typing._type_check to pass in Generic.
+        def __call__(self, *args, **kwargs):
+            pass
+
+        @property
+        def __parameters__(self):
+            return tuple(
+                tp for tp in self.__args__ if isinstance(tp, (typing.TypeVar, ParamSpec))
+            )
+
+        # 3.9 used by __getitem__ below
+        def copy_with(self, params):
+            if isinstance(params[-1], _ConcatenateGenericAlias):
+                params = (*params[:-1], *params[-1].__args__)
+            elif isinstance(params[-1], (list, tuple)):
+                return (*params[:-1], *params[-1])
+            elif (not (params[-1] is ... or isinstance(params[-1], ParamSpec))):
+                raise TypeError("The last parameter to Concatenate should be a "
+                        "ParamSpec variable or ellipsis.")
+            return self.__class__(self.__origin__, params)
+
+        # 3.9; accessed during GenericAlias.__getitem__ when substituting
+        def __getitem__(self, args):
+            if self.__origin__ in (Generic, Protocol):
+                # Can't subscript Generic[...] or Protocol[...].
+                raise TypeError(f"Cannot subscript already-subscripted {self}")
+            if not self.__parameters__:
+                raise TypeError(f"{self} is not a generic class")
+
+            if not isinstance(args, tuple):
+                args = (args,)
+            args = _unpack_args(*(_type_convert(p) for p in args))
+            params = self.__parameters__
+            for param in params:
+                prepare = getattr(param, "__typing_prepare_subst__", None)
+                if prepare is not None:
+                    args = prepare(self, args)
+                # 3.9 & typing.ParamSpec
+                elif isinstance(param, ParamSpec):
+                    i = params.index(param)
+                    if (
+                        i == len(args)
+                        and getattr(param, '__default__', NoDefault) is not NoDefault
+                    ):
+                        args = [*args, param.__default__]
+                    if i >= len(args):
+                        raise TypeError(f"Too few arguments for {self}")
+                    # Special case for Z[[int, str, bool]] == Z[int, str, bool]
+                    if len(params) == 1 and not _is_param_expr(args[0]):
+                        assert i == 0
+                        args = (args,)
+                    elif (
+                        isinstance(args[i], list)
+                        # 3.9
+                        # This class inherits from list do not convert
+                        and not isinstance(args[i], _ConcatenateGenericAlias)
+                    ):
+                        args = (*args[:i], tuple(args[i]), *args[i + 1:])
+
+            alen = len(args)
+            plen = len(params)
+            if alen != plen:
+                raise TypeError(
+                    f"Too {'many' if alen > plen else 'few'} arguments for {self};"
+                    f" actual {alen}, expected {plen}"
+                )
+
+            subst = dict(zip(self.__parameters__, args))
+            # determine new args
+            new_args = []
+            for arg in self.__args__:
+                if isinstance(arg, type):
+                    new_args.append(arg)
+                    continue
+                if isinstance(arg, TypeVar):
+                    arg = subst[arg]
+                    if (
+                        (isinstance(arg, typing._GenericAlias) and _is_unpack(arg))
+                        or (
+                            hasattr(_types, "GenericAlias")
+                            and isinstance(arg, _types.GenericAlias)
+                            and getattr(arg, "__unpacked__", False)
+                        )
+                    ):
+                        raise TypeError(f"{arg} is not valid as type argument")
+
+                elif isinstance(arg,
+                    typing._GenericAlias
+                    if not hasattr(_types, "GenericAlias") else
+                    (typing._GenericAlias, _types.GenericAlias)
+                ):
+                    subparams = arg.__parameters__
+                    if subparams:
+                        subargs = tuple(subst[x] for x in subparams)
+                        arg = arg[subargs]
+                new_args.append(arg)
+            return self.copy_with(tuple(new_args))
+
+# 3.10+
+else:
+    _ConcatenateGenericAlias = typing._ConcatenateGenericAlias
+
+    # 3.10
+    if sys.version_info < (3, 11):
+
+        class _ConcatenateGenericAlias(typing._ConcatenateGenericAlias, _root=True):
+            # needed for checks in collections.abc.Callable to accept this class
+            __module__ = "typing"
+
+            def copy_with(self, params):
+                if isinstance(params[-1], (list, tuple)):
+                    return (*params[:-1], *params[-1])
+                if isinstance(params[-1], typing._ConcatenateGenericAlias):
+                    params = (*params[:-1], *params[-1].__args__)
+                elif not (params[-1] is ... or isinstance(params[-1], ParamSpec)):
+                    raise TypeError("The last parameter to Concatenate should be a "
+                            "ParamSpec variable or ellipsis.")
+                return super(typing._ConcatenateGenericAlias, self).copy_with(params)
+
+            def __getitem__(self, args):
+                value = super().__getitem__(args)
+                if isinstance(value, tuple) and any(_is_unpack(t) for t in value):
+                    return tuple(_unpack_args(*(n for n in value)))
+                return value
+
+
+# 3.9.2
+class _EllipsisDummy: ...
+
+
+# <=3.10
+def _create_concatenate_alias(origin, parameters):
+    if parameters[-1] is ... and sys.version_info < (3, 9, 2):
+        # Hack: Arguments must be types, replace it with one.
+        parameters = (*parameters[:-1], _EllipsisDummy)
+    if sys.version_info >= (3, 10, 3):
+        concatenate = _ConcatenateGenericAlias(origin, parameters,
+                                        _typevar_types=(TypeVar, ParamSpec),
+                                        _paramspec_tvars=True)
+    else:
+        concatenate = _ConcatenateGenericAlias(origin, parameters)
+    if parameters[-1] is not _EllipsisDummy:
+        return concatenate
+    # Remove dummy again
+    concatenate.__args__ = tuple(p if p is not _EllipsisDummy else ...
+                                    for p in concatenate.__args__)
+    if sys.version_info < (3, 10):
+        # backport needs __args__ adjustment only
+        return concatenate
+    concatenate.__parameters__ = tuple(p for p in concatenate.__parameters__
+                                        if p is not _EllipsisDummy)
+    return concatenate
+
+
+# <=3.10
+@typing._tp_cache
+def _concatenate_getitem(self, parameters):
+    if parameters == ():
+        raise TypeError("Cannot take a Concatenate of no types.")
+    if not isinstance(parameters, tuple):
+        parameters = (parameters,)
+    if not (parameters[-1] is ... or isinstance(parameters[-1], ParamSpec)):
+        raise TypeError("The last parameter to Concatenate should be a "
+                        "ParamSpec variable or ellipsis.")
+    msg = "Concatenate[arg, ...]: each arg must be a type."
+    parameters = (*(typing._type_check(p, msg) for p in parameters[:-1]),
+                    parameters[-1])
+    return _create_concatenate_alias(self, parameters)
+
+
+# 3.11+; Concatenate does not accept ellipsis in 3.10
+if sys.version_info >= (3, 11):
+    Concatenate = typing.Concatenate
+# <=3.10
+else:
+    @_ExtensionsSpecialForm
+    def Concatenate(self, parameters):
+        """Used in conjunction with ``ParamSpec`` and ``Callable`` to represent a
+        higher order function which adds, removes or transforms parameters of a
+        callable.
+
+        For example::
+
+           Callable[Concatenate[int, P], int]
+
+        See PEP 612 for detailed information.
+        """
+        return _concatenate_getitem(self, parameters)
+
+
+# 3.10+
+if hasattr(typing, 'TypeGuard'):
+    TypeGuard = typing.TypeGuard
+# 3.9
+else:
+    @_ExtensionsSpecialForm
+    def TypeGuard(self, parameters):
+        """Special typing form used to annotate the return type of a user-defined
+        type guard function.  ``TypeGuard`` only accepts a single type argument.
+        At runtime, functions marked this way should return a boolean.
+
+        ``TypeGuard`` aims to benefit *type narrowing* -- a technique used by static
+        type checkers to determine a more precise type of an expression within a
+        program's code flow.  Usually type narrowing is done by analyzing
+        conditional code flow and applying the narrowing to a block of code.  The
+        conditional expression here is sometimes referred to as a "type guard".
+
+        Sometimes it would be convenient to use a user-defined boolean function
+        as a type guard.  Such a function should use ``TypeGuard[...]`` as its
+        return type to alert static type checkers to this intention.
+
+        Using  ``-> TypeGuard`` tells the static type checker that for a given
+        function:
+
+        1. The return value is a boolean.
+        2. If the return value is ``True``, the type of its argument
+        is the type inside ``TypeGuard``.
+
+        For example::
+
+            def is_str(val: Union[str, float]):
+                # "isinstance" type guard
+                if isinstance(val, str):
+                    # Type of ``val`` is narrowed to ``str``
+                    ...
+                else:
+                    # Else, type of ``val`` is narrowed to ``float``.
+                    ...
+
+        Strict type narrowing is not enforced -- ``TypeB`` need not be a narrower
+        form of ``TypeA`` (it can even be a wider form) and this may lead to
+        type-unsafe results.  The main reason is to allow for things like
+        narrowing ``List[object]`` to ``List[str]`` even though the latter is not
+        a subtype of the former, since ``List`` is invariant.  The responsibility of
+        writing type-safe type guards is left to the user.
+
+        ``TypeGuard`` also works with type variables.  For more information, see
+        PEP 647 (User-Defined Type Guards).
+        """
+        item = typing._type_check(parameters, f'{self} accepts only a single type.')
+        return typing._GenericAlias(self, (item,))
+
+
+# 3.13+
+if hasattr(typing, 'TypeIs'):
+    TypeIs = typing.TypeIs
+# <=3.12
+else:
+    @_ExtensionsSpecialForm
+    def TypeIs(self, parameters):
+        """Special typing form used to annotate the return type of a user-defined
+        type narrower function.  ``TypeIs`` only accepts a single type argument.
+        At runtime, functions marked this way should return a boolean.
+
+        ``TypeIs`` aims to benefit *type narrowing* -- a technique used by static
+        type checkers to determine a more precise type of an expression within a
+        program's code flow.  Usually type narrowing is done by analyzing
+        conditional code flow and applying the narrowing to a block of code.  The
+        conditional expression here is sometimes referred to as a "type guard".
+
+        Sometimes it would be convenient to use a user-defined boolean function
+        as a type guard.  Such a function should use ``TypeIs[...]`` as its
+        return type to alert static type checkers to this intention.
+
+        Using  ``-> TypeIs`` tells the static type checker that for a given
+        function:
+
+        1. The return value is a boolean.
+        2. If the return value is ``True``, the type of its argument
+        is the intersection of the type inside ``TypeIs`` and the argument's
+        previously known type.
+
+        For example::
+
+            def is_awaitable(val: object) -> TypeIs[Awaitable[Any]]:
+                return hasattr(val, '__await__')
+
+            def f(val: Union[int, Awaitable[int]]) -> int:
+                if is_awaitable(val):
+                    assert_type(val, Awaitable[int])
+                else:
+                    assert_type(val, int)
+
+        ``TypeIs`` also works with type variables.  For more information, see
+        PEP 742 (Narrowing types with TypeIs).
+        """
+        item = typing._type_check(parameters, f'{self} accepts only a single type.')
+        return typing._GenericAlias(self, (item,))
+
+
+# 3.14+?
+if hasattr(typing, 'TypeForm'):
+    TypeForm = typing.TypeForm
+# <=3.13
+else:
+    class _TypeFormForm(_ExtensionsSpecialForm, _root=True):
+        # TypeForm(X) is equivalent to X but indicates to the type checker
+        # that the object is a TypeForm.
+        def __call__(self, obj, /):
+            return obj
+
+    @_TypeFormForm
+    def TypeForm(self, parameters):
+        """A special form representing the value that results from the evaluation
+        of a type expression. This value encodes the information supplied in the
+        type expression, and it represents the type described by that type expression.
+
+        When used in a type expression, TypeForm describes a set of type form objects.
+        It accepts a single type argument, which must be a valid type expression.
+        ``TypeForm[T]`` describes the set of all type form objects that represent
+        the type T or types that are assignable to T.
+
+        Usage:
+
+            def cast[T](typ: TypeForm[T], value: Any) -> T: ...
+
+            reveal_type(cast(int, "x"))  # int
+
+        See PEP 747 for more information.
+        """
+        item = typing._type_check(parameters, f'{self} accepts only a single type.')
+        return typing._GenericAlias(self, (item,))
+
+
+
+
+if hasattr(typing, "LiteralString"):  # 3.11+
+    LiteralString = typing.LiteralString
+else:
+    @_SpecialForm
+    def LiteralString(self, params):
+        """Represents an arbitrary literal string.
+
+        Example::
+
+          from typing_extensions import LiteralString
+
+          def query(sql: LiteralString) -> ...:
+              ...
+
+          query("SELECT * FROM table")  # ok
+          query(f"SELECT * FROM {input()}")  # not ok
+
+        See PEP 675 for details.
+
+        """
+        raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, "Self"):  # 3.11+
+    Self = typing.Self
+else:
+    @_SpecialForm
+    def Self(self, params):
+        """Used to spell the type of "self" in classes.
+
+        Example::
+
+          from typing import Self
+
+          class ReturnsSelf:
+              def parse(self, data: bytes) -> Self:
+                  ...
+                  return self
+
+        """
+
+        raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, "Never"):  # 3.11+
+    Never = typing.Never
+else:
+    @_SpecialForm
+    def Never(self, params):
+        """The bottom type, a type that has no members.
+
+        This can be used to define a function that should never be
+        called, or a function that never returns::
+
+            from typing_extensions import Never
+
+            def never_call_me(arg: Never) -> None:
+                pass
+
+            def int_or_str(arg: int | str) -> None:
+                never_call_me(arg)  # type checker error
+                match arg:
+                    case int():
+                        print("It's an int")
+                    case str():
+                        print("It's a str")
+                    case _:
+                        never_call_me(arg)  # ok, arg is of type Never
+
+        """
+
+        raise TypeError(f"{self} is not subscriptable")
+
+
+if hasattr(typing, 'Required'):  # 3.11+
+    Required = typing.Required
+    NotRequired = typing.NotRequired
+else:  # <=3.10
+    @_ExtensionsSpecialForm
+    def Required(self, parameters):
+        """A special typing construct to mark a key of a total=False TypedDict
+        as required. For example:
+
+            class Movie(TypedDict, total=False):
+                title: Required[str]
+                year: int
+
+            m = Movie(
+                title='The Matrix',  # typechecker error if key is omitted
+                year=1999,
+            )
+
+        There is no runtime checking that a required key is actually provided
+        when instantiating a related TypedDict.
+        """
+        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+        return typing._GenericAlias(self, (item,))
+
+    @_ExtensionsSpecialForm
+    def NotRequired(self, parameters):
+        """A special typing construct to mark a key of a TypedDict as
+        potentially missing. For example:
+
+            class Movie(TypedDict):
+                title: str
+                year: NotRequired[int]
+
+            m = Movie(
+                title='The Matrix',  # typechecker error if key is omitted
+                year=1999,
+            )
+        """
+        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+        return typing._GenericAlias(self, (item,))
+
+
+if hasattr(typing, 'ReadOnly'):
+    ReadOnly = typing.ReadOnly
+else:  # <=3.12
+    @_ExtensionsSpecialForm
+    def ReadOnly(self, parameters):
+        """A special typing construct to mark an item of a TypedDict as read-only.
+
+        For example:
+
+            class Movie(TypedDict):
+                title: ReadOnly[str]
+                year: int
+
+            def mutate_movie(m: Movie) -> None:
+                m["year"] = 1992  # allowed
+                m["title"] = "The Matrix"  # typechecker error
+
+        There is no runtime checking for this property.
+        """
+        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+        return typing._GenericAlias(self, (item,))
+
+
+_UNPACK_DOC = """\
+Type unpack operator.
+
+The type unpack operator takes the child types from some container type,
+such as `tuple[int, str]` or a `TypeVarTuple`, and 'pulls them out'. For
+example:
+
+  # For some generic class `Foo`:
+  Foo[Unpack[tuple[int, str]]]  # Equivalent to Foo[int, str]
+
+  Ts = TypeVarTuple('Ts')
+  # Specifies that `Bar` is generic in an arbitrary number of types.
+  # (Think of `Ts` as a tuple of an arbitrary number of individual
+  #  `TypeVar`s, which the `Unpack` is 'pulling out' directly into the
+  #  `Generic[]`.)
+  class Bar(Generic[Unpack[Ts]]): ...
+  Bar[int]  # Valid
+  Bar[int, str]  # Also valid
+
+From Python 3.11, this can also be done using the `*` operator:
+
+    Foo[*tuple[int, str]]
+    class Bar(Generic[*Ts]): ...
+
+The operator can also be used along with a `TypedDict` to annotate
+`**kwargs` in a function signature. For instance:
+
+  class Movie(TypedDict):
+    name: str
+    year: int
+
+  # This function expects two keyword arguments - *name* of type `str` and
+  # *year* of type `int`.
+  def foo(**kwargs: Unpack[Movie]): ...
+
+Note that there is only some runtime checking of this operator. Not
+everything the runtime allows may be accepted by static type checkers.
+
+For more information, see PEP 646 and PEP 692.
+"""
+
+
+if sys.version_info >= (3, 12):  # PEP 692 changed the repr of Unpack[]
+    Unpack = typing.Unpack
+
+    def _is_unpack(obj):
+        return get_origin(obj) is Unpack
+
+else:  # <=3.11
+    class _UnpackSpecialForm(_ExtensionsSpecialForm, _root=True):
+        def __init__(self, getitem):
+            super().__init__(getitem)
+            self.__doc__ = _UNPACK_DOC
+
+    class _UnpackAlias(typing._GenericAlias, _root=True):
+        if sys.version_info < (3, 11):
+            # needed for compatibility with Generic[Unpack[Ts]]
+            __class__ = typing.TypeVar
+
+        @property
+        def __typing_unpacked_tuple_args__(self):
+            assert self.__origin__ is Unpack
+            assert len(self.__args__) == 1
+            arg, = self.__args__
+            if isinstance(arg, (typing._GenericAlias, _types.GenericAlias)):
+                if arg.__origin__ is not tuple:
+                    raise TypeError("Unpack[...] must be used with a tuple type")
+                return arg.__args__
+            return None
+
+        @property
+        def __typing_is_unpacked_typevartuple__(self):
+            assert self.__origin__ is Unpack
+            assert len(self.__args__) == 1
+            return isinstance(self.__args__[0], TypeVarTuple)
+
+        def __getitem__(self, args):
+            if self.__typing_is_unpacked_typevartuple__:
+                return args
+            return super().__getitem__(args)
+
+    @_UnpackSpecialForm
+    def Unpack(self, parameters):
+        item = typing._type_check(parameters, f'{self._name} accepts only a single type.')
+        return _UnpackAlias(self, (item,))
+
+    def _is_unpack(obj):
+        return isinstance(obj, _UnpackAlias)
+
+
+def _unpack_args(*args):
+    newargs = []
+    for arg in args:
+        subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
+        if subargs is not None and (not (subargs and subargs[-1] is ...)):
+            newargs.extend(subargs)
+        else:
+            newargs.append(arg)
+    return newargs
+
+
+if _PEP_696_IMPLEMENTED:
+    from typing import TypeVarTuple
+
+elif hasattr(typing, "TypeVarTuple"):  # 3.11+
+
+    # Add default parameter - PEP 696
+    class TypeVarTuple(metaclass=_TypeVarLikeMeta):
+        """Type variable tuple."""
+
+        _backported_typevarlike = typing.TypeVarTuple
+
+        def __new__(cls, name, *, default=NoDefault):
+            tvt = typing.TypeVarTuple(name)
+            _set_default(tvt, default)
+            _set_module(tvt)
+
+            def _typevartuple_prepare_subst(alias, args):
+                params = alias.__parameters__
+                typevartuple_index = params.index(tvt)
+                for param in params[typevartuple_index + 1:]:
+                    if isinstance(param, TypeVarTuple):
+                        raise TypeError(
+                            f"More than one TypeVarTuple parameter in {alias}"
+                        )
+
+                alen = len(args)
+                plen = len(params)
+                left = typevartuple_index
+                right = plen - typevartuple_index - 1
+                var_tuple_index = None
+                fillarg = None
+                for k, arg in enumerate(args):
+                    if not isinstance(arg, type):
+                        subargs = getattr(arg, '__typing_unpacked_tuple_args__', None)
+                        if subargs and len(subargs) == 2 and subargs[-1] is ...:
+                            if var_tuple_index is not None:
+                                raise TypeError(
+                                    "More than one unpacked "
+                                    "arbitrary-length tuple argument"
+                                )
+                            var_tuple_index = k
+                            fillarg = subargs[0]
+                if var_tuple_index is not None:
+                    left = min(left, var_tuple_index)
+                    right = min(right, alen - var_tuple_index - 1)
+                elif left + right > alen:
+                    raise TypeError(f"Too few arguments for {alias};"
+                                    f" actual {alen}, expected at least {plen - 1}")
+                if left == alen - right and tvt.has_default():
+                    replacement = _unpack_args(tvt.__default__)
+                else:
+                    replacement = args[left: alen - right]
+
+                return (
+                    *args[:left],
+                    *([fillarg] * (typevartuple_index - left)),
+                    replacement,
+                    *([fillarg] * (plen - right - left - typevartuple_index - 1)),
+                    *args[alen - right:],
+                )
+
+            tvt.__typing_prepare_subst__ = _typevartuple_prepare_subst
+            return tvt
+
+        def __init_subclass__(self, *args, **kwds):
+            raise TypeError("Cannot subclass special typing classes")
+
+else:  # <=3.10
+    class TypeVarTuple(_DefaultMixin):
+        """Type variable tuple.
+
+        Usage::
+
+            Ts = TypeVarTuple('Ts')
+
+        In the same way that a normal type variable is a stand-in for a single
+        type such as ``int``, a type variable *tuple* is a stand-in for a *tuple*
+        type such as ``Tuple[int, str]``.
+
+        Type variable tuples can be used in ``Generic`` declarations.
+        Consider the following example::
+
+            class Array(Generic[*Ts]): ...
+
+        The ``Ts`` type variable tuple here behaves like ``tuple[T1, T2]``,
+        where ``T1`` and ``T2`` are type variables. To use these type variables
+        as type parameters of ``Array``, we must *unpack* the type variable tuple using
+        the star operator: ``*Ts``. The signature of ``Array`` then behaves
+        as if we had simply written ``class Array(Generic[T1, T2]): ...``.
+        In contrast to ``Generic[T1, T2]``, however, ``Generic[*Shape]`` allows
+        us to parameterise the class with an *arbitrary* number of type parameters.
+
+        Type variable tuples can be used anywhere a normal ``TypeVar`` can.
+        This includes class definitions, as shown above, as well as function
+        signatures and variable annotations::
+
+            class Array(Generic[*Ts]):
+
+                def __init__(self, shape: Tuple[*Ts]):
+                    self._shape: Tuple[*Ts] = shape
+
+                def get_shape(self) -> Tuple[*Ts]:
+                    return self._shape
+
+            shape = (Height(480), Width(640))
+            x: Array[Height, Width] = Array(shape)
+            y = abs(x)  # Inferred type is Array[Height, Width]
+            z = x + x   #        ...    is Array[Height, Width]
+            x.get_shape()  #     ...    is tuple[Height, Width]
+
+        """
+
+        # Trick Generic __parameters__.
+        __class__ = typing.TypeVar
+
+        def __iter__(self):
+            yield self.__unpacked__
+
+        def __init__(self, name, *, default=NoDefault):
+            self.__name__ = name
+            _DefaultMixin.__init__(self, default)
+
+            # for pickling:
+            def_mod = _caller()
+            if def_mod != 'typing_extensions':
+                self.__module__ = def_mod
+
+            self.__unpacked__ = Unpack[self]
+
+        def __repr__(self):
+            return self.__name__
+
+        def __hash__(self):
+            return object.__hash__(self)
+
+        def __eq__(self, other):
+            return self is other
+
+        def __reduce__(self):
+            return self.__name__
+
+        def __init_subclass__(self, *args, **kwds):
+            if '_root' not in kwds:
+                raise TypeError("Cannot subclass special typing classes")
+
+
+if hasattr(typing, "reveal_type"):  # 3.11+
+    reveal_type = typing.reveal_type
+else:  # <=3.10
+    def reveal_type(obj: T, /) -> T:
+        """Reveal the inferred type of a variable.
+
+        When a static type checker encounters a call to ``reveal_type()``,
+        it will emit the inferred type of the argument::
+
+            x: int = 1
+            reveal_type(x)
+
+        Running a static type checker (e.g., ``mypy``) on this example
+        will produce output similar to 'Revealed type is "builtins.int"'.
+
+        At runtime, the function prints the runtime type of the
+        argument and returns it unchanged.
+
+        """
+        print(f"Runtime type is {type(obj).__name__!r}", file=sys.stderr)
+        return obj
+
+
+if hasattr(typing, "_ASSERT_NEVER_REPR_MAX_LENGTH"):  # 3.11+
+    _ASSERT_NEVER_REPR_MAX_LENGTH = typing._ASSERT_NEVER_REPR_MAX_LENGTH
+else:  # <=3.10
+    _ASSERT_NEVER_REPR_MAX_LENGTH = 100
+
+
+if hasattr(typing, "assert_never"):  # 3.11+
+    assert_never = typing.assert_never
+else:  # <=3.10
+    def assert_never(arg: Never, /) -> Never:
+        """Assert to the type checker that a line of code is unreachable.
+
+        Example::
+
+            def int_or_str(arg: int | str) -> None:
+                match arg:
+                    case int():
+                        print("It's an int")
+                    case str():
+                        print("It's a str")
+                    case _:
+                        assert_never(arg)
+
+        If a type checker finds that a call to assert_never() is
+        reachable, it will emit an error.
+
+        At runtime, this throws an exception when called.
+
+        """
+        value = repr(arg)
+        if len(value) > _ASSERT_NEVER_REPR_MAX_LENGTH:
+            value = value[:_ASSERT_NEVER_REPR_MAX_LENGTH] + '...'
+        raise AssertionError(f"Expected code to be unreachable, but got: {value}")
+
+
+if sys.version_info >= (3, 12):  # 3.12+
+    # dataclass_transform exists in 3.11 but lacks the frozen_default parameter
+    dataclass_transform = typing.dataclass_transform
+else:  # <=3.11
+    def dataclass_transform(
+        *,
+        eq_default: bool = True,
+        order_default: bool = False,
+        kw_only_default: bool = False,
+        frozen_default: bool = False,
+        field_specifiers: typing.Tuple[
+            typing.Union[typing.Type[typing.Any], typing.Callable[..., typing.Any]],
+            ...
+        ] = (),
+        **kwargs: typing.Any,
+    ) -> typing.Callable[[T], T]:
+        """Decorator that marks a function, class, or metaclass as providing
+        dataclass-like behavior.
+
+        Example:
+
+            from typing_extensions import dataclass_transform
+
+            _T = TypeVar("_T")
+
+            # Used on a decorator function
+            @dataclass_transform()
+            def create_model(cls: type[_T]) -> type[_T]:
+                ...
+                return cls
+
+            @create_model
+            class CustomerModel:
+                id: int
+                name: str
+
+            # Used on a base class
+            @dataclass_transform()
+            class ModelBase: ...
+
+            class CustomerModel(ModelBase):
+                id: int
+                name: str
+
+            # Used on a metaclass
+            @dataclass_transform()
+            class ModelMeta(type): ...
+
+            class ModelBase(metaclass=ModelMeta): ...
+
+            class CustomerModel(ModelBase):
+                id: int
+                name: str
+
+        Each of the ``CustomerModel`` classes defined in this example will now
+        behave similarly to a dataclass created with the ``@dataclasses.dataclass``
+        decorator. For example, the type checker will synthesize an ``__init__``
+        method.
+
+        The arguments to this decorator can be used to customize this behavior:
+        - ``eq_default`` indicates whether the ``eq`` parameter is assumed to be
+          True or False if it is omitted by the caller.
+        - ``order_default`` indicates whether the ``order`` parameter is
+          assumed to be True or False if it is omitted by the caller.
+        - ``kw_only_default`` indicates whether the ``kw_only`` parameter is
+          assumed to be True or False if it is omitted by the caller.
+        - ``frozen_default`` indicates whether the ``frozen`` parameter is
+          assumed to be True or False if it is omitted by the caller.
+        - ``field_specifiers`` specifies a static list of supported classes
+          or functions that describe fields, similar to ``dataclasses.field()``.
+
+        At runtime, this decorator records its arguments in the
+        ``__dataclass_transform__`` attribute on the decorated object.
+
+        See PEP 681 for details.
+
+        """
+        def decorator(cls_or_fn):
+            cls_or_fn.__dataclass_transform__ = {
+                "eq_default": eq_default,
+                "order_default": order_default,
+                "kw_only_default": kw_only_default,
+                "frozen_default": frozen_default,
+                "field_specifiers": field_specifiers,
+                "kwargs": kwargs,
+            }
+            return cls_or_fn
+        return decorator
+
+
+if hasattr(typing, "override"):  # 3.12+
+    override = typing.override
+else:  # <=3.11
+    _F = typing.TypeVar("_F", bound=typing.Callable[..., typing.Any])
+
+    def override(arg: _F, /) -> _F:
+        """Indicate that a method is intended to override a method in a base class.
+
+        Usage:
+
+            class Base:
+                def method(self) -> None:
+                    pass
+
+            class Child(Base):
+                @override
+                def method(self) -> None:
+                    super().method()
+
+        When this decorator is applied to a method, the type checker will
+        validate that it overrides a method with the same name on a base class.
+        This helps prevent bugs that may occur when a base class is changed
+        without an equivalent change to a child class.
+
+        There is no runtime checking of these properties. The decorator
+        sets the ``__override__`` attribute to ``True`` on the decorated object
+        to allow runtime introspection.
+
+        See PEP 698 for details.
+
+        """
+        try:
+            arg.__override__ = True
+        except (AttributeError, TypeError):
+            # Skip the attribute silently if it is not writable.
+            # AttributeError happens if the object has __slots__ or a
+            # read-only property, TypeError if it's a builtin class.
+            pass
+        return arg
+
+
+# Python 3.13.3+ contains a fix for the wrapped __new__
+if sys.version_info >= (3, 13, 3):
+    deprecated = warnings.deprecated
+else:
+    _T = typing.TypeVar("_T")
+
+    class deprecated:
+        """Indicate that a class, function or overload is deprecated.
+
+        When this decorator is applied to an object, the type checker
+        will generate a diagnostic on usage of the deprecated object.
+
+        Usage:
+
+            @deprecated("Use B instead")
+            class A:
+                pass
+
+            @deprecated("Use g instead")
+            def f():
+                pass
+
+            @overload
+            @deprecated("int support is deprecated")
+            def g(x: int) -> int: ...
+            @overload
+            def g(x: str) -> int: ...
+
+        The warning specified by *category* will be emitted at runtime
+        on use of deprecated objects. For functions, that happens on calls;
+        for classes, on instantiation and on creation of subclasses.
+        If the *category* is ``None``, no warning is emitted at runtime.
+        The *stacklevel* determines where the
+        warning is emitted. If it is ``1`` (the default), the warning
+        is emitted at the direct caller of the deprecated object; if it
+        is higher, it is emitted further up the stack.
+        Static type checker behavior is not affected by the *category*
+        and *stacklevel* arguments.
+
+        The deprecation message passed to the decorator is saved in the
+        ``__deprecated__`` attribute on the decorated object.
+        If applied to an overload, the decorator
+        must be after the ``@overload`` decorator for the attribute to
+        exist on the overload as returned by ``get_overloads()``.
+
+        See PEP 702 for details.
+
+        """
+        def __init__(
+            self,
+            message: str,
+            /,
+            *,
+            category: typing.Optional[typing.Type[Warning]] = DeprecationWarning,
+            stacklevel: int = 1,
+        ) -> None:
+            if not isinstance(message, str):
+                raise TypeError(
+                    "Expected an object of type str for 'message', not "
+                    f"{type(message).__name__!r}"
+                )
+            self.message = message
+            self.category = category
+            self.stacklevel = stacklevel
+
+        def __call__(self, arg: _T, /) -> _T:
+            # Make sure the inner functions created below don't
+            # retain a reference to self.
+            msg = self.message
+            category = self.category
+            stacklevel = self.stacklevel
+            if category is None:
+                arg.__deprecated__ = msg
+                return arg
+            elif isinstance(arg, type):
+                import functools
+                from types import MethodType
+
+                original_new = arg.__new__
+
+                @functools.wraps(original_new)
+                def __new__(cls, /, *args, **kwargs):
+                    if cls is arg:
+                        warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+                    if original_new is not object.__new__:
+                        return original_new(cls, *args, **kwargs)
+                    # Mirrors a similar check in object.__new__.
+                    elif cls.__init__ is object.__init__ and (args or kwargs):
+                        raise TypeError(f"{cls.__name__}() takes no arguments")
+                    else:
+                        return original_new(cls)
+
+                arg.__new__ = staticmethod(__new__)
+
+                original_init_subclass = arg.__init_subclass__
+                # We need slightly different behavior if __init_subclass__
+                # is a bound method (likely if it was implemented in Python)
+                if isinstance(original_init_subclass, MethodType):
+                    original_init_subclass = original_init_subclass.__func__
+
+                    @functools.wraps(original_init_subclass)
+                    def __init_subclass__(*args, **kwargs):
+                        warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+                        return original_init_subclass(*args, **kwargs)
+
+                    arg.__init_subclass__ = classmethod(__init_subclass__)
+                # Or otherwise, which likely means it's a builtin such as
+                # object's implementation of __init_subclass__.
+                else:
+                    @functools.wraps(original_init_subclass)
+                    def __init_subclass__(*args, **kwargs):
+                        warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+                        return original_init_subclass(*args, **kwargs)
+
+                    arg.__init_subclass__ = __init_subclass__
+
+                arg.__deprecated__ = __new__.__deprecated__ = msg
+                __init_subclass__.__deprecated__ = msg
+                return arg
+            elif callable(arg):
+                import asyncio.coroutines
+                import functools
+                import inspect
+
+                @functools.wraps(arg)
+                def wrapper(*args, **kwargs):
+                    warnings.warn(msg, category=category, stacklevel=stacklevel + 1)
+                    return arg(*args, **kwargs)
+
+                if asyncio.coroutines.iscoroutinefunction(arg):
+                    if sys.version_info >= (3, 12):
+                        wrapper = inspect.markcoroutinefunction(wrapper)
+                    else:
+                        wrapper._is_coroutine = asyncio.coroutines._is_coroutine
+
+                arg.__deprecated__ = wrapper.__deprecated__ = msg
+                return wrapper
+            else:
+                raise TypeError(
+                    "@deprecated decorator with non-None category must be applied to "
+                    f"a class or callable, not {arg!r}"
+                )
+
+if sys.version_info < (3, 10):
+    def _is_param_expr(arg):
+        return arg is ... or isinstance(
+            arg, (tuple, list, ParamSpec, _ConcatenateGenericAlias)
+        )
+else:
+    def _is_param_expr(arg):
+        return arg is ... or isinstance(
+            arg,
+            (
+                tuple,
+                list,
+                ParamSpec,
+                _ConcatenateGenericAlias,
+                typing._ConcatenateGenericAlias,
+            ),
+        )
+
+
+# We have to do some monkey patching to deal with the dual nature of
+# Unpack/TypeVarTuple:
+# - We want Unpack to be a kind of TypeVar so it gets accepted in
+#   Generic[Unpack[Ts]]
+# - We want it to *not* be treated as a TypeVar for the purposes of
+#   counting generic parameters, so that when we subscript a generic,
+#   the runtime doesn't try to substitute the Unpack with the subscripted type.
+if not hasattr(typing, "TypeVarTuple"):
+    def _check_generic(cls, parameters, elen=_marker):
+        """Check correct count for parameters of a generic cls (internal helper).
+
+        This gives a nice error message in case of count mismatch.
+        """
+        # If substituting a single ParamSpec with multiple arguments
+        # we do not check the count
+        if (inspect.isclass(cls) and issubclass(cls, typing.Generic)
+            and len(cls.__parameters__) == 1
+            and isinstance(cls.__parameters__[0], ParamSpec)
+            and parameters
+            and not _is_param_expr(parameters[0])
+        ):
+            # Generic modifies parameters variable, but here we cannot do this
+            return
+
+        if not elen:
+            raise TypeError(f"{cls} is not a generic class")
+        if elen is _marker:
+            if not hasattr(cls, "__parameters__") or not cls.__parameters__:
+                raise TypeError(f"{cls} is not a generic class")
+            elen = len(cls.__parameters__)
+        alen = len(parameters)
+        if alen != elen:
+            expect_val = elen
+            if hasattr(cls, "__parameters__"):
+                parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
+                num_tv_tuples = sum(isinstance(p, TypeVarTuple) for p in parameters)
+                if (num_tv_tuples > 0) and (alen >= elen - num_tv_tuples):
+                    return
+
+                # deal with TypeVarLike defaults
+                # required TypeVarLikes cannot appear after a defaulted one.
+                if alen < elen:
+                    # since we validate TypeVarLike default in _collect_type_vars
+                    # or _collect_parameters we can safely check parameters[alen]
+                    if (
+                        getattr(parameters[alen], '__default__', NoDefault)
+                        is not NoDefault
+                    ):
+                        return
+
+                    num_default_tv = sum(getattr(p, '__default__', NoDefault)
+                                         is not NoDefault for p in parameters)
+
+                    elen -= num_default_tv
+
+                    expect_val = f"at least {elen}"
+
+            things = "arguments" if sys.version_info >= (3, 10) else "parameters"
+            raise TypeError(f"Too {'many' if alen > elen else 'few'} {things}"
+                            f" for {cls}; actual {alen}, expected {expect_val}")
+else:
+    # Python 3.11+
+
+    def _check_generic(cls, parameters, elen):
+        """Check correct count for parameters of a generic cls (internal helper).
+
+        This gives a nice error message in case of count mismatch.
+        """
+        if not elen:
+            raise TypeError(f"{cls} is not a generic class")
+        alen = len(parameters)
+        if alen != elen:
+            expect_val = elen
+            if hasattr(cls, "__parameters__"):
+                parameters = [p for p in cls.__parameters__ if not _is_unpack(p)]
+
+                # deal with TypeVarLike defaults
+                # required TypeVarLikes cannot appear after a defaulted one.
+                if alen < elen:
+                    # since we validate TypeVarLike default in _collect_type_vars
+                    # or _collect_parameters we can safely check parameters[alen]
+                    if (
+                        getattr(parameters[alen], '__default__', NoDefault)
+                        is not NoDefault
+                    ):
+                        return
+
+                    num_default_tv = sum(getattr(p, '__default__', NoDefault)
+                                         is not NoDefault for p in parameters)
+
+                    elen -= num_default_tv
+
+                    expect_val = f"at least {elen}"
+
+            raise TypeError(f"Too {'many' if alen > elen else 'few'} arguments"
+                            f" for {cls}; actual {alen}, expected {expect_val}")
+
+if not _PEP_696_IMPLEMENTED:
+    typing._check_generic = _check_generic
+
+
+def _has_generic_or_protocol_as_origin() -> bool:
+    try:
+        frame = sys._getframe(2)
+    # - Catch AttributeError: not all Python implementations have sys._getframe()
+    # - Catch ValueError: maybe we're called from an unexpected module
+    #   and the call stack isn't deep enough
+    except (AttributeError, ValueError):
+        return False  # err on the side of leniency
+    else:
+        # If we somehow get invoked from outside typing.py,
+        # also err on the side of leniency
+        if frame.f_globals.get("__name__") != "typing":
+            return False
+        origin = frame.f_locals.get("origin")
+        # Cannot use "in" because origin may be an object with a buggy __eq__ that
+        # throws an error.
+        return origin is typing.Generic or origin is Protocol or origin is typing.Protocol
+
+
+_TYPEVARTUPLE_TYPES = {TypeVarTuple, getattr(typing, "TypeVarTuple", None)}
+
+
+def _is_unpacked_typevartuple(x) -> bool:
+    if get_origin(x) is not Unpack:
+        return False
+    args = get_args(x)
+    return (
+        bool(args)
+        and len(args) == 1
+        and type(args[0]) in _TYPEVARTUPLE_TYPES
+    )
+
+
+# Python 3.11+ _collect_type_vars was renamed to _collect_parameters
+if hasattr(typing, '_collect_type_vars'):
+    def _collect_type_vars(types, typevar_types=None):
+        """Collect all type variable contained in types in order of
+        first appearance (lexicographic order). For example::
+
+            _collect_type_vars((T, List[S, T])) == (T, S)
+        """
+        if typevar_types is None:
+            typevar_types = typing.TypeVar
+        tvars = []
+
+        # A required TypeVarLike cannot appear after a TypeVarLike with a default
+        # if it was a direct call to `Generic[]` or `Protocol[]`
+        enforce_default_ordering = _has_generic_or_protocol_as_origin()
+        default_encountered = False
+
+        # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple
+        type_var_tuple_encountered = False
+
+        for t in types:
+            if _is_unpacked_typevartuple(t):
+                type_var_tuple_encountered = True
+            elif (
+                isinstance(t, typevar_types) and not isinstance(t, _UnpackAlias)
+                and t not in tvars
+            ):
+                if enforce_default_ordering:
+                    has_default = getattr(t, '__default__', NoDefault) is not NoDefault
+                    if has_default:
+                        if type_var_tuple_encountered:
+                            raise TypeError('Type parameter with a default'
+                                            ' follows TypeVarTuple')
+                        default_encountered = True
+                    elif default_encountered:
+                        raise TypeError(f'Type parameter {t!r} without a default'
+                                        ' follows type parameter with a default')
+
+                tvars.append(t)
+            if _should_collect_from_parameters(t):
+                tvars.extend([t for t in t.__parameters__ if t not in tvars])
+            elif isinstance(t, tuple):
+                # Collect nested type_vars
+                # tuple wrapped by  _prepare_paramspec_params(cls, params)
+                for x in t:
+                    for collected in _collect_type_vars([x]):
+                        if collected not in tvars:
+                            tvars.append(collected)
+        return tuple(tvars)
+
+    typing._collect_type_vars = _collect_type_vars
+else:
+    def _collect_parameters(args):
+        """Collect all type variables and parameter specifications in args
+        in order of first appearance (lexicographic order).
+
+        For example::
+
+            assert _collect_parameters((T, Callable[P, T])) == (T, P)
+        """
+        parameters = []
+
+        # A required TypeVarLike cannot appear after a TypeVarLike with default
+        # if it was a direct call to `Generic[]` or `Protocol[]`
+        enforce_default_ordering = _has_generic_or_protocol_as_origin()
+        default_encountered = False
+
+        # Also, a TypeVarLike with a default cannot appear after a TypeVarTuple
+        type_var_tuple_encountered = False
+
+        for t in args:
+            if isinstance(t, type):
+                # We don't want __parameters__ descriptor of a bare Python class.
+                pass
+            elif isinstance(t, tuple):
+                # `t` might be a tuple, when `ParamSpec` is substituted with
+                # `[T, int]`, or `[int, *Ts]`, etc.
+                for x in t:
+                    for collected in _collect_parameters([x]):
+                        if collected not in parameters:
+                            parameters.append(collected)
+            elif hasattr(t, '__typing_subst__'):
+                if t not in parameters:
+                    if enforce_default_ordering:
+                        has_default = (
+                            getattr(t, '__default__', NoDefault) is not NoDefault
+                        )
+
+                        if type_var_tuple_encountered and has_default:
+                            raise TypeError('Type parameter with a default'
+                                            ' follows TypeVarTuple')
+
+                        if has_default:
+                            default_encountered = True
+                        elif default_encountered:
+                            raise TypeError(f'Type parameter {t!r} without a default'
+                                            ' follows type parameter with a default')
+
+                    parameters.append(t)
+            else:
+                if _is_unpacked_typevartuple(t):
+                    type_var_tuple_encountered = True
+                for x in getattr(t, '__parameters__', ()):
+                    if x not in parameters:
+                        parameters.append(x)
+
+        return tuple(parameters)
+
+    if not _PEP_696_IMPLEMENTED:
+        typing._collect_parameters = _collect_parameters
+
+# Backport typing.NamedTuple as it exists in Python 3.13.
+# In 3.11, the ability to define generic `NamedTuple`s was supported.
+# This was explicitly disallowed in 3.9-3.10, and only half-worked in <=3.8.
+# On 3.12, we added __orig_bases__ to call-based NamedTuples
+# On 3.13, we deprecated kwargs-based NamedTuples
+if sys.version_info >= (3, 13):
+    NamedTuple = typing.NamedTuple
+else:
+    def _make_nmtuple(name, types, module, defaults=()):
+        fields = [n for n, t in types]
+        annotations = {n: typing._type_check(t, f"field {n} annotation must be a type")
+                       for n, t in types}
+        nm_tpl = collections.namedtuple(name, fields,
+                                        defaults=defaults, module=module)
+        nm_tpl.__annotations__ = nm_tpl.__new__.__annotations__ = annotations
+        return nm_tpl
+
+    _prohibited_namedtuple_fields = typing._prohibited
+    _special_namedtuple_fields = frozenset({'__module__', '__name__', '__annotations__'})
+
+    class _NamedTupleMeta(type):
+        def __new__(cls, typename, bases, ns):
+            assert _NamedTuple in bases
+            for base in bases:
+                if base is not _NamedTuple and base is not typing.Generic:
+                    raise TypeError(
+                        'can only inherit from a NamedTuple type and Generic')
+            bases = tuple(tuple if base is _NamedTuple else base for base in bases)
+            if "__annotations__" in ns:
+                types = ns["__annotations__"]
+            elif "__annotate__" in ns:
+                # TODO: Use inspect.VALUE here, and make the annotations lazily evaluated
+                types = ns["__annotate__"](1)
+            else:
+                types = {}
+            default_names = []
+            for field_name in types:
+                if field_name in ns:
+                    default_names.append(field_name)
+                elif default_names:
+                    raise TypeError(f"Non-default namedtuple field {field_name} "
+                                    f"cannot follow default field"
+                                    f"{'s' if len(default_names) > 1 else ''} "
+                                    f"{', '.join(default_names)}")
+            nm_tpl = _make_nmtuple(
+                typename, types.items(),
+                defaults=[ns[n] for n in default_names],
+                module=ns['__module__']
+            )
+            nm_tpl.__bases__ = bases
+            if typing.Generic in bases:
+                if hasattr(typing, '_generic_class_getitem'):  # 3.12+
+                    nm_tpl.__class_getitem__ = classmethod(typing._generic_class_getitem)
+                else:
+                    class_getitem = typing.Generic.__class_getitem__.__func__
+                    nm_tpl.__class_getitem__ = classmethod(class_getitem)
+            # update from user namespace without overriding special namedtuple attributes
+            for key, val in ns.items():
+                if key in _prohibited_namedtuple_fields:
+                    raise AttributeError("Cannot overwrite NamedTuple attribute " + key)
+                elif key not in _special_namedtuple_fields:
+                    if key not in nm_tpl._fields:
+                        setattr(nm_tpl, key, ns[key])
+                    try:
+                        set_name = type(val).__set_name__
+                    except AttributeError:
+                        pass
+                    else:
+                        try:
+                            set_name(val, nm_tpl, key)
+                        except BaseException as e:
+                            msg = (
+                                f"Error calling __set_name__ on {type(val).__name__!r} "
+                                f"instance {key!r} in {typename!r}"
+                            )
+                            # BaseException.add_note() existed on py311,
+                            # but the __set_name__ machinery didn't start
+                            # using add_note() until py312.
+                            # Making sure exceptions are raised in the same way
+                            # as in "normal" classes seems most important here.
+                            if sys.version_info >= (3, 12):
+                                e.add_note(msg)
+                                raise
+                            else:
+                                raise RuntimeError(msg) from e
+
+            if typing.Generic in bases:
+                nm_tpl.__init_subclass__()
+            return nm_tpl
+
+    _NamedTuple = type.__new__(_NamedTupleMeta, 'NamedTuple', (), {})
+
+    def _namedtuple_mro_entries(bases):
+        assert NamedTuple in bases
+        return (_NamedTuple,)
+
+    def NamedTuple(typename, fields=_marker, /, **kwargs):
+        """Typed version of namedtuple.
+
+        Usage::
+
+            class Employee(NamedTuple):
+                name: str
+                id: int
+
+        This is equivalent to::
+
+            Employee = collections.namedtuple('Employee', ['name', 'id'])
+
+        The resulting class has an extra __annotations__ attribute, giving a
+        dict that maps field names to types.  (The field names are also in
+        the _fields attribute, which is part of the namedtuple API.)
+        An alternative equivalent functional syntax is also accepted::
+
+            Employee = NamedTuple('Employee', [('name', str), ('id', int)])
+        """
+        if fields is _marker:
+            if kwargs:
+                deprecated_thing = "Creating NamedTuple classes using keyword arguments"
+                deprecation_msg = (
+                    "{name} is deprecated and will be disallowed in Python {remove}. "
+                    "Use the class-based or functional syntax instead."
+                )
+            else:
+                deprecated_thing = "Failing to pass a value for the 'fields' parameter"
+                example = f"`{typename} = NamedTuple({typename!r}, [])`"
+                deprecation_msg = (
+                    "{name} is deprecated and will be disallowed in Python {remove}. "
+                    "To create a NamedTuple class with 0 fields "
+                    "using the functional syntax, "
+                    "pass an empty list, e.g. "
+                ) + example + "."
+        elif fields is None:
+            if kwargs:
+                raise TypeError(
+                    "Cannot pass `None` as the 'fields' parameter "
+                    "and also specify fields using keyword arguments"
+                )
+            else:
+                deprecated_thing = "Passing `None` as the 'fields' parameter"
+                example = f"`{typename} = NamedTuple({typename!r}, [])`"
+                deprecation_msg = (
+                    "{name} is deprecated and will be disallowed in Python {remove}. "
+                    "To create a NamedTuple class with 0 fields "
+                    "using the functional syntax, "
+                    "pass an empty list, e.g. "
+                ) + example + "."
+        elif kwargs:
+            raise TypeError("Either list of fields or keywords"
+                            " can be provided to NamedTuple, not both")
+        if fields is _marker or fields is None:
+            warnings.warn(
+                deprecation_msg.format(name=deprecated_thing, remove="3.15"),
+                DeprecationWarning,
+                stacklevel=2,
+            )
+            fields = kwargs.items()
+        nt = _make_nmtuple(typename, fields, module=_caller())
+        nt.__orig_bases__ = (NamedTuple,)
+        return nt
+
+    NamedTuple.__mro_entries__ = _namedtuple_mro_entries
+
+
+if hasattr(collections.abc, "Buffer"):
+    Buffer = collections.abc.Buffer
+else:
+    class Buffer(abc.ABC):  # noqa: B024
+        """Base class for classes that implement the buffer protocol.
+
+        The buffer protocol allows Python objects to expose a low-level
+        memory buffer interface. Before Python 3.12, it is not possible
+        to implement the buffer protocol in pure Python code, or even
+        to check whether a class implements the buffer protocol. In
+        Python 3.12 and higher, the ``__buffer__`` method allows access
+        to the buffer protocol from Python code, and the
+        ``collections.abc.Buffer`` ABC allows checking whether a class
+        implements the buffer protocol.
+
+        To indicate support for the buffer protocol in earlier versions,
+        inherit from this ABC, either in a stub file or at runtime,
+        or use ABC registration. This ABC provides no methods, because
+        there is no Python-accessible methods shared by pre-3.12 buffer
+        classes. It is useful primarily for static checks.
+
+        """
+
+    # As a courtesy, register the most common stdlib buffer classes.
+    Buffer.register(memoryview)
+    Buffer.register(bytearray)
+    Buffer.register(bytes)
+
+
+# Backport of types.get_original_bases, available on 3.12+ in CPython
+if hasattr(_types, "get_original_bases"):
+    get_original_bases = _types.get_original_bases
+else:
+    def get_original_bases(cls, /):
+        """Return the class's "original" bases prior to modification by `__mro_entries__`.
+
+        Examples::
+
+            from typing import TypeVar, Generic
+            from typing_extensions import NamedTuple, TypedDict
+
+            T = TypeVar("T")
+            class Foo(Generic[T]): ...
+            class Bar(Foo[int], float): ...
+            class Baz(list[str]): ...
+            Eggs = NamedTuple("Eggs", [("a", int), ("b", str)])
+            Spam = TypedDict("Spam", {"a": int, "b": str})
+
+            assert get_original_bases(Bar) == (Foo[int], float)
+            assert get_original_bases(Baz) == (list[str],)
+            assert get_original_bases(Eggs) == (NamedTuple,)
+            assert get_original_bases(Spam) == (TypedDict,)
+            assert get_original_bases(int) == (object,)
+        """
+        try:
+            return cls.__dict__.get("__orig_bases__", cls.__bases__)
+        except AttributeError:
+            raise TypeError(
+                f'Expected an instance of type, not {type(cls).__name__!r}'
+            ) from None
+
+
+# NewType is a class on Python 3.10+, making it pickleable
+# The error message for subclassing instances of NewType was improved on 3.11+
+if sys.version_info >= (3, 11):
+    NewType = typing.NewType
+else:
+    class NewType:
+        """NewType creates simple unique types with almost zero
+        runtime overhead. NewType(name, tp) is considered a subtype of tp
+        by static type checkers. At runtime, NewType(name, tp) returns
+        a dummy callable that simply returns its argument. Usage::
+            UserId = NewType('UserId', int)
+            def name_by_id(user_id: UserId) -> str:
+                ...
+            UserId('user')          # Fails type check
+            name_by_id(42)          # Fails type check
+            name_by_id(UserId(42))  # OK
+            num = UserId(5) + 1     # type: int
+        """
+
+        def __call__(self, obj, /):
+            return obj
+
+        def __init__(self, name, tp):
+            self.__qualname__ = name
+            if '.' in name:
+                name = name.rpartition('.')[-1]
+            self.__name__ = name
+            self.__supertype__ = tp
+            def_mod = _caller()
+            if def_mod != 'typing_extensions':
+                self.__module__ = def_mod
+
+        def __mro_entries__(self, bases):
+            # We defined __mro_entries__ to get a better error message
+            # if a user attempts to subclass a NewType instance. bpo-46170
+            supercls_name = self.__name__
+
+            class Dummy:
+                def __init_subclass__(cls):
+                    subcls_name = cls.__name__
+                    raise TypeError(
+                        f"Cannot subclass an instance of NewType. "
+                        f"Perhaps you were looking for: "
+                        f"`{subcls_name} = NewType({subcls_name!r}, {supercls_name})`"
+                    )
+
+            return (Dummy,)
+
+        def __repr__(self):
+            return f'{self.__module__}.{self.__qualname__}'
+
+        def __reduce__(self):
+            return self.__qualname__
+
+        if sys.version_info >= (3, 10):
+            # PEP 604 methods
+            # It doesn't make sense to have these methods on Python <3.10
+
+            def __or__(self, other):
+                return typing.Union[self, other]
+
+            def __ror__(self, other):
+                return typing.Union[other, self]
+
+
+if sys.version_info >= (3, 14):
+    TypeAliasType = typing.TypeAliasType
+# <=3.13
+else:
+    if sys.version_info >= (3, 12):
+        # 3.12-3.13
+        def _is_unionable(obj):
+            """Corresponds to is_unionable() in unionobject.c in CPython."""
+            return obj is None or isinstance(obj, (
+                type,
+                _types.GenericAlias,
+                _types.UnionType,
+                typing.TypeAliasType,
+                TypeAliasType,
+            ))
+    else:
+        # <=3.11
+        def _is_unionable(obj):
+            """Corresponds to is_unionable() in unionobject.c in CPython."""
+            return obj is None or isinstance(obj, (
+                type,
+                _types.GenericAlias,
+                _types.UnionType,
+                TypeAliasType,
+            ))
+
+    if sys.version_info < (3, 10):
+        # Copied and pasted from https://github.com/python/cpython/blob/986a4e1b6fcae7fe7a1d0a26aea446107dd58dd2/Objects/genericaliasobject.c#L568-L582,
+        # so that we emulate the behaviour of `types.GenericAlias`
+        # on the latest versions of CPython
+        _ATTRIBUTE_DELEGATION_EXCLUSIONS = frozenset({
+            "__class__",
+            "__bases__",
+            "__origin__",
+            "__args__",
+            "__unpacked__",
+            "__parameters__",
+            "__typing_unpacked_tuple_args__",
+            "__mro_entries__",
+            "__reduce_ex__",
+            "__reduce__",
+            "__copy__",
+            "__deepcopy__",
+        })
+
+        class _TypeAliasGenericAlias(typing._GenericAlias, _root=True):
+            def __getattr__(self, attr):
+                if attr in _ATTRIBUTE_DELEGATION_EXCLUSIONS:
+                    return object.__getattr__(self, attr)
+                return getattr(self.__origin__, attr)
+
+
+    class TypeAliasType:
+        """Create named, parameterized type aliases.
+
+        This provides a backport of the new `type` statement in Python 3.12:
+
+            type ListOrSet[T] = list[T] | set[T]
+
+        is equivalent to:
+
+            T = TypeVar("T")
+            ListOrSet = TypeAliasType("ListOrSet", list[T] | set[T], type_params=(T,))
+
+        The name ListOrSet can then be used as an alias for the type it refers to.
+
+        The type_params argument should contain all the type parameters used
+        in the value of the type alias. If the alias is not generic, this
+        argument is omitted.
+
+        Static type checkers should only support type aliases declared using
+        TypeAliasType that follow these rules:
+
+        - The first argument (the name) must be a string literal.
+        - The TypeAliasType instance must be immediately assigned to a variable
+          of the same name. (For example, 'X = TypeAliasType("Y", int)' is invalid,
+          as is 'X, Y = TypeAliasType("X", int), TypeAliasType("Y", int)').
+
+        """
+
+        def __init__(self, name: str, value, *, type_params=()):
+            if not isinstance(name, str):
+                raise TypeError("TypeAliasType name must be a string")
+            if not isinstance(type_params, tuple):
+                raise TypeError("type_params must be a tuple")
+            self.__value__ = value
+            self.__type_params__ = type_params
+
+            default_value_encountered = False
+            parameters = []
+            for type_param in type_params:
+                if (
+                    not isinstance(type_param, (TypeVar, TypeVarTuple, ParamSpec))
+                    # <=3.11
+                    # Unpack Backport passes isinstance(type_param, TypeVar)
+                    or _is_unpack(type_param)
+                ):
+                    raise TypeError(f"Expected a type param, got {type_param!r}")
+                has_default = (
+                    getattr(type_param, '__default__', NoDefault) is not NoDefault
+                )
+                if default_value_encountered and not has_default:
+                    raise TypeError(f"non-default type parameter '{type_param!r}'"
+                                    " follows default type parameter")
+                if has_default:
+                    default_value_encountered = True
+                if isinstance(type_param, TypeVarTuple):
+                    parameters.extend(type_param)
+                else:
+                    parameters.append(type_param)
+            self.__parameters__ = tuple(parameters)
+            def_mod = _caller()
+            if def_mod != 'typing_extensions':
+                self.__module__ = def_mod
+            # Setting this attribute closes the TypeAliasType from further modification
+            self.__name__ = name
+
+        def __setattr__(self, name: str, value: object, /) -> None:
+            if hasattr(self, "__name__"):
+                self._raise_attribute_error(name)
+            super().__setattr__(name, value)
+
+        def __delattr__(self, name: str, /) -> Never:
+            self._raise_attribute_error(name)
+
+        def _raise_attribute_error(self, name: str) -> Never:
+            # Match the Python 3.12 error messages exactly
+            if name == "__name__":
+                raise AttributeError("readonly attribute")
+            elif name in {"__value__", "__type_params__", "__parameters__", "__module__"}:
+                raise AttributeError(
+                    f"attribute '{name}' of 'typing.TypeAliasType' objects "
+                    "is not writable"
+                )
+            else:
+                raise AttributeError(
+                    f"'typing.TypeAliasType' object has no attribute '{name}'"
+                )
+
+        def __repr__(self) -> str:
+            return self.__name__
+
+        if sys.version_info < (3, 11):
+            def _check_single_param(self, param, recursion=0):
+                # Allow [], [int], [int, str], [int, ...], [int, T]
+                if param is ...:
+                    return ...
+                if param is None:
+                    return None
+                # Note in <= 3.9 _ConcatenateGenericAlias inherits from list
+                if isinstance(param, list) and recursion == 0:
+                    return [self._check_single_param(arg, recursion+1)
+                            for arg in param]
+                return typing._type_check(
+                        param, f'Subscripting {self.__name__} requires a type.'
+                    )
+
+        def _check_parameters(self, parameters):
+            if sys.version_info < (3, 11):
+                return tuple(
+                    self._check_single_param(item)
+                    for item in parameters
+                )
+            return tuple(typing._type_check(
+                        item, f'Subscripting {self.__name__} requires a type.'
+                    )
+                    for item in parameters
+            )
+
+        def __getitem__(self, parameters):
+            if not self.__type_params__:
+                raise TypeError("Only generic type aliases are subscriptable")
+            if not isinstance(parameters, tuple):
+                parameters = (parameters,)
+            # Using 3.9 here will create problems with Concatenate
+            if sys.version_info >= (3, 10):
+                return _types.GenericAlias(self, parameters)
+            type_vars = _collect_type_vars(parameters)
+            parameters = self._check_parameters(parameters)
+            alias = _TypeAliasGenericAlias(self, parameters)
+            # alias.__parameters__ is not complete if Concatenate is present
+            # as it is converted to a list from which no parameters are extracted.
+            if alias.__parameters__ != type_vars:
+                alias.__parameters__ = type_vars
+            return alias
+
+        def __reduce__(self):
+            return self.__name__
+
+        def __init_subclass__(cls, *args, **kwargs):
+            raise TypeError(
+                "type 'typing_extensions.TypeAliasType' is not an acceptable base type"
+            )
+
+        # The presence of this method convinces typing._type_check
+        # that TypeAliasTypes are types.
+        def __call__(self):
+            raise TypeError("Type alias is not callable")
+
+        if sys.version_info >= (3, 10):
+            def __or__(self, right):
+                # For forward compatibility with 3.12, reject Unions
+                # that are not accepted by the built-in Union.
+                if not _is_unionable(right):
+                    return NotImplemented
+                return typing.Union[self, right]
+
+            def __ror__(self, left):
+                if not _is_unionable(left):
+                    return NotImplemented
+                return typing.Union[left, self]
+
+
+if hasattr(typing, "is_protocol"):
+    is_protocol = typing.is_protocol
+    get_protocol_members = typing.get_protocol_members
+else:
+    def is_protocol(tp: type, /) -> bool:
+        """Return True if the given type is a Protocol.
+
+        Example::
+
+            >>> from typing_extensions import Protocol, is_protocol
+            >>> class P(Protocol):
+            ...     def a(self) -> str: ...
+            ...     b: int
+            >>> is_protocol(P)
+            True
+            >>> is_protocol(int)
+            False
+        """
+        return (
+            isinstance(tp, type)
+            and getattr(tp, '_is_protocol', False)
+            and tp is not Protocol
+            and tp is not typing.Protocol
+        )
+
+    def get_protocol_members(tp: type, /) -> typing.FrozenSet[str]:
+        """Return the set of members defined in a Protocol.
+
+        Example::
+
+            >>> from typing_extensions import Protocol, get_protocol_members
+            >>> class P(Protocol):
+            ...     def a(self) -> str: ...
+            ...     b: int
+            >>> get_protocol_members(P)
+            frozenset({'a', 'b'})
+
+        Raise a TypeError for arguments that are not Protocols.
+        """
+        if not is_protocol(tp):
+            raise TypeError(f'{tp!r} is not a Protocol')
+        if hasattr(tp, '__protocol_attrs__'):
+            return frozenset(tp.__protocol_attrs__)
+        return frozenset(_get_protocol_attrs(tp))
+
+
+if hasattr(typing, "Doc"):
+    Doc = typing.Doc
+else:
+    class Doc:
+        """Define the documentation of a type annotation using ``Annotated``, to be
+         used in class attributes, function and method parameters, return values,
+         and variables.
+
+        The value should be a positional-only string literal to allow static tools
+        like editors and documentation generators to use it.
+
+        This complements docstrings.
+
+        The string value passed is available in the attribute ``documentation``.
+
+        Example::
+
+            >>> from typing_extensions import Annotated, Doc
+            >>> def hi(to: Annotated[str, Doc("Who to say hi to")]) -> None: ...
+        """
+        def __init__(self, documentation: str, /) -> None:
+            self.documentation = documentation
+
+        def __repr__(self) -> str:
+            return f"Doc({self.documentation!r})"
+
+        def __hash__(self) -> int:
+            return hash(self.documentation)
+
+        def __eq__(self, other: object) -> bool:
+            if not isinstance(other, Doc):
+                return NotImplemented
+            return self.documentation == other.documentation
+
+
+_CapsuleType = getattr(_types, "CapsuleType", None)
+
+if _CapsuleType is None:
+    try:
+        import _socket
+    except ImportError:
+        pass
+    else:
+        _CAPI = getattr(_socket, "CAPI", None)
+        if _CAPI is not None:
+            _CapsuleType = type(_CAPI)
+
+if _CapsuleType is not None:
+    CapsuleType = _CapsuleType
+    __all__.append("CapsuleType")
+
+
+if sys.version_info >= (3,14):
+    from annotationlib import Format, get_annotations
+else:
+    class Format(enum.IntEnum):
+        VALUE = 1
+        VALUE_WITH_FAKE_GLOBALS = 2
+        FORWARDREF = 3
+        STRING = 4
+
+    def get_annotations(obj, *, globals=None, locals=None, eval_str=False,
+                        format=Format.VALUE):
+        """Compute the annotations dict for an object.
+
+        obj may be a callable, class, or module.
+        Passing in an object of any other type raises TypeError.
+
+        Returns a dict.  get_annotations() returns a new dict every time
+        it's called; calling it twice on the same object will return two
+        different but equivalent dicts.
+
+        This is a backport of `inspect.get_annotations`, which has been
+        in the standard library since Python 3.10. See the standard library
+        documentation for more:
+
+            https://docs.python.org/3/library/inspect.html#inspect.get_annotations
+
+        This backport adds the *format* argument introduced by PEP 649. The
+        three formats supported are:
+        * VALUE: the annotations are returned as-is. This is the default and
+          it is compatible with the behavior on previous Python versions.
+        * FORWARDREF: return annotations as-is if possible, but replace any
+          undefined names with ForwardRef objects. The implementation proposed by
+          PEP 649 relies on language changes that cannot be backported; the
+          typing-extensions implementation simply returns the same result as VALUE.
+        * STRING: return annotations as strings, in a format close to the original
+          source. Again, this behavior cannot be replicated directly in a backport.
+          As an approximation, typing-extensions retrieves the annotations under
+          VALUE semantics and then stringifies them.
+
+        The purpose of this backport is to allow users who would like to use
+        FORWARDREF or STRING semantics once PEP 649 is implemented, but who also
+        want to support earlier Python versions, to simply write:
+
+            typing_extensions.get_annotations(obj, format=Format.FORWARDREF)
+
+        """
+        format = Format(format)
+        if format is Format.VALUE_WITH_FAKE_GLOBALS:
+            raise ValueError(
+                "The VALUE_WITH_FAKE_GLOBALS format is for internal use only"
+            )
+
+        if eval_str and format is not Format.VALUE:
+            raise ValueError("eval_str=True is only supported with format=Format.VALUE")
+
+        if isinstance(obj, type):
+            # class
+            obj_dict = getattr(obj, '__dict__', None)
+            if obj_dict and hasattr(obj_dict, 'get'):
+                ann = obj_dict.get('__annotations__', None)
+                if isinstance(ann, _types.GetSetDescriptorType):
+                    ann = None
+            else:
+                ann = None
+
+            obj_globals = None
+            module_name = getattr(obj, '__module__', None)
+            if module_name:
+                module = sys.modules.get(module_name, None)
+                if module:
+                    obj_globals = getattr(module, '__dict__', None)
+            obj_locals = dict(vars(obj))
+            unwrap = obj
+        elif isinstance(obj, _types.ModuleType):
+            # module
+            ann = getattr(obj, '__annotations__', None)
+            obj_globals = obj.__dict__
+            obj_locals = None
+            unwrap = None
+        elif callable(obj):
+            # this includes types.Function, types.BuiltinFunctionType,
+            # types.BuiltinMethodType, functools.partial, functools.singledispatch,
+            # "class funclike" from Lib/test/test_inspect... on and on it goes.
+            ann = getattr(obj, '__annotations__', None)
+            obj_globals = getattr(obj, '__globals__', None)
+            obj_locals = None
+            unwrap = obj
+        elif hasattr(obj, '__annotations__'):
+            ann = obj.__annotations__
+            obj_globals = obj_locals = unwrap = None
+        else:
+            raise TypeError(f"{obj!r} is not a module, class, or callable.")
+
+        if ann is None:
+            return {}
+
+        if not isinstance(ann, dict):
+            raise ValueError(f"{obj!r}.__annotations__ is neither a dict nor None")
+
+        if not ann:
+            return {}
+
+        if not eval_str:
+            if format is Format.STRING:
+                return {
+                    key: value if isinstance(value, str) else typing._type_repr(value)
+                    for key, value in ann.items()
+                }
+            return dict(ann)
+
+        if unwrap is not None:
+            while True:
+                if hasattr(unwrap, '__wrapped__'):
+                    unwrap = unwrap.__wrapped__
+                    continue
+                if isinstance(unwrap, functools.partial):
+                    unwrap = unwrap.func
+                    continue
+                break
+            if hasattr(unwrap, "__globals__"):
+                obj_globals = unwrap.__globals__
+
+        if globals is None:
+            globals = obj_globals
+        if locals is None:
+            locals = obj_locals or {}
+
+        # "Inject" type parameters into the local namespace
+        # (unless they are shadowed by assignments *in* the local namespace),
+        # as a way of emulating annotation scopes when calling `eval()`
+        if type_params := getattr(obj, "__type_params__", ()):
+            locals = {param.__name__: param for param in type_params} | locals
+
+        return_value = {key:
+            value if not isinstance(value, str) else eval(value, globals, locals)
+            for key, value in ann.items() }
+        return return_value
+
+
+if hasattr(typing, "evaluate_forward_ref"):
+    evaluate_forward_ref = typing.evaluate_forward_ref
+else:
+    # Implements annotationlib.ForwardRef.evaluate
+    def _eval_with_owner(
+        forward_ref, *, owner=None, globals=None, locals=None, type_params=None
+    ):
+        if forward_ref.__forward_evaluated__:
+            return forward_ref.__forward_value__
+        if getattr(forward_ref, "__cell__", None) is not None:
+            try:
+                value = forward_ref.__cell__.cell_contents
+            except ValueError:
+                pass
+            else:
+                forward_ref.__forward_evaluated__ = True
+                forward_ref.__forward_value__ = value
+                return value
+        if owner is None:
+            owner = getattr(forward_ref, "__owner__", None)
+
+        if (
+            globals is None
+            and getattr(forward_ref, "__forward_module__", None) is not None
+        ):
+            globals = getattr(
+                sys.modules.get(forward_ref.__forward_module__, None), "__dict__", None
+            )
+        if globals is None:
+            globals = getattr(forward_ref, "__globals__", None)
+        if globals is None:
+            if isinstance(owner, type):
+                module_name = getattr(owner, "__module__", None)
+                if module_name:
+                    module = sys.modules.get(module_name, None)
+                    if module:
+                        globals = getattr(module, "__dict__", None)
+            elif isinstance(owner, _types.ModuleType):
+                globals = getattr(owner, "__dict__", None)
+            elif callable(owner):
+                globals = getattr(owner, "__globals__", None)
+
+        # If we pass None to eval() below, the globals of this module are used.
+        if globals is None:
+            globals = {}
+
+        if locals is None:
+            locals = {}
+            if isinstance(owner, type):
+                locals.update(vars(owner))
+
+        if type_params is None and owner is not None:
+            # "Inject" type parameters into the local namespace
+            # (unless they are shadowed by assignments *in* the local namespace),
+            # as a way of emulating annotation scopes when calling `eval()`
+            type_params = getattr(owner, "__type_params__", None)
+
+        # type parameters require some special handling,
+        # as they exist in their own scope
+        # but `eval()` does not have a dedicated parameter for that scope.
+        # For classes, names in type parameter scopes should override
+        # names in the global scope (which here are called `localns`!),
+        # but should in turn be overridden by names in the class scope
+        # (which here are called `globalns`!)
+        if type_params is not None:
+            globals = dict(globals)
+            locals = dict(locals)
+            for param in type_params:
+                param_name = param.__name__
+                if (
+                    _FORWARD_REF_HAS_CLASS and not forward_ref.__forward_is_class__
+                ) or param_name not in globals:
+                    globals[param_name] = param
+                    locals.pop(param_name, None)
+
+        arg = forward_ref.__forward_arg__
+        if arg.isidentifier() and not keyword.iskeyword(arg):
+            if arg in locals:
+                value = locals[arg]
+            elif arg in globals:
+                value = globals[arg]
+            elif hasattr(builtins, arg):
+                return getattr(builtins, arg)
+            else:
+                raise NameError(arg)
+        else:
+            code = forward_ref.__forward_code__
+            value = eval(code, globals, locals)
+        forward_ref.__forward_evaluated__ = True
+        forward_ref.__forward_value__ = value
+        return value
+
+    def evaluate_forward_ref(
+        forward_ref,
+        *,
+        owner=None,
+        globals=None,
+        locals=None,
+        type_params=None,
+        format=None,
+        _recursive_guard=frozenset(),
+    ):
+        """Evaluate a forward reference as a type hint.
+
+        This is similar to calling the ForwardRef.evaluate() method,
+        but unlike that method, evaluate_forward_ref() also:
+
+        * Recursively evaluates forward references nested within the type hint.
+        * Rejects certain objects that are not valid type hints.
+        * Replaces type hints that evaluate to None with types.NoneType.
+        * Supports the *FORWARDREF* and *STRING* formats.
+
+        *forward_ref* must be an instance of ForwardRef. *owner*, if given,
+        should be the object that holds the annotations that the forward reference
+        derived from, such as a module, class object, or function. It is used to
+        infer the namespaces to use for looking up names. *globals* and *locals*
+        can also be explicitly given to provide the global and local namespaces.
+        *type_params* is a tuple of type parameters that are in scope when
+        evaluating the forward reference. This parameter must be provided (though
+        it may be an empty tuple) if *owner* is not given and the forward reference
+        does not already have an owner set. *format* specifies the format of the
+        annotation and is a member of the annotationlib.Format enum.
+
+        """
+        if format == Format.STRING:
+            return forward_ref.__forward_arg__
+        if forward_ref.__forward_arg__ in _recursive_guard:
+            return forward_ref
+
+        # Evaluate the forward reference
+        try:
+            value = _eval_with_owner(
+                forward_ref,
+                owner=owner,
+                globals=globals,
+                locals=locals,
+                type_params=type_params,
+            )
+        except NameError:
+            if format == Format.FORWARDREF:
+                return forward_ref
+            else:
+                raise
+
+        if isinstance(value, str):
+            value = ForwardRef(value)
+
+        # Recursively evaluate the type
+        if isinstance(value, ForwardRef):
+            if getattr(value, "__forward_module__", True) is not None:
+                globals = None
+            return evaluate_forward_ref(
+                value,
+                globals=globals,
+                locals=locals,
+                 type_params=type_params, owner=owner,
+                _recursive_guard=_recursive_guard, format=format
+            )
+        if sys.version_info < (3, 12, 5) and type_params:
+            # Make use of type_params
+            locals = dict(locals) if locals else {}
+            for tvar in type_params:
+                if tvar.__name__ not in locals:  # lets not overwrite something present
+                    locals[tvar.__name__] = tvar
+        if sys.version_info < (3, 12, 5):
+            return typing._eval_type(
+                value,
+                globals,
+                locals,
+                recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+            )
+        else:
+            return typing._eval_type(
+                value,
+                globals,
+                locals,
+                type_params,
+                recursive_guard=_recursive_guard | {forward_ref.__forward_arg__},
+            )
+
+
+class Sentinel:
+    """Create a unique sentinel object.
+
+    *name* should be the name of the variable to which the return value shall be assigned.
+
+    *repr*, if supplied, will be used for the repr of the sentinel object.
+    If not provided, "" will be used.
+    """
+
+    def __init__(
+        self,
+        name: str,
+        repr: typing.Optional[str] = None,
+    ):
+        self._name = name
+        self._repr = repr if repr is not None else f'<{name}>'
+
+    def __repr__(self):
+        return self._repr
+
+    if sys.version_info < (3, 11):
+        # The presence of this method convinces typing._type_check
+        # that Sentinels are types.
+        def __call__(self, *args, **kwargs):
+            raise TypeError(f"{type(self).__name__!r} object is not callable")
+
+    if sys.version_info >= (3, 10):
+        def __or__(self, other):
+            return typing.Union[self, other]
+
+        def __ror__(self, other):
+            return typing.Union[other, self]
+
+    def __getstate__(self):
+        raise TypeError(f"Cannot pickle {type(self).__name__!r} object")
+
+
+# Aliases for items that are in typing in all supported versions.
+# We use hasattr() checks so this library will continue to import on
+# future versions of Python that may remove these names.
+_typing_names = [
+    "AbstractSet",
+    "AnyStr",
+    "BinaryIO",
+    "Callable",
+    "Collection",
+    "Container",
+    "Dict",
+    "FrozenSet",
+    "Hashable",
+    "IO",
+    "ItemsView",
+    "Iterable",
+    "Iterator",
+    "KeysView",
+    "List",
+    "Mapping",
+    "MappingView",
+    "Match",
+    "MutableMapping",
+    "MutableSequence",
+    "MutableSet",
+    "Optional",
+    "Pattern",
+    "Reversible",
+    "Sequence",
+    "Set",
+    "Sized",
+    "TextIO",
+    "Tuple",
+    "Union",
+    "ValuesView",
+    "cast",
+    "no_type_check",
+    "no_type_check_decorator",
+    # This is private, but it was defined by typing_extensions for a long time
+    # and some users rely on it.
+    "_AnnotatedAlias",
+]
+globals().update(
+    {name: getattr(typing, name) for name in _typing_names if hasattr(typing, name)}
+)
+# These are defined unconditionally because they are used in
+# typing-extensions itself.
+Generic = typing.Generic
+ForwardRef = typing.ForwardRef
+Annotated = typing.Annotated
diff --git a/server/noxfile.py b/server/noxfile.py
index 82f0645..45e0c3c 100644
--- a/server/noxfile.py
+++ b/server/noxfile.py
@@ -1,3 +1,5 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
 """All the action we need during build"""
 
 import json
@@ -9,13 +11,10 @@ from typing import List
 import nox  # pylint: disable=import-error
 
 
-@nox.session()
-def setup(session: nox.Session) -> None:
-    """Sets up the template for development."""
-
+def _install_bundle(session: nox.Session) -> None:
     session.install(
         "-t",
-        "./bundled/libs",
+        "./libs",
         "--no-cache-dir",
         "--implementation",
         "py",
@@ -24,3 +23,157 @@ def setup(session: nox.Session) -> None:
         "-r",
         "./requirements.txt",
     )
+
+
+def _check_files(names: List[str]) -> None:
+    root_dir = pathlib.Path(__file__).parent
+    for name in names:
+        file_path = root_dir / name
+        lines: List[str] = file_path.read_text().splitlines()
+        if any(line for line in lines if line.startswith("# TODO:")):
+            raise Exception(f"Please update {os.fspath(file_path)}.")
+
+
+def _update_pip_packages(session: nox.Session) -> None:
+    session.install("wheel", "pip-tools==7.3.0")
+    session.run(
+        "pip-compile",
+        "--generate-hashes",
+        "--resolver=backtracking",
+        "--upgrade",
+        "./requirements.in",
+    )
+    session.run(
+        "pip-compile",
+        "--generate-hashes",
+        "--resolver=backtracking",
+        "--upgrade",
+        "./src/test/python_tests/requirements.in",
+    )
+
+
+def _get_package_data(package):
+    json_uri = f"https://registry.npmjs.org/{package}"
+    with url_lib.urlopen(json_uri) as response:
+        return json.loads(response.read())
+
+
+def _update_npm_packages(session: nox.Session) -> None:
+    pinned = {
+        "vscode-languageclient",
+        "@types/vscode",
+        "@types/node",
+    }
+    package_json_path = pathlib.Path(__file__).parent / "package.json"
+    package_json = json.loads(package_json_path.read_text(encoding="utf-8"))
+
+    for package in package_json["dependencies"]:
+        if package not in pinned:
+            data = _get_package_data(package)
+            latest = "^" + data["dist-tags"]["latest"]
+            package_json["dependencies"][package] = latest
+
+    for package in package_json["devDependencies"]:
+        if package not in pinned:
+            data = _get_package_data(package)
+            latest = "^" + data["dist-tags"]["latest"]
+            package_json["devDependencies"][package] = latest
+
+    # Ensure engine matches the package
+    if (
+        package_json["engines"]["vscode"]
+        != package_json["devDependencies"]["@types/vscode"]
+    ):
+        print(
+            "Please check VS Code engine version and @types/vscode version in package.json."
+        )
+
+    new_package_json = json.dumps(package_json, indent=4)
+    # JSON dumps uses \n for line ending on all platforms by default
+    if not new_package_json.endswith("\n"):
+        new_package_json += "\n"
+    package_json_path.write_text(new_package_json, encoding="utf-8")
+    session.run("npm", "install", external=True)
+
+
+def _setup_template_environment(session: nox.Session) -> None:
+    session.install("wheel", "pip-tools")
+    session.run(
+        "pip-compile",
+        "--generate-hashes",
+        "--resolver=backtracking",
+        "--upgrade",
+        "./requirements.in",
+    )
+    # session.run(
+    #     "pip-compile",
+    #     "--generate-hashes",
+    #     "--resolver=backtracking",
+    #     "--upgrade",
+    #     "./src/test/python_tests/requirements.in",
+    # )
+    _install_bundle(session)
+
+
+@nox.session()
+def setup(session: nox.Session) -> None:
+    """Sets up the template for development."""
+    session.install("pip<24")
+    _setup_template_environment(session)
+
+
+@nox.session()
+def tests(session: nox.Session) -> None:
+    """Runs all the tests for the extension."""
+    session.install("-r", "src/test/python_tests/requirements.txt")
+    session.run("pytest", "src/test/python_tests")
+
+
+@nox.session()
+def lint(session: nox.Session) -> None:
+    """Runs linter and formatter checks on python files."""
+    session.install("-r", "./requirements.txt")
+    session.install("-r", "src/test/python_tests/requirements.txt")
+
+    session.install("pylint")
+    session.run("pylint", "-d", "W0511", "./bundled/tool")
+    session.run(
+        "pylint",
+        "-d",
+        "W0511",
+        "--ignore=./src/test/python_tests/test_data",
+        "./src/test/python_tests",
+    )
+    session.run("pylint", "-d", "W0511", "noxfile.py")
+
+    # check formatting using black
+    session.install("black")
+    session.run("black", "--check", "./bundled/tool")
+    session.run("black", "--check", "./src/test/python_tests")
+    session.run("black", "--check", "noxfile.py")
+
+    # check import sorting using isort
+    session.install("isort")
+    session.run("isort", "--check", "./bundled/tool")
+    session.run("isort", "--check", "./src/test/python_tests")
+    session.run("isort", "--check", "noxfile.py")
+
+    # check typescript code
+    session.run("npm", "run", "lint", external=True)
+
+
+@nox.session()
+def build_package(session: nox.Session) -> None:
+    """Builds VSIX package for publishing."""
+    _check_files(["README.md", "LICENSE", "SECURITY.md", "SUPPORT.md"])
+    _setup_template_environment(session)
+    session.run("npm", "install", external=True)
+    session.run("npm", "run", "vsce-package", external=True)
+
+
+@nox.session()
+def update_packages(session: nox.Session) -> None:
+    """Update pip and npm packages."""
+    session.install("wheel", "pip-tools")
+    _update_pip_packages(session)
+    _update_npm_packages(session)
diff --git a/server/pyproject.toml b/server/pyproject.toml
deleted file mode 100644
index b54b66b..0000000
--- a/server/pyproject.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-[project]
-name = "server"
-version = "0.1.0"
-description = "Add your description here"
-readme = "README.md"
-requires-python = ">=3.11"
-dependencies = [
-    "pygls","ply"
-]
diff --git a/server/requirements.in b/server/requirements.in
new file mode 100644
index 0000000..fe5e44a
--- /dev/null
+++ b/server/requirements.in
@@ -0,0 +1,16 @@
+# This file is used to generate requirements.txt.
+# NOTE:
+# Use Python 3.8 or greater which ever is the minimum version of the python
+# you plan on supporting when creating the environment or using pip-tools.
+# Only run the commands below to manully upgrade packages in requirements.txt:
+# 1) python -m pip install pip-tools
+# 2) pip-compile --generate-hashes --resolver=backtracking --upgrade ./requirements.in
+# If you are using nox commands to setup or build package you don't need to
+# run the above commands manually.
+
+# Required packages
+pygls
+packaging
+
+# TODO: Add your tool here
+ply
\ No newline at end of file
diff --git a/server/requirements.txt b/server/requirements.txt
new file mode 100644
index 0000000..cadc061
--- /dev/null
+++ b/server/requirements.txt
@@ -0,0 +1,38 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+#    pip-compile --generate-hashes ./requirements.in
+#
+attrs==25.3.0 \
+    --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \
+    --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b
+    # via
+    #   cattrs
+    #   lsprotocol
+cattrs==25.1.1 \
+    --hash=sha256:1b40b2d3402af7be79a7e7e097a9b4cd16d4c06e6d526644b0b26a063a1cc064 \
+    --hash=sha256:c914b734e0f2d59e5b720d145ee010f1fd9a13ee93900922a2f3f9d593b8382c
+    # via
+    #   lsprotocol
+    #   pygls
+lsprotocol==2023.0.1 \
+    --hash=sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2 \
+    --hash=sha256:cc5c15130d2403c18b734304339e51242d3018a05c4f7d0f198ad6e0cd21861d
+    # via pygls
+packaging==25.0 \
+    --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
+    --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
+    # via -r ./requirements.in
+ply==3.11 \
+    --hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \
+    --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce
+    # via -r ./requirements.in
+pygls==1.3.1 \
+    --hash=sha256:140edceefa0da0e9b3c533547c892a42a7d2fd9217ae848c330c53d266a55018 \
+    --hash=sha256:6e00f11efc56321bdeb6eac04f6d86131f654c7d49124344a9ebb968da3dd91e
+    # via -r ./requirements.in
+typing-extensions==4.14.1 \
+    --hash=sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36 \
+    --hash=sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76
+    # via cattrs
diff --git a/server/src/__init__.py b/server/src/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/server/src/common/completion_list.json b/server/src/common/completion_list.json
new file mode 100644
index 0000000..d67c328
--- /dev/null
+++ b/server/src/common/completion_list.json
@@ -0,0 +1,16 @@
+{
+    "tcl": {
+        "keywords": [
+            "proc",
+            "puts",
+            "if"
+        ]
+    },
+    "MOM_procs": [
+        "MOM_output_literal",
+        "MOM_ask_ude_info",
+        "MOM_suppress",
+        "MOM_force",
+        "MOM_enable_address"
+    ]
+}
\ No newline at end of file
diff --git a/server/src/lsp_jsonrpc.py b/server/src/lsp_jsonrpc.py
new file mode 100644
index 0000000..011b511
--- /dev/null
+++ b/server/src/lsp_jsonrpc.py
@@ -0,0 +1,254 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Light-weight JSON-RPC over standard IO."""
+
+
+import atexit
+import contextlib
+import io
+import json
+import pathlib
+import subprocess
+import threading
+import uuid
+from concurrent.futures import ThreadPoolExecutor
+from typing import BinaryIO, Dict, Optional, Sequence, Union
+
+CONTENT_LENGTH = "Content-Length: "
+RUNNER_SCRIPT = str(pathlib.Path(__file__).parent / "lsp_runner.py")
+
+
+def to_str(text) -> str:
+    """Convert bytes to string as needed."""
+    return text.decode("utf-8") if isinstance(text, bytes) else text
+
+
+class StreamClosedException(Exception):
+    """JSON RPC stream is closed."""
+
+    pass  # pylint: disable=unnecessary-pass
+
+
+class JsonWriter:
+    """Manages writing JSON-RPC messages to the writer stream."""
+
+    def __init__(self, writer: io.TextIOWrapper):
+        self._writer = writer
+        self._lock = threading.Lock()
+
+    def close(self):
+        """Closes the underlying writer stream."""
+        with self._lock:
+            if not self._writer.closed:
+                self._writer.close()
+
+    def write(self, data):
+        """Writes given data to stream in JSON-RPC format."""
+        if self._writer.closed:
+            raise StreamClosedException()
+
+        with self._lock:
+            content = json.dumps(data)
+            length = len(content.encode("utf-8"))
+            self._writer.write(
+                f"{CONTENT_LENGTH}{length}\r\n\r\n{content}".encode("utf-8")
+            )
+            self._writer.flush()
+
+
+class JsonReader:
+    """Manages reading JSON-RPC messages from stream."""
+
+    def __init__(self, reader: io.TextIOWrapper):
+        self._reader = reader
+
+    def close(self):
+        """Closes the underlying reader stream."""
+        if not self._reader.closed:
+            self._reader.close()
+
+    def read(self):
+        """Reads data from the stream in JSON-RPC format."""
+        if self._reader.closed:
+            raise StreamClosedException
+        length = None
+        while not length:
+            line = to_str(self._readline())
+            if line.startswith(CONTENT_LENGTH):
+                length = int(line[len(CONTENT_LENGTH) :])
+
+        line = to_str(self._readline()).strip()
+        while line:
+            line = to_str(self._readline()).strip()
+
+        content = to_str(self._reader.read(length))
+        return json.loads(content)
+
+    def _readline(self):
+        line = self._reader.readline()
+        if not line:
+            raise EOFError
+        return line
+
+
+class JsonRpc:
+    """Manages sending and receiving data over JSON-RPC."""
+
+    def __init__(self, reader: io.TextIOWrapper, writer: io.TextIOWrapper):
+        self._reader = JsonReader(reader)
+        self._writer = JsonWriter(writer)
+
+    def close(self):
+        """Closes the underlying streams."""
+        with contextlib.suppress(Exception):
+            self._reader.close()
+        with contextlib.suppress(Exception):
+            self._writer.close()
+
+    def send_data(self, data):
+        """Send given data in JSON-RPC format."""
+        self._writer.write(data)
+
+    def receive_data(self):
+        """Receive data in JSON-RPC format."""
+        return self._reader.read()
+
+
+def create_json_rpc(readable: BinaryIO, writable: BinaryIO) -> JsonRpc:
+    """Creates JSON-RPC wrapper for the readable and writable streams."""
+    return JsonRpc(readable, writable)
+
+
+class ProcessManager:
+    """Manages sub-processes launched for running tools."""
+
+    def __init__(self):
+        self._args: Dict[str, Sequence[str]] = {}
+        self._processes: Dict[str, subprocess.Popen] = {}
+        self._rpc: Dict[str, JsonRpc] = {}
+        self._lock = threading.Lock()
+        self._thread_pool = ThreadPoolExecutor(10)
+
+    def stop_all_processes(self):
+        """Send exit command to all processes and shutdown transport."""
+        for i in self._rpc.values():
+            with contextlib.suppress(Exception):
+                i.send_data({"id": str(uuid.uuid4()), "method": "exit"})
+        self._thread_pool.shutdown(wait=False)
+
+    def start_process(self, workspace: str, args: Sequence[str], cwd: str) -> None:
+        """Starts a process and establishes JSON-RPC communication over stdio."""
+        # pylint: disable=consider-using-with
+        proc = subprocess.Popen(
+            args,
+            cwd=cwd,
+            stdout=subprocess.PIPE,
+            stdin=subprocess.PIPE,
+        )
+        self._processes[workspace] = proc
+        self._rpc[workspace] = create_json_rpc(proc.stdout, proc.stdin)
+
+        def _monitor_process():
+            proc.wait()
+            with self._lock:
+                try:
+                    del self._processes[workspace]
+                    rpc = self._rpc.pop(workspace)
+                    rpc.close()
+                except:  # pylint: disable=bare-except
+                    pass
+
+        self._thread_pool.submit(_monitor_process)
+
+    def get_json_rpc(self, workspace: str) -> JsonRpc:
+        """Gets the JSON-RPC wrapper for the a given id."""
+        with self._lock:
+            if workspace in self._rpc:
+                return self._rpc[workspace]
+        raise StreamClosedException()
+
+
+_process_manager = ProcessManager()
+atexit.register(_process_manager.stop_all_processes)
+
+
+def _get_json_rpc(workspace: str) -> Union[JsonRpc, None]:
+    try:
+        return _process_manager.get_json_rpc(workspace)
+    except StreamClosedException:
+        return None
+    except KeyError:
+        return None
+
+
+def get_or_start_json_rpc(
+    workspace: str, interpreter: Sequence[str], cwd: str
+) -> Union[JsonRpc, None]:
+    """Gets an existing JSON-RPC connection or starts one and return it."""
+    res = _get_json_rpc(workspace)
+    if not res:
+        args = [*interpreter, RUNNER_SCRIPT]
+        _process_manager.start_process(workspace, args, cwd)
+        res = _get_json_rpc(workspace)
+    return res
+
+
+class RpcRunResult:
+    """Object to hold result from running tool over RPC."""
+
+    def __init__(self, stdout: str, stderr: str, exception: Optional[str] = None):
+        self.stdout: str = stdout
+        self.stderr: str = stderr
+        self.exception: Optional[str] = exception
+
+
+# pylint: disable=too-many-arguments
+def run_over_json_rpc(
+    workspace: str,
+    interpreter: Sequence[str],
+    module: str,
+    argv: Sequence[str],
+    use_stdin: bool,
+    cwd: str,
+    source: str = None,
+) -> RpcRunResult:
+    """Uses JSON-RPC to execute a command."""
+    rpc: Union[JsonRpc, None] = get_or_start_json_rpc(workspace, interpreter, cwd)
+    if not rpc:
+        raise Exception("Failed to run over JSON-RPC.")
+
+    msg_id = str(uuid.uuid4())
+    msg = {
+        "id": msg_id,
+        "method": "run",
+        "module": module,
+        "argv": argv,
+        "useStdin": use_stdin,
+        "cwd": cwd,
+    }
+    if source:
+        msg["source"] = source
+
+    rpc.send_data(msg)
+
+    data = rpc.receive_data()
+
+    if data["id"] != msg_id:
+        return RpcRunResult(
+            "", f"Invalid result for request: {json.dumps(msg, indent=4)}"
+        )
+
+    result = data["result"] if "result" in data else ""
+    if "error" in data:
+        error = data["error"]
+
+        if data.get("exception", False):
+            return RpcRunResult(result, "", error)
+        return RpcRunResult(result, error)
+
+    return RpcRunResult(result, "")
+
+
+def shutdown_json_rpc():
+    """Shutdown all JSON-RPC processes."""
+    _process_manager.stop_all_processes()
diff --git a/server/src/lsp_runner.py b/server/src/lsp_runner.py
new file mode 100644
index 0000000..b294834
--- /dev/null
+++ b/server/src/lsp_runner.py
@@ -0,0 +1,77 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""
+Runner to use when running under a different interpreter.
+"""
+
+import os
+import pathlib
+import sys
+import traceback
+
+
+# **********************************************************
+# Update sys.path before importing any bundled libraries.
+# **********************************************************
+def update_sys_path(path_to_add: str, strategy: str) -> None:
+    """Add given path to `sys.path`."""
+    if path_to_add not in sys.path and os.path.isdir(path_to_add):
+        if strategy == "useBundled":
+            sys.path.insert(0, path_to_add)
+        elif strategy == "fromEnvironment":
+            sys.path.append(path_to_add)
+
+
+# Ensure that we can import LSP libraries, and other bundled libraries.
+update_sys_path(
+    os.fspath(pathlib.Path(__file__).parent.parent / "libs"),
+    os.getenv("LS_IMPORT_STRATEGY", "useBundled"),
+)
+
+
+# pylint: disable=wrong-import-position,import-error
+import lsp_jsonrpc as jsonrpc
+import lsp_utils as utils
+
+RPC = jsonrpc.create_json_rpc(sys.stdin.buffer, sys.stdout.buffer)
+
+EXIT_NOW = False
+while not EXIT_NOW:
+    msg = RPC.receive_data()
+
+    method = msg["method"]
+    if method == "exit":
+        EXIT_NOW = True
+        continue
+
+    if method == "run":
+        is_exception = False
+        # This is needed to preserve sys.path, pylint modifies
+        # sys.path and that might not work for this scenario
+        # next time around.
+        with utils.substitute_attr(sys, "path", sys.path[:]):
+            try:
+                # TODO: `utils.run_module` is equivalent to running `python -m `.
+                # If your tool supports a programmatic API then replace the function below
+                # with code for your tool. You can also use `utils.run_api` helper, which
+                # handles changing working directories, managing io streams, etc.
+                # Also update `_run_tool_on_document` and `_run_tool` functions in `lsp_server.py`.
+                result = utils.run_module(
+                    module=msg["module"],
+                    argv=msg["argv"],
+                    use_stdin=msg["useStdin"],
+                    cwd=msg["cwd"],
+                    source=msg["source"] if "source" in msg else None,
+                )
+            except Exception:  # pylint: disable=broad-except
+                result = utils.RunResult("", traceback.format_exc(chain=True))
+                is_exception = True
+
+        response = {"id": msg["id"]}
+        if result.stderr:
+            response["error"] = result.stderr
+            response["exception"] = is_exception
+        elif result.stdout:
+            response["result"] = result.stdout
+
+        RPC.send_data(response)
diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py
index 074d769..84fd833 100644
--- a/server/src/lsp_server.py
+++ b/server/src/lsp_server.py
@@ -1,3 +1,5 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
 """Implementation of tool support over LSP."""
 
 from __future__ import annotations
@@ -30,3 +32,564 @@ update_sys_path(
     os.fspath(pathlib.Path(__file__).parent.parent / "libs"),
     os.getenv("LS_IMPORT_STRATEGY", "useBundled"),
 )
+
+# **********************************************************
+# Imports needed for the language server goes below this.
+# **********************************************************
+# pylint: disable=wrong-import-position,import-error
+import lsp_jsonrpc as jsonrpc
+import lsp_utils as utils
+import lsprotocol.types as lsp
+from pygls import server, uris, workspace
+
+WORKSPACE_SETTINGS = {}
+GLOBAL_SETTINGS = {}
+RUNNER = pathlib.Path(__file__).parent / "lsp_runner.py"
+
+MAX_WORKERS = 5
+LSP_SERVER = server.LanguageServer(
+    name="NX Postprocessor Support", version="0.0.1", max_workers=MAX_WORKERS
+)
+
+KEYWORD_LIST = []
+
+# **********************************************************
+# Tool specific code goes below this.
+# **********************************************************
+TOOL_MODULE = "nx-post-support"
+TOOL_DISPLAY = "NX Postprocessor Support"
+TOOL_ARGS = []  # default arguments always passed to your tool.
+
+# Delete "Linting features" section if your tool is NOT a linter.
+# **********************************************************
+# Linting features start here
+# **********************************************************
+
+#  See `pylint` implementation for a full featured linter extension:
+#  Pylint: https://github.com/microsoft/vscode-pylint/blob/main/bundled/tool
+
+
+@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_OPEN)
+def did_open(params: lsp.DidOpenTextDocumentParams) -> None:
+    """LSP handler for textDocument/didOpen request."""
+    document = LSP_SERVER.workspace.get_document(params.text_document.uri)
+
+
+@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_SAVE)
+def did_save(params: lsp.DidSaveTextDocumentParams) -> None:
+    """LSP handler for textDocument/didSave request."""
+    document = LSP_SERVER.workspace.get_document(params.text_document.uri)
+
+
+@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_CLOSE)
+def did_close(params: lsp.DidCloseTextDocumentParams) -> None:
+    """LSP handler for textDocument/didClose request."""
+
+
+@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_CHANGE)
+def did_change(params: lsp.DidChangeTextDocumentParams) -> None:
+    """LSP handler for textDocument/didChange request"""
+    log_to_output("Document has changed")
+
+
+@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_COMPLETION)
+def on_completion(params: lsp.CompletionParams) -> lsp.CompletionItem:
+    items = [
+        lsp.CompletionItem(
+            label="proc",
+            kind=lsp.CompletionItemKind.Keyword,
+            detail="TCL Procedure Definition",
+            documentation="Defines a new procedure in TCL",
+        ),
+        lsp.CompletionItem(
+            label="puts",
+            kind=lsp.CompletionItemKind.Function,
+            detail="Print to stdout",
+            documentation="Prints a string to the standard output in TCL",
+        ),
+    ]
+    return lsp.CompletionList(is_incomplete=False, items=items)
+
+
+# TODO: If your linter outputs in a known format like JSON, then parse
+# accordingly. But incase you need to parse the output using RegEx here
+# is a helper you can work with.
+# flake8 example:
+# If you use following format argument with flake8 you can use the regex below to parse it.
+# TOOL_ARGS += ["--format='%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s'"]
+# DIAGNOSTIC_RE =
+#    r"(?P\d+),(?P-?\d+),(?P\w+),(?P\w+\d+):(?P[^\r\n]*)"
+DIAGNOSTIC_RE = re.compile(r"")
+
+
+def _parse_output_using_regex(content: str) -> list[lsp.Diagnostic]:
+    lines: list[str] = content.splitlines()
+    diagnostics: list[lsp.Diagnostic] = []
+
+    # TODO: Determine if your linter reports line numbers starting at 1 (True) or 0 (False).
+    line_at_1 = True
+    # TODO: Determine if your linter reports column numbers starting at 1 (True) or 0 (False).
+    column_at_1 = True
+
+    line_offset = 1 if line_at_1 else 0
+    col_offset = 1 if column_at_1 else 0
+    for line in lines:
+        if line.startswith("'") and line.endswith("'"):
+            line = line[1:-1]
+        match = DIAGNOSTIC_RE.match(line)
+        if match:
+            data = match.groupdict()
+            position = lsp.Position(
+                line=max([int(data["line"]) - line_offset, 0]),
+                character=int(data["column"]) - col_offset,
+            )
+            diagnostic = lsp.Diagnostic(
+                range=lsp.Range(
+                    start=position,
+                    end=position,
+                ),
+                message=data.get("message"),
+                severity=_get_severity(data["code"], data["type"]),
+                code=data["code"],
+                source=TOOL_MODULE,
+            )
+            diagnostics.append(diagnostic)
+
+    return diagnostics
+
+
+# TODO: if you want to handle setting specific severity for your linter
+# in a user configurable way, then look at look at how it is implemented
+# for `pylint` extension from our team.
+# Pylint: https://github.com/microsoft/vscode-pylint
+# Follow the flow of severity from the settings in package.json to the server.
+def _get_severity(*_codes: list[str]) -> lsp.DiagnosticSeverity:
+    # TODO: All reported issues from linter are treated as warning.
+    # change it as appropriate for your linter.
+    return lsp.DiagnosticSeverity.Warning
+
+
+# **********************************************************
+# Linting features end here
+# **********************************************************
+
+# TODO: If your tool is a formatter then update this section.
+# Delete "Formatting features" section if your tool is NOT a
+# formatter.
+# **********************************************************
+# Formatting features start here
+# **********************************************************
+#  Sample implementations:
+#  Black: https://github.com/microsoft/vscode-black-formatter/blob/main/bundled/tool
+
+
+@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_FORMATTING)
+def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | None:
+    """LSP handler for textDocument/formatting request."""
+    # If your tool is a formatter you can use this handler to provide
+    # formatting support on save. You have to return an array of lsp.TextEdit
+    # objects, to provide your formatted results.
+
+    document = LSP_SERVER.workspace.get_document(params.text_document.uri)
+    edits = _formatting_helper(document)
+    if edits:
+        return edits
+
+    # NOTE: If you provide [] array, VS Code will clear the file of all contents.
+    # To indicate no changes to file return None.
+    return None
+
+
+def _formatting_helper(document: workspace.Document) -> list[lsp.TextEdit] | None:
+    # TODO: For formatting on save support the formatter you use must support
+    # formatting via stdin.
+    # Read, and update_run_tool_on_document and _run_tool functions as needed
+    # for your formatter.
+    result = _run_tool_on_document(document, use_stdin=True)
+    if result.stdout:
+        new_source = _match_line_endings(document, result.stdout)
+        return [
+            lsp.TextEdit(
+                range=lsp.Range(
+                    start=lsp.Position(line=0, character=0),
+                    end=lsp.Position(line=len(document.lines), character=0),
+                ),
+                new_text=new_source,
+            )
+        ]
+    return None
+
+
+def _get_line_endings(lines: list[str]) -> str:
+    """Returns line endings used in the text."""
+    try:
+        if lines[0][-2:] == "\r\n":
+            return "\r\n"
+        return "\n"
+    except Exception:  # pylint: disable=broad-except
+        return None
+
+
+def _match_line_endings(document: workspace.Document, text: str) -> str:
+    """Ensures that the edited text line endings matches the document line endings."""
+    expected = _get_line_endings(document.source.splitlines(keepends=True))
+    actual = _get_line_endings(text.splitlines(keepends=True))
+    if actual == expected or actual is None or expected is None:
+        return text
+    return text.replace(actual, expected)
+
+
+# **********************************************************
+# Formatting features ends here
+# **********************************************************
+
+
+# **********************************************************
+# Required Language Server Initialization and Exit handlers.
+# **********************************************************
+@LSP_SERVER.feature(lsp.INITIALIZE)
+def initialize(params: lsp.InitializeParams) -> None:
+    """LSP handler for initialize request."""
+    log_to_output(f"CWD Server: {os.getcwd()}")
+
+    paths = "\r\n   ".join(sys.path)
+    log_to_output(f"sys.path used to run Server:\r\n   {paths}")
+
+    GLOBAL_SETTINGS.update(**params.initialization_options.get("globalSettings", {}))
+
+    settings = params.initialization_options["settings"]
+    _update_workspace_settings(settings)
+    log_to_output(
+        f"Settings used to run Server:\r\n{json.dumps(settings, indent=4, ensure_ascii=False)}\r\n"
+    )
+    log_to_output(
+        f"Global settings:\r\n{json.dumps(GLOBAL_SETTINGS, indent=4, ensure_ascii=False)}\r\n"
+    )
+
+
+@LSP_SERVER.feature(lsp.EXIT)
+def on_exit(_params: Optional[Any] = None) -> None:
+    """Handle clean up on exit."""
+    jsonrpc.shutdown_json_rpc()
+
+
+@LSP_SERVER.feature(lsp.SHUTDOWN)
+def on_shutdown(_params: Optional[Any] = None) -> None:
+    """Handle clean up on shutdown."""
+    jsonrpc.shutdown_json_rpc()
+
+
+def _get_global_defaults():
+    return {
+        "path": GLOBAL_SETTINGS.get("path", []),
+        "interpreter": GLOBAL_SETTINGS.get("interpreter", [sys.executable]),
+        "args": GLOBAL_SETTINGS.get("args", []),
+        "importStrategy": GLOBAL_SETTINGS.get("importStrategy", "useBundled"),
+        "showNotifications": GLOBAL_SETTINGS.get("showNotifications", "off"),
+    }
+
+
+def _update_workspace_settings(settings):
+    if not settings:
+        key = os.getcwd()
+        WORKSPACE_SETTINGS[key] = {
+            "cwd": key,
+            "workspaceFS": key,
+            "workspace": uris.from_fs_path(key),
+            **_get_global_defaults(),
+        }
+        return
+
+    for setting in settings:
+        key = uris.to_fs_path(setting["workspace"])
+        WORKSPACE_SETTINGS[key] = {
+            "cwd": key,
+            **setting,
+            "workspaceFS": key,
+        }
+
+
+def _get_settings_by_path(file_path: pathlib.Path):
+    workspaces = {s["workspaceFS"] for s in WORKSPACE_SETTINGS.values()}
+
+    while file_path != file_path.parent:
+        str_file_path = str(file_path)
+        if str_file_path in workspaces:
+            return WORKSPACE_SETTINGS[str_file_path]
+        file_path = file_path.parent
+
+    setting_values = list(WORKSPACE_SETTINGS.values())
+    return setting_values[0]
+
+
+def _get_document_key(document: workspace.Document):
+    if WORKSPACE_SETTINGS:
+        document_workspace = pathlib.Path(document.path)
+        workspaces = {s["workspaceFS"] for s in WORKSPACE_SETTINGS.values()}
+
+        # Find workspace settings for the given file.
+        while document_workspace != document_workspace.parent:
+            if str(document_workspace) in workspaces:
+                return str(document_workspace)
+            document_workspace = document_workspace.parent
+
+    return None
+
+
+def _get_settings_by_document(document: workspace.Document | None):
+    if document is None or document.path is None:
+        return list(WORKSPACE_SETTINGS.values())[0]
+
+    key = _get_document_key(document)
+    if key is None:
+        # This is either a non-workspace file or there is no workspace.
+        key = os.fspath(pathlib.Path(document.path).parent)
+        return {
+            "cwd": key,
+            "workspaceFS": key,
+            "workspace": uris.from_fs_path(key),
+            **_get_global_defaults(),
+        }
+
+    return WORKSPACE_SETTINGS[str(key)]
+
+
+# *****************************************************
+# Internal execution APIs.
+# *****************************************************
+def _run_tool_on_document(
+    document: workspace.Document,
+    use_stdin: bool = False,
+    extra_args: Optional[Sequence[str]] = None,
+) -> utils.RunResult | None:
+    """Runs tool on the given document.
+
+    if use_stdin is true then contents of the document is passed to the
+    tool via stdin.
+    """
+    if extra_args is None:
+        extra_args = []
+    if str(document.uri).startswith("vscode-notebook-cell"):
+        # TODO: Decide on if you want to skip notebook cells.
+        # Skip notebook cells
+        return None
+
+    if utils.is_stdlib_file(document.path):
+        # TODO: Decide on if you want to skip standard library files.
+        # Skip standard library python files.
+        return None
+
+    # deep copy here to prevent accidentally updating global settings.
+    settings = copy.deepcopy(_get_settings_by_document(document))
+
+    code_workspace = settings["workspaceFS"]
+    cwd = settings["cwd"]
+
+    use_path = False
+    use_rpc = False
+    if settings["path"]:
+        # 'path' setting takes priority over everything.
+        use_path = True
+        argv = settings["path"]
+    elif settings["interpreter"] and not utils.is_current_interpreter(
+        settings["interpreter"][0]
+    ):
+        # If there is a different interpreter set use JSON-RPC to the subprocess
+        # running under that interpreter.
+        argv = [TOOL_MODULE]
+        use_rpc = True
+    else:
+        # if the interpreter is same as the interpreter running this
+        # process then run as module.
+        argv = [TOOL_MODULE]
+
+    argv += TOOL_ARGS + settings["args"] + extra_args
+
+    if use_stdin:
+        # TODO: update these to pass the appropriate arguments to provide document contents
+        # to tool via stdin.
+        # For example, for pylint args for stdin looks like this:
+        #     pylint --from-stdin 
+        # Here `--from-stdin` path is used by pylint to make decisions on the file contents
+        # that are being processed. Like, applying exclusion rules.
+        # It should look like this when you pass it:
+        #     argv += ["--from-stdin", document.path]
+        # Read up on how your tool handles contents via stdin. If stdin is not supported use
+        # set use_stdin to False, or provide path, what ever is appropriate for your tool.
+        argv += []
+    else:
+        argv += [document.path]
+
+    if use_path:
+        # This mode is used when running executables.
+        log_to_output(" ".join(argv))
+        log_to_output(f"CWD Server: {cwd}")
+        result = utils.run_path(
+            argv=argv,
+            use_stdin=use_stdin,
+            cwd=cwd,
+            source=document.source.replace("\r\n", "\n"),
+        )
+        if result.stderr:
+            log_to_output(result.stderr)
+    elif use_rpc:
+        # This mode is used if the interpreter running this server is different from
+        # the interpreter used for running this server.
+        log_to_output(" ".join(settings["interpreter"] + ["-m"] + argv))
+        log_to_output(f"CWD Linter: {cwd}")
+
+        result = jsonrpc.run_over_json_rpc(
+            workspace=code_workspace,
+            interpreter=settings["interpreter"],
+            module=TOOL_MODULE,
+            argv=argv,
+            use_stdin=use_stdin,
+            cwd=cwd,
+            source=document.source,
+        )
+        if result.exception:
+            log_error(result.exception)
+            result = utils.RunResult(result.stdout, result.stderr)
+        elif result.stderr:
+            log_to_output(result.stderr)
+    else:
+        # In this mode the tool is run as a module in the same process as the language server.
+        log_to_output(" ".join([sys.executable, "-m"] + argv))
+        log_to_output(f"CWD Linter: {cwd}")
+        # This is needed to preserve sys.path, in cases where the tool modifies
+        # sys.path and that might not work for this scenario next time around.
+        with utils.substitute_attr(sys, "path", sys.path[:]):
+            try:
+                # TODO: `utils.run_module` is equivalent to running `python -m `.
+                # If your tool supports a programmatic API then replace the function below
+                # with code for your tool. You can also use `utils.run_api` helper, which
+                # handles changing working directories, managing io streams, etc.
+                # Also update `_run_tool` function and `utils.run_module` in `lsp_runner.py`.
+                result = utils.run_module(
+                    module=TOOL_MODULE,
+                    argv=argv,
+                    use_stdin=use_stdin,
+                    cwd=cwd,
+                    source=document.source,
+                )
+            except Exception:
+                log_error(traceback.format_exc(chain=True))
+                raise
+        if result.stderr:
+            log_to_output(result.stderr)
+
+    log_to_output(f"{document.uri} :\r\n{result.stdout}")
+    return result
+
+
+def _run_tool(extra_args: Sequence[str]) -> utils.RunResult:
+    """Runs tool."""
+    # deep copy here to prevent accidentally updating global settings.
+    settings = copy.deepcopy(_get_settings_by_document(None))
+
+    code_workspace = settings["workspaceFS"]
+    cwd = settings["workspaceFS"]
+
+    use_path = False
+    use_rpc = False
+    if len(settings["path"]) > 0:
+        # 'path' setting takes priority over everything.
+        use_path = True
+        argv = settings["path"]
+    elif len(settings["interpreter"]) > 0 and not utils.is_current_interpreter(
+        settings["interpreter"][0]
+    ):
+        # If there is a different interpreter set use JSON-RPC to the subprocess
+        # running under that interpreter.
+        argv = [TOOL_MODULE]
+        use_rpc = True
+    else:
+        # if the interpreter is same as the interpreter running this
+        # process then run as module.
+        argv = [TOOL_MODULE]
+
+    argv += extra_args
+
+    if use_path:
+        # This mode is used when running executables.
+        log_to_output(" ".join(argv))
+        log_to_output(f"CWD Server: {cwd}")
+        result = utils.run_path(argv=argv, use_stdin=True, cwd=cwd)
+        if result.stderr:
+            log_to_output(result.stderr)
+    elif use_rpc:
+        # This mode is used if the interpreter running this server is different from
+        # the interpreter used for running this server.
+        log_to_output(" ".join(settings["interpreter"] + ["-m"] + argv))
+        log_to_output(f"CWD Linter: {cwd}")
+        result = jsonrpc.run_over_json_rpc(
+            workspace=code_workspace,
+            interpreter=settings["interpreter"],
+            module=TOOL_MODULE,
+            argv=argv,
+            use_stdin=True,
+            cwd=cwd,
+        )
+        if result.exception:
+            log_error(result.exception)
+            result = utils.RunResult(result.stdout, result.stderr)
+        elif result.stderr:
+            log_to_output(result.stderr)
+    else:
+        # In this mode the tool is run as a module in the same process as the language server.
+        log_to_output(" ".join([sys.executable, "-m"] + argv))
+        log_to_output(f"CWD Linter: {cwd}")
+        # This is needed to preserve sys.path, in cases where the tool modifies
+        # sys.path and that might not work for this scenario next time around.
+        with utils.substitute_attr(sys, "path", sys.path[:]):
+            try:
+                # TODO: `utils.run_module` is equivalent to running `python -m `.
+                # If your tool supports a programmatic API then replace the function below
+                # with code for your tool. You can also use `utils.run_api` helper, which
+                # handles changing working directories, managing io streams, etc.
+                # Also update `_run_tool_on_document` function and `utils.run_module` in `lsp_runner.py`.
+                result = utils.run_module(
+                    module=TOOL_MODULE, argv=argv, use_stdin=True, cwd=cwd
+                )
+            except Exception:
+                log_error(traceback.format_exc(chain=True))
+                raise
+        if result.stderr:
+            log_to_output(result.stderr)
+
+    log_to_output(f"\r\n{result.stdout}\r\n")
+    return result
+
+
+# *****************************************************
+# Logging and notification.
+# *****************************************************
+def log_to_output(
+    message: str, msg_type: lsp.MessageType = lsp.MessageType.Log
+) -> None:
+    LSP_SERVER.show_message_log(message, msg_type)
+
+
+def log_error(message: str) -> None:
+    LSP_SERVER.show_message_log(message, lsp.MessageType.Error)
+    if os.getenv("LS_SHOW_NOTIFICATION", "off") in ["onError", "onWarning", "always"]:
+        LSP_SERVER.show_message(message, lsp.MessageType.Error)
+
+
+def log_warning(message: str) -> None:
+    LSP_SERVER.show_message_log(message, lsp.MessageType.Warning)
+    if os.getenv("LS_SHOW_NOTIFICATION", "off") in ["onWarning", "always"]:
+        LSP_SERVER.show_message(message, lsp.MessageType.Warning)
+
+
+def log_always(message: str) -> None:
+    LSP_SERVER.show_message_log(message, lsp.MessageType.Info)
+    if os.getenv("LS_SHOW_NOTIFICATION", "off") in ["always"]:
+        LSP_SERVER.show_message(message, lsp.MessageType.Info)
+
+
+# *****************************************************
+# Start the server.
+# *****************************************************
+if __name__ == "__main__":
+    LSP_SERVER.start_io()
diff --git a/server/src/lsp_utils.py b/server/src/lsp_utils.py
new file mode 100644
index 0000000..fb5f032
--- /dev/null
+++ b/server/src/lsp_utils.py
@@ -0,0 +1,207 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+"""Utility functions and classes for use with running tools over LSP."""
+from __future__ import annotations
+
+import contextlib
+import io
+import os
+import os.path
+import runpy
+import site
+import subprocess
+import sys
+import threading
+from typing import Any, Callable, List, Sequence, Tuple, Union
+
+# Save the working directory used when loading this module
+SERVER_CWD = os.getcwd()
+CWD_LOCK = threading.Lock()
+
+
+def as_list(content: Union[Any, List[Any], Tuple[Any]]) -> Union[List[Any], Tuple[Any]]:
+    """Ensures we always get a list"""
+    if isinstance(content, (list, tuple)):
+        return content
+    return [content]
+
+
+# pylint: disable-next=consider-using-generator
+_site_paths = tuple(
+    [
+        os.path.normcase(os.path.normpath(p))
+        for p in (as_list(site.getsitepackages()) + as_list(site.getusersitepackages()))
+    ]
+)
+
+
+def is_same_path(file_path1, file_path2) -> bool:
+    """Returns true if two paths are the same."""
+    return os.path.normcase(os.path.normpath(file_path1)) == os.path.normcase(
+        os.path.normpath(file_path2)
+    )
+
+
+def is_current_interpreter(executable) -> bool:
+    """Returns true if the executable path is same as the current interpreter."""
+    return is_same_path(executable, sys.executable)
+
+
+def is_stdlib_file(file_path) -> bool:
+    """Return True if the file belongs to standard library."""
+    return os.path.normcase(os.path.normpath(file_path)).startswith(_site_paths)
+
+
+# pylint: disable-next=too-few-public-methods
+class RunResult:
+    """Object to hold result from running tool."""
+
+    def __init__(self, stdout: str, stderr: str):
+        self.stdout: str = stdout
+        self.stderr: str = stderr
+
+
+class CustomIO(io.TextIOWrapper):
+    """Custom stream object to replace stdio."""
+
+    name = None
+
+    def __init__(self, name, encoding="utf-8", newline=None):
+        self._buffer = io.BytesIO()
+        self._buffer.name = name
+        super().__init__(self._buffer, encoding=encoding, newline=newline)
+
+    def close(self):
+        """Provide this close method which is used by some tools."""
+        # This is intentionally empty.
+
+    def get_value(self) -> str:
+        """Returns value from the buffer as string."""
+        self.seek(0)
+        return self.read()
+
+
+@contextlib.contextmanager
+def substitute_attr(obj: Any, attribute: str, new_value: Any):
+    """Manage object attributes context when using runpy.run_module()."""
+    old_value = getattr(obj, attribute)
+    setattr(obj, attribute, new_value)
+    yield
+    setattr(obj, attribute, old_value)
+
+
+@contextlib.contextmanager
+def redirect_io(stream: str, new_stream):
+    """Redirect stdio streams to a custom stream."""
+    old_stream = getattr(sys, stream)
+    setattr(sys, stream, new_stream)
+    yield
+    setattr(sys, stream, old_stream)
+
+
+@contextlib.contextmanager
+def change_cwd(new_cwd):
+    """Change working directory before running code."""
+    os.chdir(new_cwd)
+    yield
+    os.chdir(SERVER_CWD)
+
+
+def _run_module(
+    module: str, argv: Sequence[str], use_stdin: bool, source: str = None
+) -> RunResult:
+    """Runs as a module."""
+    str_output = CustomIO("", encoding="utf-8")
+    str_error = CustomIO("", encoding="utf-8")
+
+    with contextlib.suppress(SystemExit):
+        with substitute_attr(sys, "argv", argv):
+            with redirect_io("stdout", str_output):
+                with redirect_io("stderr", str_error):
+                    if use_stdin and source is not None:
+                        str_input = CustomIO("", encoding="utf-8", newline="\n")
+                        with redirect_io("stdin", str_input):
+                            str_input.write(source)
+                            str_input.seek(0)
+                            runpy.run_module(module, run_name="__main__")
+                    else:
+                        runpy.run_module(module, run_name="__main__")
+
+    return RunResult(str_output.get_value(), str_error.get_value())
+
+
+def run_module(
+    module: str, argv: Sequence[str], use_stdin: bool, cwd: str, source: str = None
+) -> RunResult:
+    """Runs as a module."""
+    with CWD_LOCK:
+        if is_same_path(os.getcwd(), cwd):
+            return _run_module(module, argv, use_stdin, source)
+        with change_cwd(cwd):
+            return _run_module(module, argv, use_stdin, source)
+
+
+def run_path(
+    argv: Sequence[str], use_stdin: bool, cwd: str, source: str = None
+) -> RunResult:
+    """Runs as an executable."""
+    if use_stdin:
+        with subprocess.Popen(
+            argv,
+            encoding="utf-8",
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            stdin=subprocess.PIPE,
+            cwd=cwd,
+        ) as process:
+            return RunResult(*process.communicate(input=source))
+    else:
+        result = subprocess.run(
+            argv,
+            encoding="utf-8",
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+            check=False,
+            cwd=cwd,
+        )
+        return RunResult(result.stdout, result.stderr)
+
+
+def run_api(
+    callback: Callable[[Sequence[str], CustomIO, CustomIO, CustomIO | None], None],
+    argv: Sequence[str],
+    use_stdin: bool,
+    cwd: str,
+    source: str = None,
+) -> RunResult:
+    """Run a API."""
+    with CWD_LOCK:
+        if is_same_path(os.getcwd(), cwd):
+            return _run_api(callback, argv, use_stdin, source)
+        with change_cwd(cwd):
+            return _run_api(callback, argv, use_stdin, source)
+
+
+def _run_api(
+    callback: Callable[[Sequence[str], CustomIO, CustomIO, CustomIO | None], None],
+    argv: Sequence[str],
+    use_stdin: bool,
+    source: str = None,
+) -> RunResult:
+    str_output = CustomIO("", encoding="utf-8")
+    str_error = CustomIO("", encoding="utf-8")
+
+    with contextlib.suppress(SystemExit):
+        with substitute_attr(sys, "argv", argv):
+            with redirect_io("stdout", str_output):
+                with redirect_io("stderr", str_error):
+                    if use_stdin and source is not None:
+                        str_input = CustomIO("", encoding="utf-8", newline="\n")
+                        with redirect_io("stdin", str_input):
+                            str_input.write(source)
+                            str_input.seek(0)
+                            callback(argv, str_output, str_error, str_input)
+                    else:
+                        callback(argv, str_output, str_error)
+
+    return RunResult(str_output.get_value(), str_error.get_value())
diff --git a/server/uv.lock b/server/uv.lock
deleted file mode 100644
index 4af21f5..0000000
--- a/server/uv.lock
+++ /dev/null
@@ -1,83 +0,0 @@
-version = 1
-requires-python = ">=3.11"
-
-[[package]]
-name = "attrs"
-version = "25.3.0"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 },
-]
-
-[[package]]
-name = "cattrs"
-version = "25.1.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
-    { name = "attrs" },
-    { name = "typing-extensions" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/57/2b/561d78f488dcc303da4639e02021311728fb7fda8006dd2835550cddd9ed/cattrs-25.1.1.tar.gz", hash = "sha256:c914b734e0f2d59e5b720d145ee010f1fd9a13ee93900922a2f3f9d593b8382c", size = 435016 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/18/b0/215274ef0d835bbc1056392a367646648b6084e39d489099959aefcca2af/cattrs-25.1.1-py3-none-any.whl", hash = "sha256:1b40b2d3402af7be79a7e7e097a9b4cd16d4c06e6d526644b0b26a063a1cc064", size = 69386 },
-]
-
-[[package]]
-name = "lsprotocol"
-version = "2023.0.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
-    { name = "attrs" },
-    { name = "cattrs" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/9d/f6/6e80484ec078d0b50699ceb1833597b792a6c695f90c645fbaf54b947e6f/lsprotocol-2023.0.1.tar.gz", hash = "sha256:cc5c15130d2403c18b734304339e51242d3018a05c4f7d0f198ad6e0cd21861d", size = 69434 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/8d/37/2351e48cb3309673492d3a8c59d407b75fb6630e560eb27ecd4da03adc9a/lsprotocol-2023.0.1-py3-none-any.whl", hash = "sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2", size = 70826 },
-]
-
-[[package]]
-name = "ply"
-version = "3.11"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567 },
-]
-
-[[package]]
-name = "pygls"
-version = "1.3.1"
-source = { registry = "https://pypi.org/simple" }
-dependencies = [
-    { name = "cattrs" },
-    { name = "lsprotocol" },
-]
-sdist = { url = "https://files.pythonhosted.org/packages/86/b9/41d173dad9eaa9db9c785a85671fc3d68961f08d67706dc2e79011e10b5c/pygls-1.3.1.tar.gz", hash = "sha256:140edceefa0da0e9b3c533547c892a42a7d2fd9217ae848c330c53d266a55018", size = 45527 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/11/19/b74a10dd24548e96e8c80226cbacb28b021bc3a168a7d2709fb0d0185348/pygls-1.3.1-py3-none-any.whl", hash = "sha256:6e00f11efc56321bdeb6eac04f6d86131f654c7d49124344a9ebb968da3dd91e", size = 56031 },
-]
-
-[[package]]
-name = "server"
-version = "0.1.0"
-source = { virtual = "." }
-dependencies = [
-    { name = "ply" },
-    { name = "pygls" },
-]
-
-[package.metadata]
-requires-dist = [
-    { name = "ply" },
-    { name = "pygls" },
-]
-
-[[package]]
-name = "typing-extensions"
-version = "4.14.1"
-source = { registry = "https://pypi.org/simple" }
-sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 }
-wheels = [
-    { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 },
-]

From d4e3f8f806d3fc71494dc4b34bbe2606de9aef99 Mon Sep 17 00:00:00 2001
From: christoph_xd 
Date: Wed, 16 Jul 2025 21:34:40 +0200
Subject: [PATCH 04/12] update completion feature

---
 .gitignore                             |  3 ++-
 server/src/common/completion_list.json | 14 ++++++-------
 server/src/common/load_data.py         | 28 ++++++++++++++++++++++++++
 server/src/lsp_server.py               | 21 +++++--------------
 4 files changed, 42 insertions(+), 24 deletions(-)
 create mode 100644 server/src/common/load_data.py

diff --git a/.gitignore b/.gitignore
index b75d172..bbdd8dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@ dist
 target
 .venv
 __pycache__
-*.pyc
\ No newline at end of file
+*.pyc
+.nox
\ No newline at end of file
diff --git a/server/src/common/completion_list.json b/server/src/common/completion_list.json
index d67c328..4af918f 100644
--- a/server/src/common/completion_list.json
+++ b/server/src/common/completion_list.json
@@ -1,11 +1,11 @@
 {
-    "tcl": {
-        "keywords": [
-            "proc",
-            "puts",
-            "if"
-        ]
-    },
+    "tcl": [
+        "proc",
+        "puts",
+        "if",
+        "else",
+        "elseif"
+    ],
     "MOM_procs": [
         "MOM_output_literal",
         "MOM_ask_ude_info",
diff --git a/server/src/common/load_data.py b/server/src/common/load_data.py
new file mode 100644
index 0000000..4663526
--- /dev/null
+++ b/server/src/common/load_data.py
@@ -0,0 +1,28 @@
+import json
+import lsprotocol.types as lsp
+import pathlib
+
+
+class StandardCompletionItems:
+    def __init__(self):
+        self.__tcl_keyword_list = self.load_tcl_keyword()
+
+    @property
+    def tcl_keyword_list(self):
+        return self.__tcl_keyword_list
+
+    def load_tcl_keyword(self) -> list[lsp.CompletionItem]:
+        with open(
+            pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r"
+        ) as f:
+            data = json.load(f)
+
+        items = []
+        for i in data.get("tcl", []):
+            items.append(
+                lsp.CompletionItem(label=i, kind=lsp.CompletionItemKind.Keyword)
+            )
+        return items
+
+
+standard_items = StandardCompletionItems()
diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py
index 84fd833..1b6051b 100644
--- a/server/src/lsp_server.py
+++ b/server/src/lsp_server.py
@@ -41,6 +41,7 @@ import lsp_jsonrpc as jsonrpc
 import lsp_utils as utils
 import lsprotocol.types as lsp
 from pygls import server, uris, workspace
+from common.load_data import standard_items
 
 WORKSPACE_SETTINGS = {}
 GLOBAL_SETTINGS = {}
@@ -51,7 +52,8 @@ LSP_SERVER = server.LanguageServer(
     name="NX Postprocessor Support", version="0.0.1", max_workers=MAX_WORKERS
 )
 
-KEYWORD_LIST = []
+
+NX_PROC_LIST = []
 
 # **********************************************************
 # Tool specific code goes below this.
@@ -93,21 +95,8 @@ def did_change(params: lsp.DidChangeTextDocumentParams) -> None:
 
 
 @LSP_SERVER.feature(lsp.TEXT_DOCUMENT_COMPLETION)
-def on_completion(params: lsp.CompletionParams) -> lsp.CompletionItem:
-    items = [
-        lsp.CompletionItem(
-            label="proc",
-            kind=lsp.CompletionItemKind.Keyword,
-            detail="TCL Procedure Definition",
-            documentation="Defines a new procedure in TCL",
-        ),
-        lsp.CompletionItem(
-            label="puts",
-            kind=lsp.CompletionItemKind.Function,
-            detail="Print to stdout",
-            documentation="Prints a string to the standard output in TCL",
-        ),
-    ]
+def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]:
+    items = standard_items.tcl_keyword_list
     return lsp.CompletionList(is_incomplete=False, items=items)
 
 

From 9390ad456968565161cbc274237528b15d047072 Mon Sep 17 00:00:00 2001
From: Christoph Brandau 
Date: Thu, 17 Jul 2025 14:36:33 +0200
Subject: [PATCH 05/12] add vars and function for completion

---
 .vscodeignore                          |   8 +-
 server/src/common/completion_list.json | 232 ++++++++++++++++++++++++-
 server/src/common/load_data.py         | 114 +++++++++++-
 server/src/lsp_server.py               |   6 +-
 4 files changed, 351 insertions(+), 9 deletions(-)

diff --git a/.vscodeignore b/.vscodeignore
index 35e5767..a054988 100644
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -8,4 +8,10 @@ test
 vite.config.js
 .prettierrc.json
 esbuild.js
-.gitea
\ No newline at end of file
+.gitea
+.venv/**
+.nox/**
+**/__pycache__/**
+**/requirements.txt
+**/requirements.in
+**/server/src/_debug_server.py
\ No newline at end of file
diff --git a/server/src/common/completion_list.json b/server/src/common/completion_list.json
index 4af918f..f69debf 100644
--- a/server/src/common/completion_list.json
+++ b/server/src/common/completion_list.json
@@ -7,10 +7,232 @@
         "elseif"
     ],
     "MOM_procs": [
-        "MOM_output_literal",
-        "MOM_ask_ude_info",
-        "MOM_suppress",
-        "MOM_force",
-        "MOM_enable_address"
+        {
+            "label": "MOM_abort",
+            "kind": "function",
+            "description": "This function causes postprocessing to write an error message to the syslog and then terminate.",
+            "format": "MOM_abort ",
+            "parameters": [
+                {
+                    "name": "message",
+                    "desc": "Text string to be output when postprocessing is aborted."
+                }
+            ],
+            "example": [
+                "MOM_abort \"Error occurred. Postprocessing has been aborted.\""
+            ]
+        },
+        {
+            "label": "MOM_abort_event",
+            "kind": "function",
+            "description": "This command immediately aborts the current event and returns processing control back to NX. Use this command if you do not want to return to a calling procedure.",
+            "format": "MOM_abort_event ",
+            "parameters": [
+                {
+                    "name": "message",
+                    "desc": "Text string to be output when postprocessing is aborted."
+                }
+            ],
+            "example": [
+                "MOM_abort_event \"Error occurred. Postprocessing has been aborted.\""
+            ]
+        },
+        {
+            "label": "MOM_abort_operation",
+            "kind": "function",
+            "description": "This function causes post processing to terminate the current operation. Post processing will continue with MOM_end_of_path event.",
+            "format": "MOM_abort_operation",
+            "parameters": [
+                {
+                    "name": "message",
+                    "desc": "Text string to be output when postprocessing is aborted."
+                }
+            ],
+            "example": [
+                "MOM_abort_operation"
+            ]
+        },
+        {
+            "label": "MOM_abort_program",
+            "kind": "function",
+            "description": "This function causes post processing to terminate the current program group with an entry to the syslog file. Postprocessing will continue with the MOM_end_of_program event. The message is optional. This command is available with NX12.",
+            "format": "MOM_abort_program []",
+            "parameters": [
+                {
+                    "name": "message",
+                    "desc": "Text string to be output when postprocessing is aborted."
+                }
+            ],
+            "example": [
+                "MOM_abort_program",
+                "MOM_abort_program \"Abort program group post processing.\""
+            ]
+        },
+        {
+            "label": "MOM_add_to_address_buffer",
+            "kind": "function",
+            "description": "Depending on the  attribute specified, this extension will add  to either the address start buffer or the address end buffer. Each time that this extension is called, it adds to the specified buffer. When the contents of the address are sent to the output buffer, the contents of the address start buffer will precede it and the contents of the end buffer will go after it. The address start buffer and the address end buffer are cleared once they have been sent to the output buffer.",
+            "format": "MOM_add_to_address_buffer   +",
+            "parameters": [
+                {
+                    "name": "address_name",
+                    "desc": "Name of address."
+                },
+                {
+                    "name": "start|end",
+                    "desc": "Position in address buffer to add content."
+                },
+                {
+                    "name": "value",
+                    "desc": "Content to be added to address buffer."
+                }
+            ],
+            "example": [
+                "MOM_add_to_address_buffer X end [append end $list2 $list1]",
+                "MOM_add_to_address_buffer X start $prefix",
+                "MOM_add_to_address_buffer X start [MOM_do_template Seq_no CREATE]",
+                "MOM_add_to_address_buffer X start \"X\""
+            ]
+        },
+        {
+            "label": "MOM_add_to_block_buffer",
+            "kind": "function",
+            "description": "Depending on the  attribute specified, this extension will add  to either the block start buffer or the block end buffer. Each time that this extension is called, it adds to the specified buffer. When the block is sent to the output buffer, the contents of the block start buffer will precede it on the line and the contents of the block end buffer will go after it. The block start buffer and the block end buffer are cleared once they have been written to the output buffer.",
+            "format": "MOM_add_to_block_buffer   +",
+            "parameters": [
+                {
+                    "name": "block_name",
+                    "desc": "Name of block."
+                },
+                {
+                    "name": "start|end",
+                    "desc": "Position in block buffer to add content."
+                },
+                {
+                    "name": "value",
+                    "desc": "Content to be added to block buffer."
+                }
+            ],
+            "example": [
+                "MOM_add_to_block_buffer From end [append end $list2 $list1]",
+                "MOM_add_to_block_buffer From start $prefix",
+                "MOM_add_to_block_buffer Linear start [MOM_do_template Seq_no CREATE]",
+                "MOM_add_to_block _buffer From start \"Remark\""
+            ]
+        },
+        {
+            "label": "MOM_add_to_block_buffer",
+            "kind": "function",
+            "description": "Depending on the  attribute specified, this extension will add  to either the line's start buffer or the line's end buffer. Each time that this extension is called, it adds to the specified buffer. When the contents of the output buffer are sent to the output file, the contents of the line's start buffer will precede it on the line and the contents of the end buffer will go on the same line after it. The line start buffer, line end buffer, and output buffer are all cleared once they have been written to the output file.",
+            "format": "MOM_add_to_line_buffer  +",
+            "parameters": [
+                {
+                    "name": "start|end",
+                    "desc": "Position in line buffer to add content."
+                },
+                {
+                    "name": "value",
+                    "desc": "Content to be added to line buffer."
+                }
+            ],
+            "example": [
+                "MOM_add_to_line_buffer start [MOM_do_template Linear CREATE]",
+                "MOM_add_to_line_buffer start [append start $list1 $list2]",
+                "MOM_add_to_line_buffer start $prefix",
+                "MOM_add_to_line_buffer start \"****\""
+            ]
+        },
+        {
+            "label": "MOM_ask_address_value",
+            "kind": "function",
+            "description": "Always returns the absolute address value NOT the incremental value.",
+            "format": "MOM_ask_address_value ",
+            "parameters": [
+                {
+                    "name": "address_name",
+                    "desc": "Text string of the output address."
+                }
+            ],
+            "example": [
+                "MOM_ask_address_value X"
+            ]
+        },
+        {
+            "label": "MOM_ask_cutter_info",
+            "kind": "function",
+            "description": "Retrieves tool information at the start of a program and stores the results in the array variable mom_cutter_info. Called from Post Configurator procedure MOM_start_of_path_LIB.",
+            "format": "MOM_ask_cutter_info ",
+            "parameters": [
+                {
+                    "name": "cutter_list",
+                    "desc": "List of tool names."
+                }
+            ],
+            "example": [
+                "set cutter_list \"UGTI0202_030 UGTI0202_025 UGTI0201_062\"\nMOM_ask_cutter_info cutter_list"
+            ]
+        },
+        {
+            "label": "MOM_ask_ude_info",
+            "kind": "function",
+            "description": "This command is used to retrieve the information about a user-defined event (UDE) of a specified object.",
+            "format": "MOM_ask_ude_info object_name object_type ",
+            "parameters": [
+                {
+                    "name": "Object_name",
+                    "desc": "The name of the object to which the UDE is attached. It can be a group, an operation, a tool, a geometry, or a method."
+                },
+                {
+                    "name": "object_type",
+                    "desc": "The type of the object to which the UDE is attached. The following types are available.(group || operation/oper || tool || geometry/geom || method/meth)"
+                },
+                {
+                    "name": "Start/End/\"\"",
+                    "desc": "This parameter is optional. Indicates whether you want to retrieve the UDE attached to the start event, end event, or both. If it is empty, returns the UDEs attached to the specified operation."
+                }
+            ],
+            "returns": [
+                "0 - The UDE information retrieval failed.",
+                "1 - The UDE information successfully retrieved."
+            ],
+            "example": []
+        }
+    ],
+    "mom_variables": [
+        {
+            "label": "mom_alt_pos",
+            "kind": "variable",
+            "description": "The alternate machine tool coordinates (X,Y,Z,fourth axis,fifth axis) for the current GOTO. This value is different from mom_pos if the kinematics of the machine tool is five axis. Otherwise, mom_alt_pos will be the same as mom_pos. For five axis machine tools, there are always at least two valid solutions when converting the input coordinates (X,Y,Z,I,J,K) into machine tool coordinates (X,Y,Z,A,B). The optimal solution will be mom_pos, the alternate solution will be mom_alt_pos.",
+            "possible_values": "Real numbers.",
+            "data_type": "Numeric Array(5)"
+        },
+        {
+            "label": "mom_arc_angle",
+            "kind": "variable",
+            "description": "The total number of degrees for the current arc.",
+            "possible_values": "0.0 to 360.0",
+            "data_type": "Numeric Value"
+        },
+        {
+            "label": "mom_arc_axis",
+            "kind": "variable",
+            "description": "The circle axis vector.",
+            "possible_values": "Unit vector.",
+            "data_type": "Numeric Array(3)"
+        },
+        {
+            "label": "mom_arc_center",
+            "kind": "variable",
+            "description": "The center of the circle.",
+            "possible_values": "Real numbers.",
+            "data_type": "Numeric Array(3)"
+        },
+        {
+            "label": "mom_arc_direction",
+            "kind": "variable",
+            "description": "The direction of the circle.",
+            "possible_values": "CLW or CCLW",
+            "data_type": "String"
+        }
     ]
 }
\ No newline at end of file
diff --git a/server/src/common/load_data.py b/server/src/common/load_data.py
index 4663526..952f3fc 100644
--- a/server/src/common/load_data.py
+++ b/server/src/common/load_data.py
@@ -3,15 +3,36 @@ import lsprotocol.types as lsp
 import pathlib
 
 
+KIND_MAP = {
+    "function": lsp.CompletionItemKind.Function,
+    "method": lsp.CompletionItemKind.Method,
+    "class": lsp.CompletionItemKind.Class,
+    "variable": lsp.CompletionItemKind.Variable,
+    "field": lsp.CompletionItemKind.Field,
+    "module": lsp.CompletionItemKind.Module,
+    "namespace": lsp.CompletionItemKind.Class,
+}
+
+
 class StandardCompletionItems:
     def __init__(self):
-        self.__tcl_keyword_list = self.load_tcl_keyword()
+        self.__tcl_keyword_list = self.__load_tcl_keyword()
+        self.__nx_procs = self.__load_nx_procs()
+        self.__nx_variables = self.__load_nx_variables()
 
     @property
     def tcl_keyword_list(self):
         return self.__tcl_keyword_list
 
-    def load_tcl_keyword(self) -> list[lsp.CompletionItem]:
+    @property
+    def nx_procs(self):
+        return self.__nx_procs
+
+    @property
+    def nx_variables(self):
+        return self.__nx_variables
+
+    def __load_tcl_keyword(self) -> list[lsp.CompletionItem]:
         with open(
             pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r"
         ) as f:
@@ -24,5 +45,94 @@ class StandardCompletionItems:
             )
         return items
 
+    def __load_nx_variables(self):
+        with open(
+            pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r"
+        ) as f:
+            data = json.load(f)
+        items = []
+        for var in data.get("mom_variables", []):
+            label = var.get("label", "")
+            kind_str = var.get("kind", "").lower()
+            kind_enum = KIND_MAP.get(kind_str, lsp.CompletionItemKind.Text)
+            doc_md = f"""\
+**MOM variable**  
+{label}
+
+**Description**  
+{var.get("description", "")}
+
+**Possible Values:**  
+{var.get("possible_values", "any")}
+    
+**Data type**  
+{var.get("data_type", "")}
+"""
+            items.append(
+                lsp.CompletionItem(
+                    label=label,
+                    kind=kind_enum,
+                    detail=f"{label}",
+                    documentation=lsp.MarkupContent(
+                        kind=lsp.MarkupKind.Markdown, value=doc_md
+                    ),
+                )
+            )
+        return items
+
+    def __load_nx_procs(self) -> list[lsp.CompletionItem]:
+        with open(
+            pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r"
+        ) as f:
+            data = json.load(f)
+
+        items = []
+        for proc in data.get("MOM_procs", []):
+            label = proc.get("label", "")
+            kind_str = proc.get("kind", "").lower()
+            kind_enum = KIND_MAP.get(kind_str, lsp.CompletionItemKind.Text)
+
+            parameters = proc.get("parameters", [])
+            param_lines = (
+                "\n".join(f"- `{p['name']}`: {p['desc']}" for p in parameters)
+                or "_None_"
+            )
+
+            example_data = proc.get("example", [])
+            example_md = "\n".join(f"{line}" for line in example_data)
+
+            returns_data = proc.get("returns", ["None"])
+            returns_md = "\n".join(f"- {line}" for line in returns_data)
+
+            doc_md = f"""\
+### 📘 {label}
+
+**Purpose**  
+{proc.get("description", "No description available.")}
+
+**Format**  
+`{proc.get("format", label)}`
+
+**Parameters**  
+{param_lines}
+
+**Return value**  
+{returns_md}
+
+**Example**  
+```tcl
+{example_md}"""
+            items.append(
+                lsp.CompletionItem(
+                    label=label,
+                    kind=kind_enum,
+                    detail=f"{label} – {proc.get('description', '').split('.')[0]}",
+                    documentation=lsp.MarkupContent(
+                        kind=lsp.MarkupKind.Markdown, value=doc_md
+                    ),
+                )
+            )
+        return items
+
 
 standard_items = StandardCompletionItems()
diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py
index 1b6051b..fd662a2 100644
--- a/server/src/lsp_server.py
+++ b/server/src/lsp_server.py
@@ -96,7 +96,11 @@ def did_change(params: lsp.DidChangeTextDocumentParams) -> None:
 
 @LSP_SERVER.feature(lsp.TEXT_DOCUMENT_COMPLETION)
 def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]:
-    items = standard_items.tcl_keyword_list
+    items = (
+        standard_items.tcl_keyword_list
+        + standard_items.nx_procs
+        + standard_items.nx_variables
+    )
     return lsp.CompletionList(is_incomplete=False, items=items)
 
 

From f482c8db877954f462937c489aee93685a5cd6d2 Mon Sep 17 00:00:00 2001
From: Christoph Brandau 
Date: Thu, 17 Jul 2025 15:01:02 +0200
Subject: [PATCH 06/12] update tcl standard

---
 server/src/common/completion_list.json | 25 ++++++++++++++++++++-----
 server/src/common/load_data.py         |  6 +++++-
 server/src/lsp_server.py               |  4 ++--
 3 files changed, 27 insertions(+), 8 deletions(-)

diff --git a/server/src/common/completion_list.json b/server/src/common/completion_list.json
index f69debf..78a9971 100644
--- a/server/src/common/completion_list.json
+++ b/server/src/common/completion_list.json
@@ -1,10 +1,25 @@
 {
     "tcl": [
-        "proc",
-        "puts",
-        "if",
-        "else",
-        "elseif"
+        {
+            "label": "proc",
+            "kind": "keyword"
+        },
+        {
+            "label": "if",
+            "kind": "keyword"
+        },
+        {
+            "label": "else",
+            "kind": "keyword"
+        },
+        {
+            "label": "elseif",
+            "kind": "keyword"
+        },
+        {
+            "label": "puts",
+            "kind": "function"
+        }
     ],
     "MOM_procs": [
         {
diff --git a/server/src/common/load_data.py b/server/src/common/load_data.py
index 952f3fc..bd8643f 100644
--- a/server/src/common/load_data.py
+++ b/server/src/common/load_data.py
@@ -11,6 +11,7 @@ KIND_MAP = {
     "field": lsp.CompletionItemKind.Field,
     "module": lsp.CompletionItemKind.Module,
     "namespace": lsp.CompletionItemKind.Class,
+    "keyword": lsp.CompletionItemKind.Keyword,
 }
 
 
@@ -41,7 +42,10 @@ class StandardCompletionItems:
         items = []
         for i in data.get("tcl", []):
             items.append(
-                lsp.CompletionItem(label=i, kind=lsp.CompletionItemKind.Keyword)
+                lsp.CompletionItem(
+                    label=i.get("label", ""),
+                    kind=KIND_MAP.get(i.get("kind", ""), lsp.CompletionItemKind.Text),
+                )
             )
         return items
 
diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py
index fd662a2..07cec2a 100644
--- a/server/src/lsp_server.py
+++ b/server/src/lsp_server.py
@@ -74,13 +74,13 @@ TOOL_ARGS = []  # default arguments always passed to your tool.
 @LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_OPEN)
 def did_open(params: lsp.DidOpenTextDocumentParams) -> None:
     """LSP handler for textDocument/didOpen request."""
-    document = LSP_SERVER.workspace.get_document(params.text_document.uri)
+    document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
 
 
 @LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_SAVE)
 def did_save(params: lsp.DidSaveTextDocumentParams) -> None:
     """LSP handler for textDocument/didSave request."""
-    document = LSP_SERVER.workspace.get_document(params.text_document.uri)
+    document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
 
 
 @LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_CLOSE)

From e2ba07c8b4162e5ed9c066a4d6356dcd953421f2 Mon Sep 17 00:00:00 2001
From: christoph_xd 
Date: Thu, 17 Jul 2025 22:35:07 +0200
Subject: [PATCH 07/12] use lark

---
 server/src/parser/parser.py | 32 ++++++++++++++++++++++++++++++++
 server/src/parser/tcl.lark  | 27 +++++++++++++++++++++++++++
 2 files changed, 59 insertions(+)
 create mode 100644 server/src/parser/parser.py
 create mode 100644 server/src/parser/tcl.lark

diff --git a/server/src/parser/parser.py b/server/src/parser/parser.py
new file mode 100644
index 0000000..366230d
--- /dev/null
+++ b/server/src/parser/parser.py
@@ -0,0 +1,32 @@
+from lark import Lark, Transformer, Token, v_args
+from pathlib import Path
+
+GRAMMAR_FILE = Path(__file__).parent.joinpath("tcl.lark")
+TCL_GRAMMAR = GRAMMAR_FILE.read_text(encoding="utf-8")
+
+
+@v_args(inline=True)
+class TCLTransformer(Transformer):
+    def start(self, *stmts):
+        return list(stmts)
+
+    def command(self, *elements):
+        return list(elements)
+
+    def braced(self, *content):
+        return "{" + "".join(c for c in content) + "}"
+
+    def quoted(self, *content):
+        return '"' + "".join(c for c in content) + '"'
+
+    def variable(self, token):
+        return token.value
+
+    def cmdsubst(self, *inner):
+        return "[" + " ".join(inner) + "]"
+
+    def bare(self, token):
+        return token.value
+
+    def COMMENT(self, token):
+        return token.value
diff --git a/server/src/parser/tcl.lark b/server/src/parser/tcl.lark
new file mode 100644
index 0000000..b8a7353
--- /dev/null
+++ b/server/src/parser/tcl.lark
@@ -0,0 +1,27 @@
+start: statement*
+
+?statement: command | COMMENT
+
+COMMENT: /#[^\n]*/
+
+command: element+
+
+?element: braced
+| quoted
+| variable
+| cmdsubst
+| bare
+
+braced: "{" inner_braced* "}"
+inner_braced: /[^{}]+/ | braced
+
+quoted: '"' inner_quoted* '"'
+inner_quoted: /[^"{}]+/ | braced
+
+variable: "$" /[A-Za-z_][A-Za-z0-9_]*/
+cmdsubst: "[" start "]"
+
+bare: /[^\s{}$"]+/
+
+%import common.WS_INLINE
+%ignore WS_INLINE
\ No newline at end of file

From c38fedd897c57f0c7c07eef6166abd10dea0b1ae Mon Sep 17 00:00:00 2001
From: Christoph Brandau 
Date: Fri, 18 Jul 2025 18:03:12 +0200
Subject: [PATCH 08/12] add lark

---
 server/libs/lark-1.2.2.dist-info/INSTALLER    |    1 +
 server/libs/lark-1.2.2.dist-info/LICENSE      |   18 +
 server/libs/lark-1.2.2.dist-info/METADATA     |   47 +
 server/libs/lark-1.2.2.dist-info/RECORD       |   83 +
 server/libs/lark-1.2.2.dist-info/REQUESTED    |    0
 server/libs/lark-1.2.2.dist-info/WHEEL        |    5 +
 .../lark-1.2.2.dist-info/entry_points.txt     |    2 +
 .../libs/lark-1.2.2.dist-info/top_level.txt   |    1 +
 server/libs/lark/__init__.py                  |   38 +
 server/libs/lark/__pyinstaller/__init__.py    |    6 +
 server/libs/lark/__pyinstaller/hook-lark.py   |   14 +
 server/libs/lark/ast_utils.py                 |   59 +
 server/libs/lark/common.py                    |   86 +
 server/libs/lark/exceptions.py                |  292 ++++
 server/libs/lark/grammar.py                   |  130 ++
 server/libs/lark/grammars/__init__.py         |    0
 server/libs/lark/grammars/common.lark         |   59 +
 server/libs/lark/grammars/lark.lark           |   62 +
 server/libs/lark/grammars/python.lark         |  302 ++++
 server/libs/lark/grammars/unicode.lark        |    7 +
 server/libs/lark/indenter.py                  |  143 ++
 server/libs/lark/lark.py                      |  658 ++++++++
 server/libs/lark/lexer.py                     |  678 ++++++++
 server/libs/lark/load_grammar.py              | 1428 +++++++++++++++++
 server/libs/lark/parse_tree_builder.py        |  391 +++++
 server/libs/lark/parser_frontends.py          |  257 +++
 server/libs/lark/parsers/__init__.py          |    0
 server/libs/lark/parsers/cyk.py               |  340 ++++
 server/libs/lark/parsers/earley.py            |  317 ++++
 server/libs/lark/parsers/earley_common.py     |   42 +
 server/libs/lark/parsers/earley_forest.py     |  802 +++++++++
 server/libs/lark/parsers/grammar_analysis.py  |  203 +++
 server/libs/lark/parsers/lalr_analysis.py     |  332 ++++
 .../lark/parsers/lalr_interactive_parser.py   |  158 ++
 server/libs/lark/parsers/lalr_parser.py       |  122 ++
 server/libs/lark/parsers/lalr_parser_state.py |  110 ++
 server/libs/lark/parsers/xearley.py           |  165 ++
 server/libs/lark/py.typed                     |    0
 server/libs/lark/reconstruct.py               |  107 ++
 server/libs/lark/tools/__init__.py            |   70 +
 server/libs/lark/tools/nearley.py             |  202 +++
 server/libs/lark/tools/serialize.py           |   32 +
 server/libs/lark/tools/standalone.py          |  196 +++
 server/libs/lark/tree.py                      |  267 +++
 server/libs/lark/tree_matcher.py              |  186 +++
 server/libs/lark/tree_templates.py            |  180 +++
 server/libs/lark/utils.py                     |  346 ++++
 server/libs/lark/visitors.py                  |  596 +++++++
 server/requirements.in                        |    3 +-
 server/requirements.txt                       |    4 +
 server/src/common/completion_list.json        |   46 +
 server/src/parser/tcl.lark                    |    7 +-
 52 files changed, 9597 insertions(+), 3 deletions(-)
 create mode 100644 server/libs/lark-1.2.2.dist-info/INSTALLER
 create mode 100644 server/libs/lark-1.2.2.dist-info/LICENSE
 create mode 100644 server/libs/lark-1.2.2.dist-info/METADATA
 create mode 100644 server/libs/lark-1.2.2.dist-info/RECORD
 create mode 100644 server/libs/lark-1.2.2.dist-info/REQUESTED
 create mode 100644 server/libs/lark-1.2.2.dist-info/WHEEL
 create mode 100644 server/libs/lark-1.2.2.dist-info/entry_points.txt
 create mode 100644 server/libs/lark-1.2.2.dist-info/top_level.txt
 create mode 100644 server/libs/lark/__init__.py
 create mode 100644 server/libs/lark/__pyinstaller/__init__.py
 create mode 100644 server/libs/lark/__pyinstaller/hook-lark.py
 create mode 100644 server/libs/lark/ast_utils.py
 create mode 100644 server/libs/lark/common.py
 create mode 100644 server/libs/lark/exceptions.py
 create mode 100644 server/libs/lark/grammar.py
 create mode 100644 server/libs/lark/grammars/__init__.py
 create mode 100644 server/libs/lark/grammars/common.lark
 create mode 100644 server/libs/lark/grammars/lark.lark
 create mode 100644 server/libs/lark/grammars/python.lark
 create mode 100644 server/libs/lark/grammars/unicode.lark
 create mode 100644 server/libs/lark/indenter.py
 create mode 100644 server/libs/lark/lark.py
 create mode 100644 server/libs/lark/lexer.py
 create mode 100644 server/libs/lark/load_grammar.py
 create mode 100644 server/libs/lark/parse_tree_builder.py
 create mode 100644 server/libs/lark/parser_frontends.py
 create mode 100644 server/libs/lark/parsers/__init__.py
 create mode 100644 server/libs/lark/parsers/cyk.py
 create mode 100644 server/libs/lark/parsers/earley.py
 create mode 100644 server/libs/lark/parsers/earley_common.py
 create mode 100644 server/libs/lark/parsers/earley_forest.py
 create mode 100644 server/libs/lark/parsers/grammar_analysis.py
 create mode 100644 server/libs/lark/parsers/lalr_analysis.py
 create mode 100644 server/libs/lark/parsers/lalr_interactive_parser.py
 create mode 100644 server/libs/lark/parsers/lalr_parser.py
 create mode 100644 server/libs/lark/parsers/lalr_parser_state.py
 create mode 100644 server/libs/lark/parsers/xearley.py
 create mode 100644 server/libs/lark/py.typed
 create mode 100644 server/libs/lark/reconstruct.py
 create mode 100644 server/libs/lark/tools/__init__.py
 create mode 100644 server/libs/lark/tools/nearley.py
 create mode 100644 server/libs/lark/tools/serialize.py
 create mode 100644 server/libs/lark/tools/standalone.py
 create mode 100644 server/libs/lark/tree.py
 create mode 100644 server/libs/lark/tree_matcher.py
 create mode 100644 server/libs/lark/tree_templates.py
 create mode 100644 server/libs/lark/utils.py
 create mode 100644 server/libs/lark/visitors.py

diff --git a/server/libs/lark-1.2.2.dist-info/INSTALLER b/server/libs/lark-1.2.2.dist-info/INSTALLER
new file mode 100644
index 0000000..a1b589e
--- /dev/null
+++ b/server/libs/lark-1.2.2.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/server/libs/lark-1.2.2.dist-info/LICENSE b/server/libs/lark-1.2.2.dist-info/LICENSE
new file mode 100644
index 0000000..aaf210b
--- /dev/null
+++ b/server/libs/lark-1.2.2.dist-info/LICENSE
@@ -0,0 +1,18 @@
+Copyright © 2017 Erez Shinan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/server/libs/lark-1.2.2.dist-info/METADATA b/server/libs/lark-1.2.2.dist-info/METADATA
new file mode 100644
index 0000000..6d5f341
--- /dev/null
+++ b/server/libs/lark-1.2.2.dist-info/METADATA
@@ -0,0 +1,47 @@
+Metadata-Version: 2.1
+Name: lark
+Version: 1.2.2
+Summary: a modern parsing library
+Author-email: Erez Shinan 
+License: MIT
+Project-URL: Homepage, https://github.com/lark-parser/lark
+Project-URL: Download, https://github.com/lark-parser/lark/tarball/master
+Keywords: Earley,LALR,parser,parsing,ast
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Programming Language :: Python :: 3
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Text Processing :: General
+Classifier: Topic :: Text Processing :: Linguistic
+Classifier: License :: OSI Approved :: MIT License
+Requires-Python: >=3.8
+Description-Content-Type: text/markdown
+License-File: LICENSE
+Provides-Extra: atomic_cache
+Requires-Dist: atomicwrites ; extra == 'atomic_cache'
+Provides-Extra: interegular
+Requires-Dist: interegular <0.4.0,>=0.3.1 ; extra == 'interegular'
+Provides-Extra: nearley
+Requires-Dist: js2py ; extra == 'nearley'
+Provides-Extra: regex
+Requires-Dist: regex ; extra == 'regex'
+
+Lark is a modern general-purpose parsing library for Python.
+With Lark, you can parse any context-free grammar, efficiently, with very little code.
+Main Features:
+- Builds a parse-tree (AST) automagically, based on the structure of the grammar
+- Earley parser
+- Can parse all context-free grammars
+- Full support for ambiguous grammars
+- LALR(1) parser
+- Fast and light, competitive with PLY
+- Can generate a stand-alone parser
+- CYK parser, for highly ambiguous grammars
+- EBNF grammar
+- Unicode fully supported
+- Automatic line & column tracking
+- Standard library of terminals (strings, numbers, names, etc.)
+- Import grammars from Nearley.js
+- Extensive test suite
+- And much more!
+Since version 1.2, only Python versions 3.8 and up are supported.
diff --git a/server/libs/lark-1.2.2.dist-info/RECORD b/server/libs/lark-1.2.2.dist-info/RECORD
new file mode 100644
index 0000000..0ca7133
--- /dev/null
+++ b/server/libs/lark-1.2.2.dist-info/RECORD
@@ -0,0 +1,83 @@
+lark-1.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+lark-1.2.2.dist-info/LICENSE,sha256=Lu5g9S1OETV7-J5ysDTQUOKF5H_aE2HlZi-zIu4n13E,1055
+lark-1.2.2.dist-info/METADATA,sha256=S-69HuNJr0ktlvb7J5XE48ghb_6ahYn8ksdW9HcB-d0,1831
+lark-1.2.2.dist-info/RECORD,,
+lark-1.2.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+lark-1.2.2.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
+lark-1.2.2.dist-info/entry_points.txt,sha256=WXYg_uCUdFlxQDPUhli3HFah37bNNFQfXLdzCqsacGI,61
+lark-1.2.2.dist-info/top_level.txt,sha256=dyS6jg8hCHHkXWvsfcIMO8rjlv_bdzAxiE0lkkzJ5hk,5
+lark/__init__.py,sha256=bc0tK7h7XwHA-Y4vVeJoNIqSMA-MHVTihq8yy795WXo,744
+lark/__pycache__/__init__.cpython-311.pyc,,
+lark/__pycache__/ast_utils.cpython-311.pyc,,
+lark/__pycache__/common.cpython-311.pyc,,
+lark/__pycache__/exceptions.cpython-311.pyc,,
+lark/__pycache__/grammar.cpython-311.pyc,,
+lark/__pycache__/indenter.cpython-311.pyc,,
+lark/__pycache__/lark.cpython-311.pyc,,
+lark/__pycache__/lexer.cpython-311.pyc,,
+lark/__pycache__/load_grammar.cpython-311.pyc,,
+lark/__pycache__/parse_tree_builder.cpython-311.pyc,,
+lark/__pycache__/parser_frontends.cpython-311.pyc,,
+lark/__pycache__/reconstruct.cpython-311.pyc,,
+lark/__pycache__/tree.cpython-311.pyc,,
+lark/__pycache__/tree_matcher.cpython-311.pyc,,
+lark/__pycache__/tree_templates.cpython-311.pyc,,
+lark/__pycache__/utils.cpython-311.pyc,,
+lark/__pycache__/visitors.cpython-311.pyc,,
+lark/__pyinstaller/__init__.py,sha256=_PpFm44f_mwHlCpvYgv9ZgubLfNDc3PlePVir4sxRfI,182
+lark/__pyinstaller/__pycache__/__init__.cpython-311.pyc,,
+lark/__pyinstaller/__pycache__/hook-lark.cpython-311.pyc,,
+lark/__pyinstaller/hook-lark.py,sha256=5aFHiZWVHPRdHT8qnb4kW4JSOql5GusHodHR25_q9sU,599
+lark/ast_utils.py,sha256=jwn44ocNQhZGbfcFsEZnwi_gGvPbNgzjQ-0RuEtwDzI,2117
+lark/common.py,sha256=M9-CFAUP3--OkftyyWjke-Kc1-pQMczT1MluHCFwdy4,3008
+lark/exceptions.py,sha256=g76ygMPfSMl6ukKqFAZVpR2EAJTOOdyfJ_ALXc_MCR8,10939
+lark/grammar.py,sha256=DR17QSLSKCRhMOqx2UQh4n-Ywu4CD-wjdQxtuM8OHkY,3665
+lark/grammars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+lark/grammars/__pycache__/__init__.cpython-311.pyc,,
+lark/grammars/common.lark,sha256=FV9xGIPiPqHRM4ULAxP6jApXRTVsSwbOe697I9s7DLs,885
+lark/grammars/lark.lark,sha256=nq1NTZYqm_DPI2mjRIlpd3ZcxPjGhapA4GUzkcfBTQs,1541
+lark/grammars/python.lark,sha256=WMakTkpzCqOd0jUjYONI3LOnSy2KRN9NoL9pFtAZYCI,10641
+lark/grammars/unicode.lark,sha256=d9YCz0XWimdl4F8M5YCptavBcFG9D58Yd4aMwxjYtEI,96
+lark/indenter.py,sha256=L5uNDYUMNrk4ZTWKmW0Tu-H-3GGErLOHygMC32N_twE,4221
+lark/lark.py,sha256=_IHWmTxt43kfd9eYVtwx58zEWWSFAq9_gKH7Oeu5PZs,28184
+lark/lexer.py,sha256=OwgQPCpQ-vUi-2aeZztsydd4DLkEgCbZeucvEPvHFi4,24037
+lark/load_grammar.py,sha256=WYZDxyO6omhA8NKyMjSckfAMwVKuIMF3liiYXE_-kHo,53946
+lark/parse_tree_builder.py,sha256=jT_3gCEkBGZoTXAWSnhMn1kRuJILWB-E7XkUciYNHI4,14412
+lark/parser_frontends.py,sha256=mxMXxux2hkfTfE859wuVp4-Fr1no6YVEUt8toDjEdPQ,10165
+lark/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+lark/parsers/__pycache__/__init__.cpython-311.pyc,,
+lark/parsers/__pycache__/cyk.cpython-311.pyc,,
+lark/parsers/__pycache__/earley.cpython-311.pyc,,
+lark/parsers/__pycache__/earley_common.cpython-311.pyc,,
+lark/parsers/__pycache__/earley_forest.cpython-311.pyc,,
+lark/parsers/__pycache__/grammar_analysis.cpython-311.pyc,,
+lark/parsers/__pycache__/lalr_analysis.cpython-311.pyc,,
+lark/parsers/__pycache__/lalr_interactive_parser.cpython-311.pyc,,
+lark/parsers/__pycache__/lalr_parser.cpython-311.pyc,,
+lark/parsers/__pycache__/lalr_parser_state.cpython-311.pyc,,
+lark/parsers/__pycache__/xearley.cpython-311.pyc,,
+lark/parsers/cyk.py,sha256=c3GLk3kq23Xwb8MqUOjvivwP488KJY6NUWgxqeR5980,12192
+lark/parsers/earley.py,sha256=03sW9vfBkcH4NR72EBt8HkndDKSVSH3IdRnDulXWy24,15117
+lark/parsers/earley_common.py,sha256=e2e6NrNucw-WMiNV8HqQ_TpGx6P7v_S8f5aEcF0Tkqo,1620
+lark/parsers/earley_forest.py,sha256=w4JTb4tVMewue8dL-gCO96-Uo0wd4BbQUfSfIhr7txY,31332
+lark/parsers/grammar_analysis.py,sha256=rQ4Sn9EP8gjXGTZXEiWLW0KByPPpeKpN5hSIQZgNl3I,7141
+lark/parsers/lalr_analysis.py,sha256=DGHFk2tIluIyeFEVFfsMRU77DVbd598IJnUUOXO04yo,12207
+lark/parsers/lalr_interactive_parser.py,sha256=LsgfT1gdne8pXHTCsN6bl6zD6Pdh2dDqp1rIWOzp7Yw,5757
+lark/parsers/lalr_parser.py,sha256=6U8jP1AlUsuGxgJBWMq15WuGuyaolsLPevcf8HZ_zZk,4586
+lark/parsers/lalr_parser_state.py,sha256=QZ12p4CtvcvFAIKIqkeDBJYgEU3ntQllBJDYXb419ls,3793
+lark/parsers/xearley.py,sha256=DboXMNtuN0G-SXrrDm5zgUDUekz85h0Rih2PRvcf1LM,7825
+lark/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+lark/reconstruct.py,sha256=s7CevBXchUG_fe2otdAITxIaSXCEIiSjy4Sbh5QC0hs,3763
+lark/tools/__init__.py,sha256=FeKYmVUjXSt-vlQm2ktyWkcxaOCTOkZnHD_kOUWjUuA,2469
+lark/tools/__pycache__/__init__.cpython-311.pyc,,
+lark/tools/__pycache__/nearley.cpython-311.pyc,,
+lark/tools/__pycache__/serialize.cpython-311.pyc,,
+lark/tools/__pycache__/standalone.cpython-311.pyc,,
+lark/tools/nearley.py,sha256=QaLYdW6mYQdDq8JKMisV3lvPqzF0wPgu8q8BtsSA33g,6265
+lark/tools/serialize.py,sha256=nwt46LNxkDm0T_Uh9k2wS4fcfgvZQ2dy4-YC_aKhTQk,965
+lark/tools/standalone.py,sha256=6eXDqBuzZSpE5BGZm_Fh6X5yRhAPYxNVyl2aUU3ABzA,5627
+lark/tree.py,sha256=aWWHMazid8bbJanhmCjK9XK2jRFJ6N6WmlwXJGTsz28,8522
+lark/tree_matcher.py,sha256=jHdZJggn405SXmPpGf9U9HLrrsfP4eNNZaj267UTB00,6003
+lark/tree_templates.py,sha256=sSnfw1m8txAkJOYhcQrooG7xajVyVplunzTnNsxY720,6139
+lark/utils.py,sha256=3qd1-c0YgHYklvx1hA28qF7N_Ty1Zz6TbtCFMzQanNk,11270
+lark/visitors.py,sha256=VJ3T1m8p78MwXJotpOAvn06mYEqKyuIlhsAF51U-a3w,21422
diff --git a/server/libs/lark-1.2.2.dist-info/REQUESTED b/server/libs/lark-1.2.2.dist-info/REQUESTED
new file mode 100644
index 0000000..e69de29
diff --git a/server/libs/lark-1.2.2.dist-info/WHEEL b/server/libs/lark-1.2.2.dist-info/WHEEL
new file mode 100644
index 0000000..71360e0
--- /dev/null
+++ b/server/libs/lark-1.2.2.dist-info/WHEEL
@@ -0,0 +1,5 @@
+Wheel-Version: 1.0
+Generator: setuptools (72.2.0)
+Root-Is-Purelib: true
+Tag: py3-none-any
+
diff --git a/server/libs/lark-1.2.2.dist-info/entry_points.txt b/server/libs/lark-1.2.2.dist-info/entry_points.txt
new file mode 100644
index 0000000..ec317d7
--- /dev/null
+++ b/server/libs/lark-1.2.2.dist-info/entry_points.txt
@@ -0,0 +1,2 @@
+[pyinstaller40]
+hook-dirs = lark.__pyinstaller:get_hook_dirs
diff --git a/server/libs/lark-1.2.2.dist-info/top_level.txt b/server/libs/lark-1.2.2.dist-info/top_level.txt
new file mode 100644
index 0000000..bc30e96
--- /dev/null
+++ b/server/libs/lark-1.2.2.dist-info/top_level.txt
@@ -0,0 +1 @@
+lark
diff --git a/server/libs/lark/__init__.py b/server/libs/lark/__init__.py
new file mode 100644
index 0000000..d22cc2d
--- /dev/null
+++ b/server/libs/lark/__init__.py
@@ -0,0 +1,38 @@
+from .exceptions import (
+    GrammarError,
+    LarkError,
+    LexError,
+    ParseError,
+    UnexpectedCharacters,
+    UnexpectedEOF,
+    UnexpectedInput,
+    UnexpectedToken,
+)
+from .lark import Lark
+from .lexer import Token
+from .tree import ParseTree, Tree
+from .utils import logger
+from .visitors import Discard, Transformer, Transformer_NonRecursive, Visitor, v_args
+
+__version__: str = "1.2.2"
+
+__all__ = (
+    "GrammarError",
+    "LarkError",
+    "LexError",
+    "ParseError",
+    "UnexpectedCharacters",
+    "UnexpectedEOF",
+    "UnexpectedInput",
+    "UnexpectedToken",
+    "Lark",
+    "Token",
+    "ParseTree",
+    "Tree",
+    "logger",
+    "Discard",
+    "Transformer",
+    "Transformer_NonRecursive",
+    "Visitor",
+    "v_args",
+)
diff --git a/server/libs/lark/__pyinstaller/__init__.py b/server/libs/lark/__pyinstaller/__init__.py
new file mode 100644
index 0000000..9da62a3
--- /dev/null
+++ b/server/libs/lark/__pyinstaller/__init__.py
@@ -0,0 +1,6 @@
+# For usage of lark with PyInstaller. See https://pyinstaller-sample-hook.readthedocs.io/en/latest/index.html
+
+import os
+
+def get_hook_dirs():
+    return [os.path.dirname(__file__)]
diff --git a/server/libs/lark/__pyinstaller/hook-lark.py b/server/libs/lark/__pyinstaller/hook-lark.py
new file mode 100644
index 0000000..cf3d8e3
--- /dev/null
+++ b/server/libs/lark/__pyinstaller/hook-lark.py
@@ -0,0 +1,14 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2017-2020, PyInstaller Development Team.
+#
+# Distributed under the terms of the GNU General Public License (version 2
+# or later) with exception for distributing the bootloader.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#
+# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
+#-----------------------------------------------------------------------------
+
+from PyInstaller.utils.hooks import collect_data_files
+
+datas = collect_data_files('lark')
diff --git a/server/libs/lark/ast_utils.py b/server/libs/lark/ast_utils.py
new file mode 100644
index 0000000..a5460f3
--- /dev/null
+++ b/server/libs/lark/ast_utils.py
@@ -0,0 +1,59 @@
+"""
+    Module of utilities for transforming a lark.Tree into a custom Abstract Syntax Tree (AST defined in classes)
+"""
+
+import inspect, re
+import types
+from typing import Optional, Callable
+
+from lark import Transformer, v_args
+
+class Ast:
+    """Abstract class
+
+    Subclasses will be collected by `create_transformer()`
+    """
+    pass
+
+class AsList:
+    """Abstract class
+
+    Subclasses will be instantiated with the parse results as a single list, instead of as arguments.
+    """
+
+class WithMeta:
+    """Abstract class
+
+    Subclasses will be instantiated with the Meta instance of the tree. (see ``v_args`` for more detail)
+    """
+    pass
+
+def camel_to_snake(name):
+    return re.sub(r'(? Transformer:
+    """Collects `Ast` subclasses from the given module, and creates a Lark transformer that builds the AST.
+
+    For each class, we create a corresponding rule in the transformer, with a matching name.
+    CamelCase names will be converted into snake_case. Example: "CodeBlock" -> "code_block".
+
+    Classes starting with an underscore (`_`) will be skipped.
+
+    Parameters:
+        ast_module: A Python module containing all the subclasses of ``ast_utils.Ast``
+        transformer (Optional[Transformer]): An initial transformer. Its attributes may be overwritten.
+        decorator_factory (Callable): An optional callable accepting two booleans, inline, and meta,
+            and returning a decorator for the methods of ``transformer``. (default: ``v_args``).
+    """
+    t = transformer or Transformer()
+
+    for name, obj in inspect.getmembers(ast_module):
+        if not name.startswith('_') and inspect.isclass(obj):
+            if issubclass(obj, Ast):
+                wrapper = decorator_factory(inline=not issubclass(obj, AsList), meta=issubclass(obj, WithMeta))
+                obj = wrapper(obj).__get__(t)
+                setattr(t, camel_to_snake(name), obj)
+
+    return t
diff --git a/server/libs/lark/common.py b/server/libs/lark/common.py
new file mode 100644
index 0000000..71b6a4c
--- /dev/null
+++ b/server/libs/lark/common.py
@@ -0,0 +1,86 @@
+from copy import deepcopy
+import sys
+from types import ModuleType
+from typing import Callable, Collection, Dict, Optional, TYPE_CHECKING, List
+
+if TYPE_CHECKING:
+    from .lark import PostLex
+    from .lexer import Lexer
+    from .grammar import Rule
+    from typing import Union, Type
+    from typing import Literal
+    if sys.version_info >= (3, 10):
+        from typing import TypeAlias
+    else:
+        from typing_extensions import TypeAlias
+
+from .utils import Serialize
+from .lexer import TerminalDef, Token
+
+###{standalone
+
+_ParserArgType: 'TypeAlias' = 'Literal["earley", "lalr", "cyk", "auto"]'
+_LexerArgType: 'TypeAlias' = 'Union[Literal["auto", "basic", "contextual", "dynamic", "dynamic_complete"], Type[Lexer]]'
+_LexerCallback = Callable[[Token], Token]
+ParserCallbacks = Dict[str, Callable]
+
+class LexerConf(Serialize):
+    __serialize_fields__ = 'terminals', 'ignore', 'g_regex_flags', 'use_bytes', 'lexer_type'
+    __serialize_namespace__ = TerminalDef,
+
+    terminals: Collection[TerminalDef]
+    re_module: ModuleType
+    ignore: Collection[str]
+    postlex: 'Optional[PostLex]'
+    callbacks: Dict[str, _LexerCallback]
+    g_regex_flags: int
+    skip_validation: bool
+    use_bytes: bool
+    lexer_type: Optional[_LexerArgType]
+    strict: bool
+
+    def __init__(self, terminals: Collection[TerminalDef], re_module: ModuleType, ignore: Collection[str]=(), postlex: 'Optional[PostLex]'=None,
+                 callbacks: Optional[Dict[str, _LexerCallback]]=None, g_regex_flags: int=0, skip_validation: bool=False, use_bytes: bool=False, strict: bool=False):
+        self.terminals = terminals
+        self.terminals_by_name = {t.name: t for t in self.terminals}
+        assert len(self.terminals) == len(self.terminals_by_name)
+        self.ignore = ignore
+        self.postlex = postlex
+        self.callbacks = callbacks or {}
+        self.g_regex_flags = g_regex_flags
+        self.re_module = re_module
+        self.skip_validation = skip_validation
+        self.use_bytes = use_bytes
+        self.strict = strict
+        self.lexer_type = None
+
+    def _deserialize(self):
+        self.terminals_by_name = {t.name: t for t in self.terminals}
+
+    def __deepcopy__(self, memo=None):
+        return type(self)(
+            deepcopy(self.terminals, memo),
+            self.re_module,
+            deepcopy(self.ignore, memo),
+            deepcopy(self.postlex, memo),
+            deepcopy(self.callbacks, memo),
+            deepcopy(self.g_regex_flags, memo),
+            deepcopy(self.skip_validation, memo),
+            deepcopy(self.use_bytes, memo),
+        )
+
+class ParserConf(Serialize):
+    __serialize_fields__ = 'rules', 'start', 'parser_type'
+
+    rules: List['Rule']
+    callbacks: ParserCallbacks
+    start: List[str]
+    parser_type: _ParserArgType
+
+    def __init__(self, rules: List['Rule'], callbacks: ParserCallbacks, start: List[str]):
+        assert isinstance(start, list)
+        self.rules = rules
+        self.callbacks = callbacks
+        self.start = start
+
+###}
diff --git a/server/libs/lark/exceptions.py b/server/libs/lark/exceptions.py
new file mode 100644
index 0000000..e099d59
--- /dev/null
+++ b/server/libs/lark/exceptions.py
@@ -0,0 +1,292 @@
+from .utils import logger, NO_VALUE
+from typing import Mapping, Iterable, Callable, Union, TypeVar, Tuple, Any, List, Set, Optional, Collection, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from .lexer import Token
+    from .parsers.lalr_interactive_parser import InteractiveParser
+    from .tree import Tree
+
+###{standalone
+
+class LarkError(Exception):
+    pass
+
+
+class ConfigurationError(LarkError, ValueError):
+    pass
+
+
+def assert_config(value, options: Collection, msg='Got %r, expected one of %s'):
+    if value not in options:
+        raise ConfigurationError(msg % (value, options))
+
+
+class GrammarError(LarkError):
+    pass
+
+
+class ParseError(LarkError):
+    pass
+
+
+class LexError(LarkError):
+    pass
+
+T = TypeVar('T')
+
+class UnexpectedInput(LarkError):
+    """UnexpectedInput Error.
+
+    Used as a base class for the following exceptions:
+
+    - ``UnexpectedCharacters``: The lexer encountered an unexpected string
+    - ``UnexpectedToken``: The parser received an unexpected token
+    - ``UnexpectedEOF``: The parser expected a token, but the input ended
+
+    After catching one of these exceptions, you may call the following helper methods to create a nicer error message.
+    """
+    line: int
+    column: int
+    pos_in_stream = None
+    state: Any
+    _terminals_by_name = None
+    interactive_parser: 'InteractiveParser'
+
+    def get_context(self, text: str, span: int=40) -> str:
+        """Returns a pretty string pinpointing the error in the text,
+        with span amount of context characters around it.
+
+        Note:
+            The parser doesn't hold a copy of the text it has to parse,
+            so you have to provide it again
+        """
+        assert self.pos_in_stream is not None, self
+        pos = self.pos_in_stream
+        start = max(pos - span, 0)
+        end = pos + span
+        if not isinstance(text, bytes):
+            before = text[start:pos].rsplit('\n', 1)[-1]
+            after = text[pos:end].split('\n', 1)[0]
+            return before + after + '\n' + ' ' * len(before.expandtabs()) + '^\n'
+        else:
+            before = text[start:pos].rsplit(b'\n', 1)[-1]
+            after = text[pos:end].split(b'\n', 1)[0]
+            return (before + after + b'\n' + b' ' * len(before.expandtabs()) + b'^\n').decode("ascii", "backslashreplace")
+
+    def match_examples(self, parse_fn: 'Callable[[str], Tree]',
+                             examples: Union[Mapping[T, Iterable[str]], Iterable[Tuple[T, Iterable[str]]]],
+                             token_type_match_fallback: bool=False,
+                             use_accepts: bool=True
+                         ) -> Optional[T]:
+        """Allows you to detect what's wrong in the input text by matching
+        against example errors.
+
+        Given a parser instance and a dictionary mapping some label with
+        some malformed syntax examples, it'll return the label for the
+        example that bests matches the current error. The function will
+        iterate the dictionary until it finds a matching error, and
+        return the corresponding value.
+
+        For an example usage, see `examples/error_reporting_lalr.py`
+
+        Parameters:
+            parse_fn: parse function (usually ``lark_instance.parse``)
+            examples: dictionary of ``{'example_string': value}``.
+            use_accepts: Recommended to keep this as ``use_accepts=True``.
+        """
+        assert self.state is not None, "Not supported for this exception"
+
+        if isinstance(examples, Mapping):
+            examples = examples.items()
+
+        candidate = (None, False)
+        for i, (label, example) in enumerate(examples):
+            assert not isinstance(example, str), "Expecting a list"
+
+            for j, malformed in enumerate(example):
+                try:
+                    parse_fn(malformed)
+                except UnexpectedInput as ut:
+                    if ut.state == self.state:
+                        if (
+                            use_accepts
+                            and isinstance(self, UnexpectedToken)
+                            and isinstance(ut, UnexpectedToken)
+                            and ut.accepts != self.accepts
+                        ):
+                            logger.debug("Different accepts with same state[%d]: %s != %s at example [%s][%s]" %
+                                         (self.state, self.accepts, ut.accepts, i, j))
+                            continue
+                        if (
+                            isinstance(self, (UnexpectedToken, UnexpectedEOF))
+                            and isinstance(ut, (UnexpectedToken, UnexpectedEOF))
+                        ):
+                            if ut.token == self.token:  # Try exact match first
+                                logger.debug("Exact Match at example [%s][%s]" % (i, j))
+                                return label
+
+                            if token_type_match_fallback:
+                                # Fallback to token types match
+                                if (ut.token.type == self.token.type) and not candidate[-1]:
+                                    logger.debug("Token Type Fallback at example [%s][%s]" % (i, j))
+                                    candidate = label, True
+
+                        if candidate[0] is None:
+                            logger.debug("Same State match at example [%s][%s]" % (i, j))
+                            candidate = label, False
+
+        return candidate[0]
+
+    def _format_expected(self, expected):
+        if self._terminals_by_name:
+            d = self._terminals_by_name
+            expected = [d[t_name].user_repr() if t_name in d else t_name for t_name in expected]
+        return "Expected one of: \n\t* %s\n" % '\n\t* '.join(expected)
+
+
+class UnexpectedEOF(ParseError, UnexpectedInput):
+    """An exception that is raised by the parser, when the input ends while it still expects a token.
+    """
+    expected: 'List[Token]'
+
+    def __init__(self, expected, state=None, terminals_by_name=None):
+        super(UnexpectedEOF, self).__init__()
+
+        self.expected = expected
+        self.state = state
+        from .lexer import Token
+        self.token = Token("", "")  # , line=-1, column=-1, pos_in_stream=-1)
+        self.pos_in_stream = -1
+        self.line = -1
+        self.column = -1
+        self._terminals_by_name = terminals_by_name
+
+
+    def __str__(self):
+        message = "Unexpected end-of-input. "
+        message += self._format_expected(self.expected)
+        return message
+
+
+class UnexpectedCharacters(LexError, UnexpectedInput):
+    """An exception that is raised by the lexer, when it cannot match the next
+    string of characters to any of its terminals.
+    """
+
+    allowed: Set[str]
+    considered_tokens: Set[Any]
+
+    def __init__(self, seq, lex_pos, line, column, allowed=None, considered_tokens=None, state=None, token_history=None,
+                 terminals_by_name=None, considered_rules=None):
+        super(UnexpectedCharacters, self).__init__()
+
+        # TODO considered_tokens and allowed can be figured out using state
+        self.line = line
+        self.column = column
+        self.pos_in_stream = lex_pos
+        self.state = state
+        self._terminals_by_name = terminals_by_name
+
+        self.allowed = allowed
+        self.considered_tokens = considered_tokens
+        self.considered_rules = considered_rules
+        self.token_history = token_history
+
+        if isinstance(seq, bytes):
+            self.char = seq[lex_pos:lex_pos + 1].decode("ascii", "backslashreplace")
+        else:
+            self.char = seq[lex_pos]
+        self._context = self.get_context(seq)
+
+
+    def __str__(self):
+        message = "No terminal matches '%s' in the current parser context, at line %d col %d" % (self.char, self.line, self.column)
+        message += '\n\n' + self._context
+        if self.allowed:
+            message += self._format_expected(self.allowed)
+        if self.token_history:
+            message += '\nPrevious tokens: %s\n' % ', '.join(repr(t) for t in self.token_history)
+        return message
+
+
+class UnexpectedToken(ParseError, UnexpectedInput):
+    """An exception that is raised by the parser, when the token it received
+    doesn't match any valid step forward.
+
+    Parameters:
+        token: The mismatched token
+        expected: The set of expected tokens
+        considered_rules: Which rules were considered, to deduce the expected tokens
+        state: A value representing the parser state. Do not rely on its value or type.
+        interactive_parser: An instance of ``InteractiveParser``, that is initialized to the point of failure,
+                            and can be used for debugging and error handling.
+
+    Note: These parameters are available as attributes of the instance.
+    """
+
+    expected: Set[str]
+    considered_rules: Set[str]
+
+    def __init__(self, token, expected, considered_rules=None, state=None, interactive_parser=None, terminals_by_name=None, token_history=None):
+        super(UnexpectedToken, self).__init__()
+
+        # TODO considered_rules and expected can be figured out using state
+        self.line = getattr(token, 'line', '?')
+        self.column = getattr(token, 'column', '?')
+        self.pos_in_stream = getattr(token, 'start_pos', None)
+        self.state = state
+
+        self.token = token
+        self.expected = expected  # XXX deprecate? `accepts` is better
+        self._accepts = NO_VALUE
+        self.considered_rules = considered_rules
+        self.interactive_parser = interactive_parser
+        self._terminals_by_name = terminals_by_name
+        self.token_history = token_history
+
+
+    @property
+    def accepts(self) -> Set[str]:
+        if self._accepts is NO_VALUE:
+            self._accepts = self.interactive_parser and self.interactive_parser.accepts()
+        return self._accepts
+
+    def __str__(self):
+        message = ("Unexpected token %r at line %s, column %s.\n%s"
+                   % (self.token, self.line, self.column, self._format_expected(self.accepts or self.expected)))
+        if self.token_history:
+            message += "Previous tokens: %r\n" % self.token_history
+
+        return message
+
+
+
+class VisitError(LarkError):
+    """VisitError is raised when visitors are interrupted by an exception
+
+    It provides the following attributes for inspection:
+
+    Parameters:
+        rule: the name of the visit rule that failed
+        obj: the tree-node or token that was being processed
+        orig_exc: the exception that cause it to fail
+
+    Note: These parameters are available as attributes
+    """
+
+    obj: 'Union[Tree, Token]'
+    orig_exc: Exception
+
+    def __init__(self, rule, obj, orig_exc):
+        message = 'Error trying to process rule "%s":\n\n%s' % (rule, orig_exc)
+        super(VisitError, self).__init__(message)
+
+        self.rule = rule
+        self.obj = obj
+        self.orig_exc = orig_exc
+
+
+class MissingVariableError(LarkError):
+    pass
+
+###}
diff --git a/server/libs/lark/grammar.py b/server/libs/lark/grammar.py
new file mode 100644
index 0000000..1d226d9
--- /dev/null
+++ b/server/libs/lark/grammar.py
@@ -0,0 +1,130 @@
+from typing import Optional, Tuple, ClassVar, Sequence
+
+from .utils import Serialize
+
+###{standalone
+TOKEN_DEFAULT_PRIORITY = 0
+
+
+class Symbol(Serialize):
+    __slots__ = ('name',)
+
+    name: str
+    is_term: ClassVar[bool] = NotImplemented
+
+    def __init__(self, name: str) -> None:
+        self.name = name
+
+    def __eq__(self, other):
+        assert isinstance(other, Symbol), other
+        return self.is_term == other.is_term and self.name == other.name
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __hash__(self):
+        return hash(self.name)
+
+    def __repr__(self):
+        return '%s(%r)' % (type(self).__name__, self.name)
+
+    fullrepr = property(__repr__)
+
+    def renamed(self, f):
+        return type(self)(f(self.name))
+
+
+class Terminal(Symbol):
+    __serialize_fields__ = 'name', 'filter_out'
+
+    is_term: ClassVar[bool] = True
+
+    def __init__(self, name, filter_out=False):
+        self.name = name
+        self.filter_out = filter_out
+
+    @property
+    def fullrepr(self):
+        return '%s(%r, %r)' % (type(self).__name__, self.name, self.filter_out)
+
+    def renamed(self, f):
+        return type(self)(f(self.name), self.filter_out)
+
+
+class NonTerminal(Symbol):
+    __serialize_fields__ = 'name',
+
+    is_term: ClassVar[bool] = False
+
+
+class RuleOptions(Serialize):
+    __serialize_fields__ = 'keep_all_tokens', 'expand1', 'priority', 'template_source', 'empty_indices'
+
+    keep_all_tokens: bool
+    expand1: bool
+    priority: Optional[int]
+    template_source: Optional[str]
+    empty_indices: Tuple[bool, ...]
+
+    def __init__(self, keep_all_tokens: bool=False, expand1: bool=False, priority: Optional[int]=None, template_source: Optional[str]=None, empty_indices: Tuple[bool, ...]=()) -> None:
+        self.keep_all_tokens = keep_all_tokens
+        self.expand1 = expand1
+        self.priority = priority
+        self.template_source = template_source
+        self.empty_indices = empty_indices
+
+    def __repr__(self):
+        return 'RuleOptions(%r, %r, %r, %r)' % (
+            self.keep_all_tokens,
+            self.expand1,
+            self.priority,
+            self.template_source
+        )
+
+
+class Rule(Serialize):
+    """
+        origin : a symbol
+        expansion : a list of symbols
+        order : index of this expansion amongst all rules of the same name
+    """
+    __slots__ = ('origin', 'expansion', 'alias', 'options', 'order', '_hash')
+
+    __serialize_fields__ = 'origin', 'expansion', 'order', 'alias', 'options'
+    __serialize_namespace__ = Terminal, NonTerminal, RuleOptions
+
+    origin: NonTerminal
+    expansion: Sequence[Symbol]
+    order: int
+    alias: Optional[str]
+    options: RuleOptions
+    _hash: int
+
+    def __init__(self, origin: NonTerminal, expansion: Sequence[Symbol],
+                 order: int=0, alias: Optional[str]=None, options: Optional[RuleOptions]=None):
+        self.origin = origin
+        self.expansion = expansion
+        self.alias = alias
+        self.order = order
+        self.options = options or RuleOptions()
+        self._hash = hash((self.origin, tuple(self.expansion)))
+
+    def _deserialize(self):
+        self._hash = hash((self.origin, tuple(self.expansion)))
+
+    def __str__(self):
+        return '<%s : %s>' % (self.origin.name, ' '.join(x.name for x in self.expansion))
+
+    def __repr__(self):
+        return 'Rule(%r, %r, %r, %r)' % (self.origin, self.expansion, self.alias, self.options)
+
+    def __hash__(self):
+        return self._hash
+
+    def __eq__(self, other):
+        if not isinstance(other, Rule):
+            return False
+        return self.origin == other.origin and self.expansion == other.expansion
+
+
+###}
diff --git a/server/libs/lark/grammars/__init__.py b/server/libs/lark/grammars/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/server/libs/lark/grammars/common.lark b/server/libs/lark/grammars/common.lark
new file mode 100644
index 0000000..d2e86d1
--- /dev/null
+++ b/server/libs/lark/grammars/common.lark
@@ -0,0 +1,59 @@
+// Basic terminals for common use
+
+
+//
+// Numbers
+//
+
+DIGIT: "0".."9"
+HEXDIGIT: "a".."f"|"A".."F"|DIGIT
+
+INT: DIGIT+
+SIGNED_INT: ["+"|"-"] INT
+DECIMAL: INT "." INT? | "." INT
+
+// float = /-?\d+(\.\d+)?([eE][+-]?\d+)?/
+_EXP: ("e"|"E") SIGNED_INT
+FLOAT: INT _EXP | DECIMAL _EXP?
+SIGNED_FLOAT: ["+"|"-"] FLOAT
+
+NUMBER: FLOAT | INT
+SIGNED_NUMBER: ["+"|"-"] NUMBER
+
+//
+// Strings
+//
+_STRING_INNER: /.*?/
+_STRING_ESC_INNER: _STRING_INNER /(? ignore
+         | "%import" import_path ["->" name]       -> import
+         | "%import" import_path name_list         -> multi_import
+         | "%override" rule                        -> override_rule
+         | "%declare" name+                        -> declare
+
+!import_path: "."? name ("." name)*
+name_list: "(" name ("," name)* ")"
+
+?expansions: alias (_VBAR alias)*
+
+?alias: expansion ["->" RULE]
+
+?expansion: expr*
+
+?expr: atom [OP | "~" NUMBER [".." NUMBER]]
+
+?atom: "(" expansions ")"
+     | "[" expansions "]" -> maybe
+     | value
+
+?value: STRING ".." STRING -> literal_range
+      | name
+      | (REGEXP | STRING) -> literal
+      | name "{" value ("," value)* "}" -> template_usage
+
+name: RULE
+    | TOKEN
+
+_VBAR: _NL? "|"
+OP: /[+*]|[?](?![a-z])/
+RULE: /!?[_?]?[a-z][_a-z0-9]*/
+TOKEN: /_?[A-Z][_A-Z0-9]*/
+STRING: _STRING "i"?
+REGEXP: /\/(?!\/)(\\\/|\\\\|[^\/])*?\/[imslux]*/
+_NL: /(\r?\n)+\s*/
+
+%import common.ESCAPED_STRING -> _STRING
+%import common.SIGNED_INT -> NUMBER
+%import common.WS_INLINE
+
+COMMENT: /\s*/ "//" /[^\n]/* | /\s*/ "#" /[^\n]/*
+
+%ignore WS_INLINE
+%ignore COMMENT
diff --git a/server/libs/lark/grammars/python.lark b/server/libs/lark/grammars/python.lark
new file mode 100644
index 0000000..8a75966
--- /dev/null
+++ b/server/libs/lark/grammars/python.lark
@@ -0,0 +1,302 @@
+// Python 3 grammar for Lark
+
+// This grammar should parse all python 3.x code successfully.
+
+// Adapted from: https://docs.python.org/3/reference/grammar.html
+
+// Start symbols for the grammar:
+//       single_input is a single interactive statement;
+//       file_input is a module or sequence of commands read from an input file;
+//       eval_input is the input for the eval() functions.
+// NB: compound_stmt in single_input is followed by extra NEWLINE!
+//
+
+single_input: _NEWLINE | simple_stmt | compound_stmt _NEWLINE
+file_input: (_NEWLINE | stmt)*
+eval_input: testlist _NEWLINE*
+
+decorator: "@" dotted_name [ "(" [arguments] ")" ] _NEWLINE
+decorators: decorator+
+decorated: decorators (classdef | funcdef | async_funcdef)
+
+async_funcdef: "async" funcdef
+funcdef: "def" name "(" [parameters] ")" ["->" test] ":" suite
+
+parameters: paramvalue ("," paramvalue)* ["," SLASH ("," paramvalue)*] ["," [starparams | kwparams]]
+          | starparams
+          | kwparams
+
+SLASH: "/" // Otherwise the it will completely disappear and it will be undisguisable in the result
+starparams: (starparam | starguard) poststarparams
+starparam: "*" typedparam
+starguard: "*"
+poststarparams: ("," paramvalue)* ["," kwparams]
+kwparams: "**" typedparam ","?
+
+?paramvalue: typedparam ("=" test)?
+?typedparam: name (":" test)?
+
+
+lambdef: "lambda" [lambda_params] ":" test
+lambdef_nocond: "lambda" [lambda_params] ":" test_nocond
+lambda_params: lambda_paramvalue ("," lambda_paramvalue)* ["," [lambda_starparams | lambda_kwparams]]
+          | lambda_starparams
+          | lambda_kwparams
+?lambda_paramvalue: name ("=" test)?
+lambda_starparams: "*" [name]  ("," lambda_paramvalue)* ["," [lambda_kwparams]]
+lambda_kwparams: "**" name ","?
+
+
+?stmt: simple_stmt | compound_stmt
+?simple_stmt: small_stmt (";" small_stmt)* [";"] _NEWLINE
+?small_stmt: (expr_stmt | assign_stmt | del_stmt | pass_stmt | flow_stmt | import_stmt | global_stmt | nonlocal_stmt | assert_stmt)
+expr_stmt: testlist_star_expr
+assign_stmt: annassign | augassign | assign
+
+annassign: testlist_star_expr ":" test ["=" test]
+assign: testlist_star_expr ("=" (yield_expr|testlist_star_expr))+
+augassign: testlist_star_expr augassign_op (yield_expr|testlist)
+!augassign_op: "+=" | "-=" | "*=" | "@=" | "/=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" | "**=" | "//="
+?testlist_star_expr: test_or_star_expr
+                   | test_or_star_expr ("," test_or_star_expr)+ ","?  -> tuple
+                   | test_or_star_expr ","  -> tuple
+
+// For normal and annotated assignments, additional restrictions enforced by the interpreter
+del_stmt: "del" exprlist
+pass_stmt: "pass"
+?flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt
+break_stmt: "break"
+continue_stmt: "continue"
+return_stmt: "return" [testlist]
+yield_stmt: yield_expr
+raise_stmt: "raise" [test ["from" test]]
+import_stmt: import_name | import_from
+import_name: "import" dotted_as_names
+// note below: the ("." | "...") is necessary because "..." is tokenized as ELLIPSIS
+import_from: "from" (dots? dotted_name | dots) "import" ("*" | "(" import_as_names ")" | import_as_names)
+!dots: "."+
+import_as_name: name ["as" name]
+dotted_as_name: dotted_name ["as" name]
+import_as_names: import_as_name ("," import_as_name)* [","]
+dotted_as_names: dotted_as_name ("," dotted_as_name)*
+dotted_name: name ("." name)*
+global_stmt: "global" name ("," name)*
+nonlocal_stmt: "nonlocal" name ("," name)*
+assert_stmt: "assert" test ["," test]
+
+?compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | match_stmt
+              | with_stmt | funcdef | classdef | decorated | async_stmt
+async_stmt: "async" (funcdef | with_stmt | for_stmt)
+if_stmt: "if" test ":" suite elifs ["else" ":" suite]
+elifs: elif_*
+elif_: "elif" test ":" suite
+while_stmt: "while" test ":" suite ["else" ":" suite]
+for_stmt: "for" exprlist "in" testlist ":" suite ["else" ":" suite]
+try_stmt: "try" ":" suite except_clauses ["else" ":" suite] [finally]
+        | "try" ":" suite finally   -> try_finally
+finally: "finally" ":" suite
+except_clauses: except_clause+
+except_clause: "except" [test ["as" name]] ":" suite
+// NB compile.c makes sure that the default except clause is last
+
+
+with_stmt: "with" with_items ":" suite
+with_items: with_item ("," with_item)*
+with_item: test ["as" name]
+
+match_stmt: "match" test ":" _NEWLINE _INDENT case+ _DEDENT
+
+case: "case" pattern ["if" test] ":" suite
+
+?pattern: sequence_item_pattern "," _sequence_pattern -> sequence_pattern
+        | as_pattern
+?as_pattern: or_pattern ("as" NAME)?
+?or_pattern: closed_pattern ("|" closed_pattern)*
+?closed_pattern: literal_pattern
+               | NAME -> capture_pattern
+               | "_" -> any_pattern
+               | attr_pattern
+               | "(" as_pattern ")"
+               | "[" _sequence_pattern "]" -> sequence_pattern
+               | "(" (sequence_item_pattern "," _sequence_pattern)? ")" -> sequence_pattern
+               | "{" (mapping_item_pattern ("," mapping_item_pattern)* ","?)?"}" -> mapping_pattern
+               | "{" (mapping_item_pattern ("," mapping_item_pattern)* ",")? "**" NAME ","? "}" -> mapping_star_pattern
+               | class_pattern
+
+literal_pattern: inner_literal_pattern
+
+?inner_literal_pattern: "None" -> const_none
+                      | "True" -> const_true
+                      | "False" -> const_false
+                      | STRING -> string
+                      | number
+
+attr_pattern: NAME ("." NAME)+ -> value
+
+name_or_attr_pattern: NAME ("." NAME)* -> value
+
+mapping_item_pattern: (literal_pattern|attr_pattern) ":" as_pattern
+
+_sequence_pattern: (sequence_item_pattern ("," sequence_item_pattern)* ","?)?
+?sequence_item_pattern: as_pattern
+                      | "*" NAME -> star_pattern
+
+class_pattern: name_or_attr_pattern "(" [arguments_pattern ","?] ")"
+arguments_pattern: pos_arg_pattern ["," keyws_arg_pattern]
+                 | keyws_arg_pattern -> no_pos_arguments
+
+pos_arg_pattern: as_pattern ("," as_pattern)*
+keyws_arg_pattern: keyw_arg_pattern ("," keyw_arg_pattern)*
+keyw_arg_pattern: NAME "=" as_pattern
+
+
+
+suite: simple_stmt | _NEWLINE _INDENT stmt+ _DEDENT
+
+?test: or_test ("if" or_test "else" test)?
+     | lambdef
+     | assign_expr
+
+assign_expr: name ":=" test
+
+?test_nocond: or_test | lambdef_nocond
+
+?or_test: and_test ("or" and_test)*
+?and_test: not_test_ ("and" not_test_)*
+?not_test_: "not" not_test_ -> not_test
+         | comparison
+?comparison: expr (comp_op expr)*
+star_expr: "*" expr
+
+?expr: or_expr
+?or_expr: xor_expr ("|" xor_expr)*
+?xor_expr: and_expr ("^" and_expr)*
+?and_expr: shift_expr ("&" shift_expr)*
+?shift_expr: arith_expr (_shift_op arith_expr)*
+?arith_expr: term (_add_op term)*
+?term: factor (_mul_op factor)*
+?factor: _unary_op factor | power
+
+!_unary_op: "+"|"-"|"~"
+!_add_op: "+"|"-"
+!_shift_op: "<<"|">>"
+!_mul_op: "*"|"@"|"/"|"%"|"//"
+// <> isn't actually a valid comparison operator in Python. It's here for the
+// sake of a __future__ import described in PEP 401 (which really works :-)
+!comp_op: "<"|">"|"=="|">="|"<="|"<>"|"!="|"in"|"not" "in"|"is"|"is" "not"
+
+?power: await_expr ("**" factor)?
+?await_expr: AWAIT? atom_expr
+AWAIT: "await"
+
+?atom_expr: atom_expr "(" [arguments] ")"      -> funccall
+          | atom_expr "[" subscriptlist "]"  -> getitem
+          | atom_expr "." name               -> getattr
+          | atom
+
+?atom: "(" yield_expr ")"
+     | "(" _tuple_inner? ")" -> tuple
+     | "(" comprehension{test_or_star_expr} ")" -> tuple_comprehension
+     | "[" _exprlist? "]"  -> list
+     | "[" comprehension{test_or_star_expr} "]"  -> list_comprehension
+     | "{" _dict_exprlist? "}" -> dict
+     | "{" comprehension{key_value} "}" -> dict_comprehension
+     | "{" _exprlist "}" -> set
+     | "{" comprehension{test} "}" -> set_comprehension
+     | name -> var
+     | number
+     | string_concat
+     | "(" test ")"
+     | "..." -> ellipsis
+     | "None"    -> const_none
+     | "True"    -> const_true
+     | "False"   -> const_false
+
+
+?string_concat: string+
+
+_tuple_inner: test_or_star_expr (("," test_or_star_expr)+ [","] | ",")
+
+?test_or_star_expr: test
+                 | star_expr
+
+?subscriptlist: subscript
+              | subscript (("," subscript)+ [","] | ",") -> subscript_tuple
+?subscript: test | ([test] ":" [test] [sliceop]) -> slice
+sliceop: ":" [test]
+?exprlist: (expr|star_expr)
+         | (expr|star_expr) (("," (expr|star_expr))+ [","]|",")
+?testlist: test | testlist_tuple
+testlist_tuple: test (("," test)+ [","] | ",")
+_dict_exprlist: (key_value | "**" expr) ("," (key_value | "**" expr))* [","]
+
+key_value: test ":"  test
+
+_exprlist: test_or_star_expr (","  test_or_star_expr)* [","]
+
+classdef: "class" name ["(" [arguments] ")"] ":" suite
+
+
+
+arguments: argvalue ("," argvalue)*  ("," [ starargs | kwargs])?
+         | starargs
+         | kwargs
+         | comprehension{test}
+
+starargs: stararg ("," stararg)* ("," argvalue)* ["," kwargs]
+stararg: "*" test
+kwargs: "**" test ("," argvalue)*
+
+?argvalue: test ("=" test)?
+
+
+comprehension{comp_result}: comp_result comp_fors [comp_if]
+comp_fors: comp_for+
+comp_for: [ASYNC] "for" exprlist "in" or_test
+ASYNC: "async"
+?comp_if: "if" test_nocond
+
+// not used in grammar, but may appear in "node" passed from Parser to Compiler
+encoding_decl: name
+
+yield_expr: "yield" [testlist]
+          | "yield" "from" test -> yield_from
+
+number: DEC_NUMBER | HEX_NUMBER | BIN_NUMBER | OCT_NUMBER | FLOAT_NUMBER | IMAG_NUMBER
+string: STRING | LONG_STRING
+
+// Other terminals
+
+_NEWLINE: ( /\r?\n[\t ]*/ | COMMENT )+
+
+%ignore /[\t \f]+/  // WS
+%ignore /\\[\t \f]*\r?\n/   // LINE_CONT
+%ignore COMMENT
+%declare _INDENT _DEDENT
+
+
+// Python terminals
+
+!name: NAME | "match" | "case"
+NAME: /[^\W\d]\w*/
+COMMENT: /#[^\n]*/
+
+STRING: /([ubf]?r?|r[ubf])("(?!"").*?(? None:
+        self.paren_level = 0
+        self.indent_level = [0]
+        assert self.tab_len > 0
+
+    def handle_NL(self, token: Token) -> Iterator[Token]:
+        if self.paren_level > 0:
+            return
+
+        yield token
+
+        indent_str = token.rsplit('\n', 1)[1] # Tabs and spaces
+        indent = indent_str.count(' ') + indent_str.count('\t') * self.tab_len
+
+        if indent > self.indent_level[-1]:
+            self.indent_level.append(indent)
+            yield Token.new_borrow_pos(self.INDENT_type, indent_str, token)
+        else:
+            while indent < self.indent_level[-1]:
+                self.indent_level.pop()
+                yield Token.new_borrow_pos(self.DEDENT_type, indent_str, token)
+
+            if indent != self.indent_level[-1]:
+                raise DedentError('Unexpected dedent to column %s. Expected dedent to %s' % (indent, self.indent_level[-1]))
+
+    def _process(self, stream):
+        for token in stream:
+            if token.type == self.NL_type:
+                yield from self.handle_NL(token)
+            else:
+                yield token
+
+            if token.type in self.OPEN_PAREN_types:
+                self.paren_level += 1
+            elif token.type in self.CLOSE_PAREN_types:
+                self.paren_level -= 1
+                assert self.paren_level >= 0
+
+        while len(self.indent_level) > 1:
+            self.indent_level.pop()
+            yield Token(self.DEDENT_type, '')
+
+        assert self.indent_level == [0], self.indent_level
+
+    def process(self, stream):
+        self.paren_level = 0
+        self.indent_level = [0]
+        return self._process(stream)
+
+    # XXX Hack for ContextualLexer. Maybe there's a more elegant solution?
+    @property
+    def always_accept(self):
+        return (self.NL_type,)
+
+    @property
+    @abstractmethod
+    def NL_type(self) -> str:
+        "The name of the newline token"
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def OPEN_PAREN_types(self) -> List[str]:
+        "The names of the tokens that open a parenthesis"
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def CLOSE_PAREN_types(self) -> List[str]:
+        """The names of the tokens that close a parenthesis
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def INDENT_type(self) -> str:
+        """The name of the token that starts an indentation in the grammar.
+
+        See also: %declare
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def DEDENT_type(self) -> str:
+        """The name of the token that end an indentation in the grammar.
+
+        See also: %declare
+        """
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def tab_len(self) -> int:
+        """How many spaces does a tab equal"""
+        raise NotImplementedError()
+
+
+class PythonIndenter(Indenter):
+    """A postlexer that "injects" _INDENT/_DEDENT tokens based on indentation, according to the Python syntax.
+
+    See also: the ``postlex`` option in `Lark`.
+    """
+
+    NL_type = '_NEWLINE'
+    OPEN_PAREN_types = ['LPAR', 'LSQB', 'LBRACE']
+    CLOSE_PAREN_types = ['RPAR', 'RSQB', 'RBRACE']
+    INDENT_type = '_INDENT'
+    DEDENT_type = '_DEDENT'
+    tab_len = 8
+
+###}
diff --git a/server/libs/lark/lark.py b/server/libs/lark/lark.py
new file mode 100644
index 0000000..7ae1f24
--- /dev/null
+++ b/server/libs/lark/lark.py
@@ -0,0 +1,658 @@
+from abc import ABC, abstractmethod
+import getpass
+import sys, os, pickle
+import tempfile
+import types
+import re
+from typing import (
+    TypeVar, Type, List, Dict, Iterator, Callable, Union, Optional, Sequence,
+    Tuple, Iterable, IO, Any, TYPE_CHECKING, Collection
+)
+if TYPE_CHECKING:
+    from .parsers.lalr_interactive_parser import InteractiveParser
+    from .tree import ParseTree
+    from .visitors import Transformer
+    from typing import Literal
+    from .parser_frontends import ParsingFrontend
+
+from .exceptions import ConfigurationError, assert_config, UnexpectedInput
+from .utils import Serialize, SerializeMemoizer, FS, logger
+from .load_grammar import load_grammar, FromPackageLoader, Grammar, verify_used_files, PackageResource, sha256_digest
+from .tree import Tree
+from .common import LexerConf, ParserConf, _ParserArgType, _LexerArgType
+
+from .lexer import Lexer, BasicLexer, TerminalDef, LexerThread, Token
+from .parse_tree_builder import ParseTreeBuilder
+from .parser_frontends import _validate_frontend_args, _get_lexer_callbacks, _deserialize_parsing_frontend, _construct_parsing_frontend
+from .grammar import Rule
+
+
+try:
+    import regex
+    _has_regex = True
+except ImportError:
+    _has_regex = False
+
+
+###{standalone
+
+
+class PostLex(ABC):
+    @abstractmethod
+    def process(self, stream: Iterator[Token]) -> Iterator[Token]:
+        return stream
+
+    always_accept: Iterable[str] = ()
+
+class LarkOptions(Serialize):
+    """Specifies the options for Lark
+
+    """
+
+    start: List[str]
+    debug: bool
+    strict: bool
+    transformer: 'Optional[Transformer]'
+    propagate_positions: Union[bool, str]
+    maybe_placeholders: bool
+    cache: Union[bool, str]
+    regex: bool
+    g_regex_flags: int
+    keep_all_tokens: bool
+    tree_class: Optional[Callable[[str, List], Any]]
+    parser: _ParserArgType
+    lexer: _LexerArgType
+    ambiguity: 'Literal["auto", "resolve", "explicit", "forest"]'
+    postlex: Optional[PostLex]
+    priority: 'Optional[Literal["auto", "normal", "invert"]]'
+    lexer_callbacks: Dict[str, Callable[[Token], Token]]
+    use_bytes: bool
+    ordered_sets: bool
+    edit_terminals: Optional[Callable[[TerminalDef], TerminalDef]]
+    import_paths: 'List[Union[str, Callable[[Union[None, str, PackageResource], str], Tuple[str, str]]]]'
+    source_path: Optional[str]
+
+    OPTIONS_DOC = r"""
+    **===  General Options  ===**
+
+    start
+            The start symbol. Either a string, or a list of strings for multiple possible starts (Default: "start")
+    debug
+            Display debug information and extra warnings. Use only when debugging (Default: ``False``)
+            When used with Earley, it generates a forest graph as "sppf.png", if 'dot' is installed.
+    strict
+            Throw an exception on any potential ambiguity, including shift/reduce conflicts, and regex collisions.
+    transformer
+            Applies the transformer to every parse tree (equivalent to applying it after the parse, but faster)
+    propagate_positions
+            Propagates positional attributes into the 'meta' attribute of all tree branches.
+            Sets attributes: (line, column, end_line, end_column, start_pos, end_pos,
+                              container_line, container_column, container_end_line, container_end_column)
+            Accepts ``False``, ``True``, or a callable, which will filter which nodes to ignore when propagating.
+    maybe_placeholders
+            When ``True``, the ``[]`` operator returns ``None`` when not matched.
+            When ``False``,  ``[]`` behaves like the ``?`` operator, and returns no value at all.
+            (default= ``True``)
+    cache
+            Cache the results of the Lark grammar analysis, for x2 to x3 faster loading. LALR only for now.
+
+            - When ``False``, does nothing (default)
+            - When ``True``, caches to a temporary file in the local directory
+            - When given a string, caches to the path pointed by the string
+    regex
+            When True, uses the ``regex`` module instead of the stdlib ``re``.
+    g_regex_flags
+            Flags that are applied to all terminals (both regex and strings)
+    keep_all_tokens
+            Prevent the tree builder from automagically removing "punctuation" tokens (Default: ``False``)
+    tree_class
+            Lark will produce trees comprised of instances of this class instead of the default ``lark.Tree``.
+
+    **=== Algorithm Options ===**
+
+    parser
+            Decides which parser engine to use. Accepts "earley" or "lalr". (Default: "earley").
+            (there is also a "cyk" option for legacy)
+    lexer
+            Decides whether or not to use a lexer stage
+
+            - "auto" (default): Choose for me based on the parser
+            - "basic": Use a basic lexer
+            - "contextual": Stronger lexer (only works with parser="lalr")
+            - "dynamic": Flexible and powerful (only with parser="earley")
+            - "dynamic_complete": Same as dynamic, but tries *every* variation of tokenizing possible.
+    ambiguity
+            Decides how to handle ambiguity in the parse. Only relevant if parser="earley"
+
+            - "resolve": The parser will automatically choose the simplest derivation
+              (it chooses consistently: greedy for tokens, non-greedy for rules)
+            - "explicit": The parser will return all derivations wrapped in "_ambig" tree nodes (i.e. a forest).
+            - "forest": The parser will return the root of the shared packed parse forest.
+
+    **=== Misc. / Domain Specific Options ===**
+
+    postlex
+            Lexer post-processing (Default: ``None``) Only works with the basic and contextual lexers.
+    priority
+            How priorities should be evaluated - "auto", ``None``, "normal", "invert" (Default: "auto")
+    lexer_callbacks
+            Dictionary of callbacks for the lexer. May alter tokens during lexing. Use with caution.
+    use_bytes
+            Accept an input of type ``bytes`` instead of ``str``.
+    ordered_sets
+            Should Earley use ordered-sets to achieve stable output (~10% slower than regular sets. Default: True)
+    edit_terminals
+            A callback for editing the terminals before parse.
+    import_paths
+            A List of either paths or loader functions to specify from where grammars are imported
+    source_path
+            Override the source of from where the grammar was loaded. Useful for relative imports and unconventional grammar loading
+    **=== End of Options ===**
+    """
+    if __doc__:
+        __doc__ += OPTIONS_DOC
+
+
+    # Adding a new option needs to be done in multiple places:
+    # - In the dictionary below. This is the primary truth of which options `Lark.__init__` accepts
+    # - In the docstring above. It is used both for the docstring of `LarkOptions` and `Lark`, and in readthedocs
+    # - As an attribute of `LarkOptions` above
+    # - Potentially in `_LOAD_ALLOWED_OPTIONS` below this class, when the option doesn't change how the grammar is loaded
+    # - Potentially in `lark.tools.__init__`, if it makes sense, and it can easily be passed as a cmd argument
+    _defaults: Dict[str, Any] = {
+        'debug': False,
+        'strict': False,
+        'keep_all_tokens': False,
+        'tree_class': None,
+        'cache': False,
+        'postlex': None,
+        'parser': 'earley',
+        'lexer': 'auto',
+        'transformer': None,
+        'start': 'start',
+        'priority': 'auto',
+        'ambiguity': 'auto',
+        'regex': False,
+        'propagate_positions': False,
+        'lexer_callbacks': {},
+        'maybe_placeholders': True,
+        'edit_terminals': None,
+        'g_regex_flags': 0,
+        'use_bytes': False,
+        'ordered_sets': True,
+        'import_paths': [],
+        'source_path': None,
+        '_plugins': {},
+    }
+
+    def __init__(self, options_dict: Dict[str, Any]) -> None:
+        o = dict(options_dict)
+
+        options = {}
+        for name, default in self._defaults.items():
+            if name in o:
+                value = o.pop(name)
+                if isinstance(default, bool) and name not in ('cache', 'use_bytes', 'propagate_positions'):
+                    value = bool(value)
+            else:
+                value = default
+
+            options[name] = value
+
+        if isinstance(options['start'], str):
+            options['start'] = [options['start']]
+
+        self.__dict__['options'] = options
+
+
+        assert_config(self.parser, ('earley', 'lalr', 'cyk', None))
+
+        if self.parser == 'earley' and self.transformer:
+            raise ConfigurationError('Cannot specify an embedded transformer when using the Earley algorithm. '
+                             'Please use your transformer on the resulting parse tree, or use a different algorithm (i.e. LALR)')
+
+        if o:
+            raise ConfigurationError("Unknown options: %s" % o.keys())
+
+    def __getattr__(self, name: str) -> Any:
+        try:
+            return self.__dict__['options'][name]
+        except KeyError as e:
+            raise AttributeError(e)
+
+    def __setattr__(self, name: str, value: str) -> None:
+        assert_config(name, self.options.keys(), "%r isn't a valid option. Expected one of: %s")
+        self.options[name] = value
+
+    def serialize(self, memo = None) -> Dict[str, Any]:
+        return self.options
+
+    @classmethod
+    def deserialize(cls, data: Dict[str, Any], memo: Dict[int, Union[TerminalDef, Rule]]) -> "LarkOptions":
+        return cls(data)
+
+
+# Options that can be passed to the Lark parser, even when it was loaded from cache/standalone.
+# These options are only used outside of `load_grammar`.
+_LOAD_ALLOWED_OPTIONS = {'postlex', 'transformer', 'lexer_callbacks', 'use_bytes', 'debug', 'g_regex_flags', 'regex', 'propagate_positions', 'tree_class', '_plugins'}
+
+_VALID_PRIORITY_OPTIONS = ('auto', 'normal', 'invert', None)
+_VALID_AMBIGUITY_OPTIONS = ('auto', 'resolve', 'explicit', 'forest')
+
+
+_T = TypeVar('_T', bound="Lark")
+
+class Lark(Serialize):
+    """Main interface for the library.
+
+    It's mostly a thin wrapper for the many different parsers, and for the tree constructor.
+
+    Parameters:
+        grammar: a string or file-object containing the grammar spec (using Lark's ebnf syntax)
+        options: a dictionary controlling various aspects of Lark.
+
+    Example:
+        >>> Lark(r'''start: "foo" ''')
+        Lark(...)
+    """
+
+    source_path: str
+    source_grammar: str
+    grammar: 'Grammar'
+    options: LarkOptions
+    lexer: Lexer
+    parser: 'ParsingFrontend'
+    terminals: Collection[TerminalDef]
+
+    def __init__(self, grammar: 'Union[Grammar, str, IO[str]]', **options) -> None:
+        self.options = LarkOptions(options)
+        re_module: types.ModuleType
+
+        # Set regex or re module
+        use_regex = self.options.regex
+        if use_regex:
+            if _has_regex:
+                re_module = regex
+            else:
+                raise ImportError('`regex` module must be installed if calling `Lark(regex=True)`.')
+        else:
+            re_module = re
+
+        # Some, but not all file-like objects have a 'name' attribute
+        if self.options.source_path is None:
+            try:
+                self.source_path = grammar.name  # type: ignore[union-attr]
+            except AttributeError:
+                self.source_path = ''
+        else:
+            self.source_path = self.options.source_path
+
+        # Drain file-like objects to get their contents
+        try:
+            read = grammar.read  # type: ignore[union-attr]
+        except AttributeError:
+            pass
+        else:
+            grammar = read()
+
+        cache_fn = None
+        cache_sha256 = None
+        if isinstance(grammar, str):
+            self.source_grammar = grammar
+            if self.options.use_bytes:
+                if not grammar.isascii():
+                    raise ConfigurationError("Grammar must be ascii only, when use_bytes=True")
+
+            if self.options.cache:
+                if self.options.parser != 'lalr':
+                    raise ConfigurationError("cache only works with parser='lalr' for now")
+
+                unhashable = ('transformer', 'postlex', 'lexer_callbacks', 'edit_terminals', '_plugins')
+                options_str = ''.join(k+str(v) for k, v in options.items() if k not in unhashable)
+                from . import __version__
+                s = grammar + options_str + __version__ + str(sys.version_info[:2])
+                cache_sha256 = sha256_digest(s)
+
+                if isinstance(self.options.cache, str):
+                    cache_fn = self.options.cache
+                else:
+                    if self.options.cache is not True:
+                        raise ConfigurationError("cache argument must be bool or str")
+
+                    try:
+                        username = getpass.getuser()
+                    except Exception:
+                        # The exception raised may be ImportError or OSError in
+                        # the future.  For the cache, we don't care about the
+                        # specific reason - we just want a username.
+                        username = "unknown"
+
+                    cache_fn = tempfile.gettempdir() + "/.lark_cache_%s_%s_%s_%s.tmp" % (username, cache_sha256, *sys.version_info[:2])
+
+                old_options = self.options
+                try:
+                    with FS.open(cache_fn, 'rb') as f:
+                        logger.debug('Loading grammar from cache: %s', cache_fn)
+                        # Remove options that aren't relevant for loading from cache
+                        for name in (set(options) - _LOAD_ALLOWED_OPTIONS):
+                            del options[name]
+                        file_sha256 = f.readline().rstrip(b'\n')
+                        cached_used_files = pickle.load(f)
+                        if file_sha256 == cache_sha256.encode('utf8') and verify_used_files(cached_used_files):
+                            cached_parser_data = pickle.load(f)
+                            self._load(cached_parser_data, **options)
+                            return
+                except FileNotFoundError:
+                    # The cache file doesn't exist; parse and compose the grammar as normal
+                    pass
+                except Exception: # We should probably narrow done which errors we catch here.
+                    logger.exception("Failed to load Lark from cache: %r. We will try to carry on.", cache_fn)
+
+                    # In theory, the Lark instance might have been messed up by the call to `_load`.
+                    # In practice the only relevant thing that might have been overwritten should be `options`
+                    self.options = old_options
+
+
+            # Parse the grammar file and compose the grammars
+            self.grammar, used_files = load_grammar(grammar, self.source_path, self.options.import_paths, self.options.keep_all_tokens)
+        else:
+            assert isinstance(grammar, Grammar)
+            self.grammar = grammar
+
+
+        if self.options.lexer == 'auto':
+            if self.options.parser == 'lalr':
+                self.options.lexer = 'contextual'
+            elif self.options.parser == 'earley':
+                if self.options.postlex is not None:
+                    logger.info("postlex can't be used with the dynamic lexer, so we use 'basic' instead. "
+                                "Consider using lalr with contextual instead of earley")
+                    self.options.lexer = 'basic'
+                else:
+                    self.options.lexer = 'dynamic'
+            elif self.options.parser == 'cyk':
+                self.options.lexer = 'basic'
+            else:
+                assert False, self.options.parser
+        lexer = self.options.lexer
+        if isinstance(lexer, type):
+            assert issubclass(lexer, Lexer)     # XXX Is this really important? Maybe just ensure interface compliance
+        else:
+            assert_config(lexer, ('basic', 'contextual', 'dynamic', 'dynamic_complete'))
+            if self.options.postlex is not None and 'dynamic' in lexer:
+                raise ConfigurationError("Can't use postlex with a dynamic lexer. Use basic or contextual instead")
+
+        if self.options.ambiguity == 'auto':
+            if self.options.parser == 'earley':
+                self.options.ambiguity = 'resolve'
+        else:
+            assert_config(self.options.parser, ('earley', 'cyk'), "%r doesn't support disambiguation. Use one of these parsers instead: %s")
+
+        if self.options.priority == 'auto':
+            self.options.priority = 'normal'
+
+        if self.options.priority not in _VALID_PRIORITY_OPTIONS:
+            raise ConfigurationError("invalid priority option: %r. Must be one of %r" % (self.options.priority, _VALID_PRIORITY_OPTIONS))
+        if self.options.ambiguity not in _VALID_AMBIGUITY_OPTIONS:
+            raise ConfigurationError("invalid ambiguity option: %r. Must be one of %r" % (self.options.ambiguity, _VALID_AMBIGUITY_OPTIONS))
+
+        if self.options.parser is None:
+            terminals_to_keep = '*'
+        elif self.options.postlex is not None:
+            terminals_to_keep = set(self.options.postlex.always_accept)
+        else:
+            terminals_to_keep = set()
+
+        # Compile the EBNF grammar into BNF
+        self.terminals, self.rules, self.ignore_tokens = self.grammar.compile(self.options.start, terminals_to_keep)
+
+        if self.options.edit_terminals:
+            for t in self.terminals:
+                self.options.edit_terminals(t)
+
+        self._terminals_dict = {t.name: t for t in self.terminals}
+
+        # If the user asked to invert the priorities, negate them all here.
+        if self.options.priority == 'invert':
+            for rule in self.rules:
+                if rule.options.priority is not None:
+                    rule.options.priority = -rule.options.priority
+            for term in self.terminals:
+                term.priority = -term.priority
+        # Else, if the user asked to disable priorities, strip them from the
+        # rules and terminals. This allows the Earley parsers to skip an extra forest walk
+        # for improved performance, if you don't need them (or didn't specify any).
+        elif self.options.priority is None:
+            for rule in self.rules:
+                if rule.options.priority is not None:
+                    rule.options.priority = None
+            for term in self.terminals:
+                term.priority = 0
+
+        # TODO Deprecate lexer_callbacks?
+        self.lexer_conf = LexerConf(
+                self.terminals, re_module, self.ignore_tokens, self.options.postlex,
+                self.options.lexer_callbacks, self.options.g_regex_flags, use_bytes=self.options.use_bytes, strict=self.options.strict
+            )
+
+        if self.options.parser:
+            self.parser = self._build_parser()
+        elif lexer:
+            self.lexer = self._build_lexer()
+
+        if cache_fn:
+            logger.debug('Saving grammar to cache: %s', cache_fn)
+            try:
+                with FS.open(cache_fn, 'wb') as f:
+                    assert cache_sha256 is not None
+                    f.write(cache_sha256.encode('utf8') + b'\n')
+                    pickle.dump(used_files, f)
+                    self.save(f, _LOAD_ALLOWED_OPTIONS)
+            except IOError as e:
+                logger.exception("Failed to save Lark to cache: %r.", cache_fn, e)
+
+    if __doc__:
+        __doc__ += "\n\n" + LarkOptions.OPTIONS_DOC
+
+    __serialize_fields__ = 'parser', 'rules', 'options'
+
+    def _build_lexer(self, dont_ignore: bool=False) -> BasicLexer:
+        lexer_conf = self.lexer_conf
+        if dont_ignore:
+            from copy import copy
+            lexer_conf = copy(lexer_conf)
+            lexer_conf.ignore = ()
+        return BasicLexer(lexer_conf)
+
+    def _prepare_callbacks(self) -> None:
+        self._callbacks = {}
+        # we don't need these callbacks if we aren't building a tree
+        if self.options.ambiguity != 'forest':
+            self._parse_tree_builder = ParseTreeBuilder(
+                    self.rules,
+                    self.options.tree_class or Tree,
+                    self.options.propagate_positions,
+                    self.options.parser != 'lalr' and self.options.ambiguity == 'explicit',
+                    self.options.maybe_placeholders
+                )
+            self._callbacks = self._parse_tree_builder.create_callback(self.options.transformer)
+        self._callbacks.update(_get_lexer_callbacks(self.options.transformer, self.terminals))
+
+    def _build_parser(self) -> "ParsingFrontend":
+        self._prepare_callbacks()
+        _validate_frontend_args(self.options.parser, self.options.lexer)
+        parser_conf = ParserConf(self.rules, self._callbacks, self.options.start)
+        return _construct_parsing_frontend(
+            self.options.parser,
+            self.options.lexer,
+            self.lexer_conf,
+            parser_conf,
+            options=self.options
+        )
+
+    def save(self, f, exclude_options: Collection[str] = ()) -> None:
+        """Saves the instance into the given file object
+
+        Useful for caching and multiprocessing.
+        """
+        if self.options.parser != 'lalr':
+            raise NotImplementedError("Lark.save() is only implemented for the LALR(1) parser.")
+        data, m = self.memo_serialize([TerminalDef, Rule])
+        if exclude_options:
+            data["options"] = {n: v for n, v in data["options"].items() if n not in exclude_options}
+        pickle.dump({'data': data, 'memo': m}, f, protocol=pickle.HIGHEST_PROTOCOL)
+
+    @classmethod
+    def load(cls: Type[_T], f) -> _T:
+        """Loads an instance from the given file object
+
+        Useful for caching and multiprocessing.
+        """
+        inst = cls.__new__(cls)
+        return inst._load(f)
+
+    def _deserialize_lexer_conf(self, data: Dict[str, Any], memo: Dict[int, Union[TerminalDef, Rule]], options: LarkOptions) -> LexerConf:
+        lexer_conf = LexerConf.deserialize(data['lexer_conf'], memo)
+        lexer_conf.callbacks = options.lexer_callbacks or {}
+        lexer_conf.re_module = regex if options.regex else re
+        lexer_conf.use_bytes = options.use_bytes
+        lexer_conf.g_regex_flags = options.g_regex_flags
+        lexer_conf.skip_validation = True
+        lexer_conf.postlex = options.postlex
+        return lexer_conf
+
+    def _load(self: _T, f: Any, **kwargs) -> _T:
+        if isinstance(f, dict):
+            d = f
+        else:
+            d = pickle.load(f)
+        memo_json = d['memo']
+        data = d['data']
+
+        assert memo_json
+        memo = SerializeMemoizer.deserialize(memo_json, {'Rule': Rule, 'TerminalDef': TerminalDef}, {})
+        options = dict(data['options'])
+        if (set(kwargs) - _LOAD_ALLOWED_OPTIONS) & set(LarkOptions._defaults):
+            raise ConfigurationError("Some options are not allowed when loading a Parser: {}"
+                             .format(set(kwargs) - _LOAD_ALLOWED_OPTIONS))
+        options.update(kwargs)
+        self.options = LarkOptions.deserialize(options, memo)
+        self.rules = [Rule.deserialize(r, memo) for r in data['rules']]
+        self.source_path = ''
+        _validate_frontend_args(self.options.parser, self.options.lexer)
+        self.lexer_conf = self._deserialize_lexer_conf(data['parser'], memo, self.options)
+        self.terminals = self.lexer_conf.terminals
+        self._prepare_callbacks()
+        self._terminals_dict = {t.name: t for t in self.terminals}
+        self.parser = _deserialize_parsing_frontend(
+            data['parser'],
+            memo,
+            self.lexer_conf,
+            self._callbacks,
+            self.options,  # Not all, but multiple attributes are used
+        )
+        return self
+
+    @classmethod
+    def _load_from_dict(cls, data, memo, **kwargs):
+        inst = cls.__new__(cls)
+        return inst._load({'data': data, 'memo': memo}, **kwargs)
+
+    @classmethod
+    def open(cls: Type[_T], grammar_filename: str, rel_to: Optional[str]=None, **options) -> _T:
+        """Create an instance of Lark with the grammar given by its filename
+
+        If ``rel_to`` is provided, the function will find the grammar filename in relation to it.
+
+        Example:
+
+            >>> Lark.open("grammar_file.lark", rel_to=__file__, parser="lalr")
+            Lark(...)
+
+        """
+        if rel_to:
+            basepath = os.path.dirname(rel_to)
+            grammar_filename = os.path.join(basepath, grammar_filename)
+        with open(grammar_filename, encoding='utf8') as f:
+            return cls(f, **options)
+
+    @classmethod
+    def open_from_package(cls: Type[_T], package: str, grammar_path: str, search_paths: 'Sequence[str]'=[""], **options) -> _T:
+        """Create an instance of Lark with the grammar loaded from within the package `package`.
+        This allows grammar loading from zipapps.
+
+        Imports in the grammar will use the `package` and `search_paths` provided, through `FromPackageLoader`
+
+        Example:
+
+            Lark.open_from_package(__name__, "example.lark", ("grammars",), parser=...)
+        """
+        package_loader = FromPackageLoader(package, search_paths)
+        full_path, text = package_loader(None, grammar_path)
+        options.setdefault('source_path', full_path)
+        options.setdefault('import_paths', [])
+        options['import_paths'].append(package_loader)
+        return cls(text, **options)
+
+    def __repr__(self):
+        return 'Lark(open(%r), parser=%r, lexer=%r, ...)' % (self.source_path, self.options.parser, self.options.lexer)
+
+
+    def lex(self, text: str, dont_ignore: bool=False) -> Iterator[Token]:
+        """Only lex (and postlex) the text, without parsing it. Only relevant when lexer='basic'
+
+        When dont_ignore=True, the lexer will return all tokens, even those marked for %ignore.
+
+        :raises UnexpectedCharacters: In case the lexer cannot find a suitable match.
+        """
+        lexer: Lexer
+        if not hasattr(self, 'lexer') or dont_ignore:
+            lexer = self._build_lexer(dont_ignore)
+        else:
+            lexer = self.lexer
+        lexer_thread = LexerThread.from_text(lexer, text)
+        stream = lexer_thread.lex(None)
+        if self.options.postlex:
+            return self.options.postlex.process(stream)
+        return stream
+
+    def get_terminal(self, name: str) -> TerminalDef:
+        """Get information about a terminal"""
+        return self._terminals_dict[name]
+
+    def parse_interactive(self, text: Optional[str]=None, start: Optional[str]=None) -> 'InteractiveParser':
+        """Start an interactive parsing session.
+
+        Parameters:
+            text (str, optional): Text to be parsed. Required for ``resume_parse()``.
+            start (str, optional): Start symbol
+
+        Returns:
+            A new InteractiveParser instance.
+
+        See Also: ``Lark.parse()``
+        """
+        return self.parser.parse_interactive(text, start=start)
+
+    def parse(self, text: str, start: Optional[str]=None, on_error: 'Optional[Callable[[UnexpectedInput], bool]]'=None) -> 'ParseTree':
+        """Parse the given text, according to the options provided.
+
+        Parameters:
+            text (str): Text to be parsed.
+            start (str, optional): Required if Lark was given multiple possible start symbols (using the start option).
+            on_error (function, optional): if provided, will be called on UnexpectedToken error. Return true to resume parsing.
+                LALR only. See examples/advanced/error_handling.py for an example of how to use on_error.
+
+        Returns:
+            If a transformer is supplied to ``__init__``, returns whatever is the
+            result of the transformation. Otherwise, returns a Tree instance.
+
+        :raises UnexpectedInput: On a parse error, one of these sub-exceptions will rise:
+                ``UnexpectedCharacters``, ``UnexpectedToken``, or ``UnexpectedEOF``.
+                For convenience, these sub-exceptions also inherit from ``ParserError`` and ``LexerError``.
+
+        """
+        return self.parser.parse(text, start=start, on_error=on_error)
+
+
+###}
diff --git a/server/libs/lark/lexer.py b/server/libs/lark/lexer.py
new file mode 100644
index 0000000..9061d60
--- /dev/null
+++ b/server/libs/lark/lexer.py
@@ -0,0 +1,678 @@
+# Lexer Implementation
+
+from abc import abstractmethod, ABC
+import re
+from contextlib import suppress
+from typing import (
+    TypeVar, Type, Dict, Iterator, Collection, Callable, Optional, FrozenSet, Any,
+    ClassVar, TYPE_CHECKING, overload
+)
+from types import ModuleType
+import warnings
+try:
+    import interegular
+except ImportError:
+    pass
+if TYPE_CHECKING:
+    from .common import LexerConf
+    from .parsers.lalr_parser_state import ParserState
+
+from .utils import classify, get_regexp_width, Serialize, logger
+from .exceptions import UnexpectedCharacters, LexError, UnexpectedToken
+from .grammar import TOKEN_DEFAULT_PRIORITY
+
+
+###{standalone
+from copy import copy
+
+try:  # For the standalone parser, we need to make sure that has_interegular is False to avoid NameErrors later on
+    has_interegular = bool(interegular)
+except NameError:
+    has_interegular = False
+
+class Pattern(Serialize, ABC):
+    "An abstraction over regular expressions."
+
+    value: str
+    flags: Collection[str]
+    raw: Optional[str]
+    type: ClassVar[str]
+
+    def __init__(self, value: str, flags: Collection[str] = (), raw: Optional[str] = None) -> None:
+        self.value = value
+        self.flags = frozenset(flags)
+        self.raw = raw
+
+    def __repr__(self):
+        return repr(self.to_regexp())
+
+    # Pattern Hashing assumes all subclasses have a different priority!
+    def __hash__(self):
+        return hash((type(self), self.value, self.flags))
+
+    def __eq__(self, other):
+        return type(self) == type(other) and self.value == other.value and self.flags == other.flags
+
+    @abstractmethod
+    def to_regexp(self) -> str:
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def min_width(self) -> int:
+        raise NotImplementedError()
+
+    @property
+    @abstractmethod
+    def max_width(self) -> int:
+        raise NotImplementedError()
+
+    def _get_flags(self, value):
+        for f in self.flags:
+            value = ('(?%s:%s)' % (f, value))
+        return value
+
+
+class PatternStr(Pattern):
+    __serialize_fields__ = 'value', 'flags', 'raw'
+
+    type: ClassVar[str] = "str"
+
+    def to_regexp(self) -> str:
+        return self._get_flags(re.escape(self.value))
+
+    @property
+    def min_width(self) -> int:
+        return len(self.value)
+
+    @property
+    def max_width(self) -> int:
+        return len(self.value)
+
+
+class PatternRE(Pattern):
+    __serialize_fields__ = 'value', 'flags', 'raw', '_width'
+
+    type: ClassVar[str] = "re"
+
+    def to_regexp(self) -> str:
+        return self._get_flags(self.value)
+
+    _width = None
+    def _get_width(self):
+        if self._width is None:
+            self._width = get_regexp_width(self.to_regexp())
+        return self._width
+
+    @property
+    def min_width(self) -> int:
+        return self._get_width()[0]
+
+    @property
+    def max_width(self) -> int:
+        return self._get_width()[1]
+
+
+class TerminalDef(Serialize):
+    "A definition of a terminal"
+    __serialize_fields__ = 'name', 'pattern', 'priority'
+    __serialize_namespace__ = PatternStr, PatternRE
+
+    name: str
+    pattern: Pattern
+    priority: int
+
+    def __init__(self, name: str, pattern: Pattern, priority: int = TOKEN_DEFAULT_PRIORITY) -> None:
+        assert isinstance(pattern, Pattern), pattern
+        self.name = name
+        self.pattern = pattern
+        self.priority = priority
+
+    def __repr__(self):
+        return '%s(%r, %r)' % (type(self).__name__, self.name, self.pattern)
+
+    def user_repr(self) -> str:
+        if self.name.startswith('__'):  # We represent a generated terminal
+            return self.pattern.raw or self.name
+        else:
+            return self.name
+
+_T = TypeVar('_T', bound="Token")
+
+class Token(str):
+    """A string with meta-information, that is produced by the lexer.
+
+    When parsing text, the resulting chunks of the input that haven't been discarded,
+    will end up in the tree as Token instances. The Token class inherits from Python's ``str``,
+    so normal string comparisons and operations will work as expected.
+
+    Attributes:
+        type: Name of the token (as specified in grammar)
+        value: Value of the token (redundant, as ``token.value == token`` will always be true)
+        start_pos: The index of the token in the text
+        line: The line of the token in the text (starting with 1)
+        column: The column of the token in the text (starting with 1)
+        end_line: The line where the token ends
+        end_column: The next column after the end of the token. For example,
+            if the token is a single character with a column value of 4,
+            end_column will be 5.
+        end_pos: the index where the token ends (basically ``start_pos + len(token)``)
+    """
+    __slots__ = ('type', 'start_pos', 'value', 'line', 'column', 'end_line', 'end_column', 'end_pos')
+
+    __match_args__ = ('type', 'value')
+
+    type: str
+    start_pos: Optional[int]
+    value: Any
+    line: Optional[int]
+    column: Optional[int]
+    end_line: Optional[int]
+    end_column: Optional[int]
+    end_pos: Optional[int]
+
+
+    @overload
+    def __new__(
+            cls,
+            type: str,
+            value: Any,
+            start_pos: Optional[int] = None,
+            line: Optional[int] = None,
+            column: Optional[int] = None,
+            end_line: Optional[int] = None,
+            end_column: Optional[int] = None,
+            end_pos: Optional[int] = None
+    ) -> 'Token':
+        ...
+
+    @overload
+    def __new__(
+            cls,
+            type_: str,
+            value: Any,
+            start_pos: Optional[int] = None,
+            line: Optional[int] = None,
+            column: Optional[int] = None,
+            end_line: Optional[int] = None,
+            end_column: Optional[int] = None,
+            end_pos: Optional[int] = None
+    ) -> 'Token':        ...
+
+    def __new__(cls, *args, **kwargs):
+        if "type_" in kwargs:
+            warnings.warn("`type_` is deprecated use `type` instead", DeprecationWarning)
+
+            if "type" in kwargs:
+                raise TypeError("Error: using both 'type' and the deprecated 'type_' as arguments.")
+            kwargs["type"] = kwargs.pop("type_")
+
+        return cls._future_new(*args, **kwargs)
+
+
+    @classmethod
+    def _future_new(cls, type, value, start_pos=None, line=None, column=None, end_line=None, end_column=None, end_pos=None):
+        inst = super(Token, cls).__new__(cls, value)
+
+        inst.type = type
+        inst.start_pos = start_pos
+        inst.value = value
+        inst.line = line
+        inst.column = column
+        inst.end_line = end_line
+        inst.end_column = end_column
+        inst.end_pos = end_pos
+        return inst
+
+    @overload
+    def update(self, type: Optional[str] = None, value: Optional[Any] = None) -> 'Token':
+        ...
+
+    @overload
+    def update(self, type_: Optional[str] = None, value: Optional[Any] = None) -> 'Token':
+        ...
+
+    def update(self, *args, **kwargs):
+        if "type_" in kwargs:
+            warnings.warn("`type_` is deprecated use `type` instead", DeprecationWarning)
+
+            if "type" in kwargs:
+                raise TypeError("Error: using both 'type' and the deprecated 'type_' as arguments.")
+            kwargs["type"] = kwargs.pop("type_")
+
+        return self._future_update(*args, **kwargs)
+
+    def _future_update(self, type: Optional[str] = None, value: Optional[Any] = None) -> 'Token':
+        return Token.new_borrow_pos(
+            type if type is not None else self.type,
+            value if value is not None else self.value,
+            self
+        )
+
+    @classmethod
+    def new_borrow_pos(cls: Type[_T], type_: str, value: Any, borrow_t: 'Token') -> _T:
+        return cls(type_, value, borrow_t.start_pos, borrow_t.line, borrow_t.column, borrow_t.end_line, borrow_t.end_column, borrow_t.end_pos)
+
+    def __reduce__(self):
+        return (self.__class__, (self.type, self.value, self.start_pos, self.line, self.column))
+
+    def __repr__(self):
+        return 'Token(%r, %r)' % (self.type, self.value)
+
+    def __deepcopy__(self, memo):
+        return Token(self.type, self.value, self.start_pos, self.line, self.column)
+
+    def __eq__(self, other):
+        if isinstance(other, Token) and self.type != other.type:
+            return False
+
+        return str.__eq__(self, other)
+
+    __hash__ = str.__hash__
+
+
+class LineCounter:
+    "A utility class for keeping track of line & column information"
+
+    __slots__ = 'char_pos', 'line', 'column', 'line_start_pos', 'newline_char'
+
+    def __init__(self, newline_char):
+        self.newline_char = newline_char
+        self.char_pos = 0
+        self.line = 1
+        self.column = 1
+        self.line_start_pos = 0
+
+    def __eq__(self, other):
+        if not isinstance(other, LineCounter):
+            return NotImplemented
+
+        return self.char_pos == other.char_pos and self.newline_char == other.newline_char
+
+    def feed(self, token: Token, test_newline=True):
+        """Consume a token and calculate the new line & column.
+
+        As an optional optimization, set test_newline=False if token doesn't contain a newline.
+        """
+        if test_newline:
+            newlines = token.count(self.newline_char)
+            if newlines:
+                self.line += newlines
+                self.line_start_pos = self.char_pos + token.rindex(self.newline_char) + 1
+
+        self.char_pos += len(token)
+        self.column = self.char_pos - self.line_start_pos + 1
+
+
+class UnlessCallback:
+    def __init__(self, scanner):
+        self.scanner = scanner
+
+    def __call__(self, t):
+        res = self.scanner.match(t.value, 0)
+        if res:
+            _value, t.type = res
+        return t
+
+
+class CallChain:
+    def __init__(self, callback1, callback2, cond):
+        self.callback1 = callback1
+        self.callback2 = callback2
+        self.cond = cond
+
+    def __call__(self, t):
+        t2 = self.callback1(t)
+        return self.callback2(t) if self.cond(t2) else t2
+
+
+def _get_match(re_, regexp, s, flags):
+    m = re_.match(regexp, s, flags)
+    if m:
+        return m.group(0)
+
+def _create_unless(terminals, g_regex_flags, re_, use_bytes):
+    tokens_by_type = classify(terminals, lambda t: type(t.pattern))
+    assert len(tokens_by_type) <= 2, tokens_by_type.keys()
+    embedded_strs = set()
+    callback = {}
+    for retok in tokens_by_type.get(PatternRE, []):
+        unless = []
+        for strtok in tokens_by_type.get(PatternStr, []):
+            if strtok.priority != retok.priority:
+                continue
+            s = strtok.pattern.value
+            if s == _get_match(re_, retok.pattern.to_regexp(), s, g_regex_flags):
+                unless.append(strtok)
+                if strtok.pattern.flags <= retok.pattern.flags:
+                    embedded_strs.add(strtok)
+        if unless:
+            callback[retok.name] = UnlessCallback(Scanner(unless, g_regex_flags, re_, match_whole=True, use_bytes=use_bytes))
+
+    new_terminals = [t for t in terminals if t not in embedded_strs]
+    return new_terminals, callback
+
+
+class Scanner:
+    def __init__(self, terminals, g_regex_flags, re_, use_bytes, match_whole=False):
+        self.terminals = terminals
+        self.g_regex_flags = g_regex_flags
+        self.re_ = re_
+        self.use_bytes = use_bytes
+        self.match_whole = match_whole
+
+        self.allowed_types = {t.name for t in self.terminals}
+
+        self._mres = self._build_mres(terminals, len(terminals))
+
+    def _build_mres(self, terminals, max_size):
+        # Python sets an unreasonable group limit (currently 100) in its re module
+        # Worse, the only way to know we reached it is by catching an AssertionError!
+        # This function recursively tries less and less groups until it's successful.
+        postfix = '$' if self.match_whole else ''
+        mres = []
+        while terminals:
+            pattern = u'|'.join(u'(?P<%s>%s)' % (t.name, t.pattern.to_regexp() + postfix) for t in terminals[:max_size])
+            if self.use_bytes:
+                pattern = pattern.encode('latin-1')
+            try:
+                mre = self.re_.compile(pattern, self.g_regex_flags)
+            except AssertionError:  # Yes, this is what Python provides us.. :/
+                return self._build_mres(terminals, max_size // 2)
+
+            mres.append(mre)
+            terminals = terminals[max_size:]
+        return mres
+
+    def match(self, text, pos):
+        for mre in self._mres:
+            m = mre.match(text, pos)
+            if m:
+                return m.group(0), m.lastgroup
+
+
+def _regexp_has_newline(r: str):
+    r"""Expressions that may indicate newlines in a regexp:
+        - newlines (\n)
+        - escaped newline (\\n)
+        - anything but ([^...])
+        - any-char (.) when the flag (?s) exists
+        - spaces (\s)
+    """
+    return '\n' in r or '\\n' in r or '\\s' in r or '[^' in r or ('(?s' in r and '.' in r)
+
+
+class LexerState:
+    """Represents the current state of the lexer as it scans the text
+    (Lexer objects are only instantiated per grammar, not per text)
+    """
+
+    __slots__ = 'text', 'line_ctr', 'last_token'
+
+    text: str
+    line_ctr: LineCounter
+    last_token: Optional[Token]
+
+    def __init__(self, text: str, line_ctr: Optional[LineCounter]=None, last_token: Optional[Token]=None):
+        self.text = text
+        self.line_ctr = line_ctr or LineCounter(b'\n' if isinstance(text, bytes) else '\n')
+        self.last_token = last_token
+
+    def __eq__(self, other):
+        if not isinstance(other, LexerState):
+            return NotImplemented
+
+        return self.text is other.text and self.line_ctr == other.line_ctr and self.last_token == other.last_token
+
+    def __copy__(self):
+        return type(self)(self.text, copy(self.line_ctr), self.last_token)
+
+
+class LexerThread:
+    """A thread that ties a lexer instance and a lexer state, to be used by the parser
+    """
+
+    def __init__(self, lexer: 'Lexer', lexer_state: LexerState):
+        self.lexer = lexer
+        self.state = lexer_state
+
+    @classmethod
+    def from_text(cls, lexer: 'Lexer', text: str) -> 'LexerThread':
+        return cls(lexer, LexerState(text))
+
+    def lex(self, parser_state):
+        return self.lexer.lex(self.state, parser_state)
+
+    def __copy__(self):
+        return type(self)(self.lexer, copy(self.state))
+
+    _Token = Token
+
+
+_Callback = Callable[[Token], Token]
+
+class Lexer(ABC):
+    """Lexer interface
+
+    Method Signatures:
+        lex(self, lexer_state, parser_state) -> Iterator[Token]
+    """
+    @abstractmethod
+    def lex(self, lexer_state: LexerState, parser_state: Any) -> Iterator[Token]:
+        return NotImplemented
+
+    def make_lexer_state(self, text):
+        "Deprecated"
+        return LexerState(text)
+
+
+def _check_regex_collisions(terminal_to_regexp: Dict[TerminalDef, str], comparator, strict_mode, max_collisions_to_show=8):
+    if not comparator:
+        comparator = interegular.Comparator.from_regexes(terminal_to_regexp)
+
+    # When in strict mode, we only ever try to provide one example, so taking
+    # a long time for that should be fine
+    max_time = 2 if strict_mode else 0.2
+
+    # We don't want to show too many collisions.
+    if comparator.count_marked_pairs() >= max_collisions_to_show:
+        return
+    for group in classify(terminal_to_regexp, lambda t: t.priority).values():
+        for a, b in comparator.check(group, skip_marked=True):
+            assert a.priority == b.priority
+            # Mark this pair to not repeat warnings when multiple different BasicLexers see the same collision
+            comparator.mark(a, b)
+
+            # Notify the user
+            message = f"Collision between Terminals {a.name} and {b.name}. "
+            try:
+                example = comparator.get_example_overlap(a, b, max_time).format_multiline()
+            except ValueError:
+                # Couldn't find an example within max_time steps.
+                example = "No example could be found fast enough. However, the collision does still exists"
+            if strict_mode:
+                raise LexError(f"{message}\n{example}")
+            logger.warning("%s The lexer will choose between them arbitrarily.\n%s", message, example)
+            if comparator.count_marked_pairs() >= max_collisions_to_show:
+                logger.warning("Found 8 regex collisions, will not check for more.")
+                return
+
+
+class AbstractBasicLexer(Lexer):
+    terminals_by_name: Dict[str, TerminalDef]
+
+    @abstractmethod
+    def __init__(self, conf: 'LexerConf', comparator=None) -> None:
+        ...
+
+    @abstractmethod
+    def next_token(self, lex_state: LexerState, parser_state: Any = None) -> Token:
+        ...
+
+    def lex(self, state: LexerState, parser_state: Any) -> Iterator[Token]:
+        with suppress(EOFError):
+            while True:
+                yield self.next_token(state, parser_state)
+
+
+class BasicLexer(AbstractBasicLexer):
+    terminals: Collection[TerminalDef]
+    ignore_types: FrozenSet[str]
+    newline_types: FrozenSet[str]
+    user_callbacks: Dict[str, _Callback]
+    callback: Dict[str, _Callback]
+    re: ModuleType
+
+    def __init__(self, conf: 'LexerConf', comparator=None) -> None:
+        terminals = list(conf.terminals)
+        assert all(isinstance(t, TerminalDef) for t in terminals), terminals
+
+        self.re = conf.re_module
+
+        if not conf.skip_validation:
+            # Sanitization
+            terminal_to_regexp = {}
+            for t in terminals:
+                regexp = t.pattern.to_regexp()
+                try:
+                    self.re.compile(regexp, conf.g_regex_flags)
+                except self.re.error:
+                    raise LexError("Cannot compile token %s: %s" % (t.name, t.pattern))
+
+                if t.pattern.min_width == 0:
+                    raise LexError("Lexer does not allow zero-width terminals. (%s: %s)" % (t.name, t.pattern))
+                if t.pattern.type == "re":
+                    terminal_to_regexp[t] = regexp
+
+            if not (set(conf.ignore) <= {t.name for t in terminals}):
+                raise LexError("Ignore terminals are not defined: %s" % (set(conf.ignore) - {t.name for t in terminals}))
+
+            if has_interegular:
+                _check_regex_collisions(terminal_to_regexp, comparator, conf.strict)
+            elif conf.strict:
+                raise LexError("interegular must be installed for strict mode. Use `pip install 'lark[interegular]'`.")
+
+        # Init
+        self.newline_types = frozenset(t.name for t in terminals if _regexp_has_newline(t.pattern.to_regexp()))
+        self.ignore_types = frozenset(conf.ignore)
+
+        terminals.sort(key=lambda x: (-x.priority, -x.pattern.max_width, -len(x.pattern.value), x.name))
+        self.terminals = terminals
+        self.user_callbacks = conf.callbacks
+        self.g_regex_flags = conf.g_regex_flags
+        self.use_bytes = conf.use_bytes
+        self.terminals_by_name = conf.terminals_by_name
+
+        self._scanner = None
+
+    def _build_scanner(self):
+        terminals, self.callback = _create_unless(self.terminals, self.g_regex_flags, self.re, self.use_bytes)
+        assert all(self.callback.values())
+
+        for type_, f in self.user_callbacks.items():
+            if type_ in self.callback:
+                # Already a callback there, probably UnlessCallback
+                self.callback[type_] = CallChain(self.callback[type_], f, lambda t: t.type == type_)
+            else:
+                self.callback[type_] = f
+
+        self._scanner = Scanner(terminals, self.g_regex_flags, self.re, self.use_bytes)
+
+    @property
+    def scanner(self):
+        if self._scanner is None:
+            self._build_scanner()
+        return self._scanner
+
+    def match(self, text, pos):
+        return self.scanner.match(text, pos)
+
+    def next_token(self, lex_state: LexerState, parser_state: Any = None) -> Token:
+        line_ctr = lex_state.line_ctr
+        while line_ctr.char_pos < len(lex_state.text):
+            res = self.match(lex_state.text, line_ctr.char_pos)
+            if not res:
+                allowed = self.scanner.allowed_types - self.ignore_types
+                if not allowed:
+                    allowed = {""}
+                raise UnexpectedCharacters(lex_state.text, line_ctr.char_pos, line_ctr.line, line_ctr.column,
+                                           allowed=allowed, token_history=lex_state.last_token and [lex_state.last_token],
+                                           state=parser_state, terminals_by_name=self.terminals_by_name)
+
+            value, type_ = res
+
+            ignored = type_ in self.ignore_types
+            t = None
+            if not ignored or type_ in self.callback:
+                t = Token(type_, value, line_ctr.char_pos, line_ctr.line, line_ctr.column)
+            line_ctr.feed(value, type_ in self.newline_types)
+            if t is not None:
+                t.end_line = line_ctr.line
+                t.end_column = line_ctr.column
+                t.end_pos = line_ctr.char_pos
+                if t.type in self.callback:
+                    t = self.callback[t.type](t)
+                if not ignored:
+                    if not isinstance(t, Token):
+                        raise LexError("Callbacks must return a token (returned %r)" % t)
+                    lex_state.last_token = t
+                    return t
+
+        # EOF
+        raise EOFError(self)
+
+
+class ContextualLexer(Lexer):
+    lexers: Dict[int, AbstractBasicLexer]
+    root_lexer: AbstractBasicLexer
+
+    BasicLexer: Type[AbstractBasicLexer] = BasicLexer
+
+    def __init__(self, conf: 'LexerConf', states: Dict[int, Collection[str]], always_accept: Collection[str]=()) -> None:
+        terminals = list(conf.terminals)
+        terminals_by_name = conf.terminals_by_name
+
+        trad_conf = copy(conf)
+        trad_conf.terminals = terminals
+
+        if has_interegular and not conf.skip_validation:
+            comparator = interegular.Comparator.from_regexes({t: t.pattern.to_regexp() for t in terminals})
+        else:
+            comparator = None
+        lexer_by_tokens: Dict[FrozenSet[str], AbstractBasicLexer] = {}
+        self.lexers = {}
+        for state, accepts in states.items():
+            key = frozenset(accepts)
+            try:
+                lexer = lexer_by_tokens[key]
+            except KeyError:
+                accepts = set(accepts) | set(conf.ignore) | set(always_accept)
+                lexer_conf = copy(trad_conf)
+                lexer_conf.terminals = [terminals_by_name[n] for n in accepts if n in terminals_by_name]
+                lexer = self.BasicLexer(lexer_conf, comparator)
+                lexer_by_tokens[key] = lexer
+
+            self.lexers[state] = lexer
+
+        assert trad_conf.terminals is terminals
+        trad_conf.skip_validation = True  # We don't need to verify all terminals again
+        self.root_lexer = self.BasicLexer(trad_conf, comparator)
+
+    def lex(self, lexer_state: LexerState, parser_state: 'ParserState') -> Iterator[Token]:
+        try:
+            while True:
+                lexer = self.lexers[parser_state.position]
+                yield lexer.next_token(lexer_state, parser_state)
+        except EOFError:
+            pass
+        except UnexpectedCharacters as e:
+            # In the contextual lexer, UnexpectedCharacters can mean that the terminal is defined, but not in the current context.
+            # This tests the input against the global context, to provide a nicer error.
+            try:
+                last_token = lexer_state.last_token  # Save last_token. Calling root_lexer.next_token will change this to the wrong token
+                token = self.root_lexer.next_token(lexer_state, parser_state)
+                raise UnexpectedToken(token, e.allowed, state=parser_state, token_history=[last_token], terminals_by_name=self.root_lexer.terminals_by_name)
+            except UnexpectedCharacters:
+                raise e  # Raise the original UnexpectedCharacters. The root lexer raises it with the wrong expected set.
+
+###}
diff --git a/server/libs/lark/load_grammar.py b/server/libs/lark/load_grammar.py
new file mode 100644
index 0000000..362a845
--- /dev/null
+++ b/server/libs/lark/load_grammar.py
@@ -0,0 +1,1428 @@
+"""Parses and compiles Lark grammars into an internal representation.
+"""
+
+import hashlib
+import os.path
+import sys
+from collections import namedtuple
+from copy import copy, deepcopy
+import pkgutil
+from ast import literal_eval
+from contextlib import suppress
+from typing import List, Tuple, Union, Callable, Dict, Optional, Sequence, Generator
+
+from .utils import bfs, logger, classify_bool, is_id_continue, is_id_start, bfs_all_unique, small_factors, OrderedSet
+from .lexer import Token, TerminalDef, PatternStr, PatternRE, Pattern
+
+from .parse_tree_builder import ParseTreeBuilder
+from .parser_frontends import ParsingFrontend
+from .common import LexerConf, ParserConf
+from .grammar import RuleOptions, Rule, Terminal, NonTerminal, Symbol, TOKEN_DEFAULT_PRIORITY
+from .utils import classify, dedup_list
+from .exceptions import GrammarError, UnexpectedCharacters, UnexpectedToken, ParseError, UnexpectedInput
+
+from .tree import Tree, SlottedTree as ST
+from .visitors import Transformer, Visitor, v_args, Transformer_InPlace, Transformer_NonRecursive
+inline_args = v_args(inline=True)
+
+IMPORT_PATHS = ['grammars']
+
+EXT = '.lark'
+
+_RE_FLAGS = 'imslux'
+
+_EMPTY = Symbol('__empty__')
+
+_TERMINAL_NAMES = {
+    '.' : 'DOT',
+    ',' : 'COMMA',
+    ':' : 'COLON',
+    ';' : 'SEMICOLON',
+    '+' : 'PLUS',
+    '-' : 'MINUS',
+    '*' : 'STAR',
+    '/' : 'SLASH',
+    '\\' : 'BACKSLASH',
+    '|' : 'VBAR',
+    '?' : 'QMARK',
+    '!' : 'BANG',
+    '@' : 'AT',
+    '#' : 'HASH',
+    '$' : 'DOLLAR',
+    '%' : 'PERCENT',
+    '^' : 'CIRCUMFLEX',
+    '&' : 'AMPERSAND',
+    '_' : 'UNDERSCORE',
+    '<' : 'LESSTHAN',
+    '>' : 'MORETHAN',
+    '=' : 'EQUAL',
+    '"' : 'DBLQUOTE',
+    '\'' : 'QUOTE',
+    '`' : 'BACKQUOTE',
+    '~' : 'TILDE',
+    '(' : 'LPAR',
+    ')' : 'RPAR',
+    '{' : 'LBRACE',
+    '}' : 'RBRACE',
+    '[' : 'LSQB',
+    ']' : 'RSQB',
+    '\n' : 'NEWLINE',
+    '\r\n' : 'CRLF',
+    '\t' : 'TAB',
+    ' ' : 'SPACE',
+}
+
+# Grammar Parser
+TERMINALS = {
+    '_LPAR': r'\(',
+    '_RPAR': r'\)',
+    '_LBRA': r'\[',
+    '_RBRA': r'\]',
+    '_LBRACE': r'\{',
+    '_RBRACE': r'\}',
+    'OP': '[+*]|[?](?![a-z_])',
+    '_COLON': ':',
+    '_COMMA': ',',
+    '_OR': r'\|',
+    '_DOT': r'\.(?!\.)',
+    '_DOTDOT': r'\.\.',
+    'TILDE': '~',
+    'RULE_MODIFIERS': '(!|![?]?|[?]!?)(?=[_a-z])',
+    'RULE': '_?[a-z][_a-z0-9]*',
+    'TERMINAL': '_?[A-Z][_A-Z0-9]*',
+    'STRING': r'"(\\"|\\\\|[^"\n])*?"i?',
+    'REGEXP': r'/(?!/)(\\/|\\\\|[^/])*?/[%s]*' % _RE_FLAGS,
+    '_NL': r'(\r?\n)+\s*',
+    '_NL_OR': r'(\r?\n)+\s*\|',
+    'WS': r'[ \t]+',
+    'COMMENT': r'\s*//[^\n]*|\s*#[^\n]*',
+    'BACKSLASH': r'\\[ ]*\n',
+    '_TO': '->',
+    '_IGNORE': r'%ignore',
+    '_OVERRIDE': r'%override',
+    '_DECLARE': r'%declare',
+    '_EXTEND': r'%extend',
+    '_IMPORT': r'%import',
+    'NUMBER': r'[+-]?\d+',
+}
+
+RULES = {
+    'start': ['_list'],
+    '_list':  ['_item', '_list _item'],
+    '_item':  ['rule', 'term', 'ignore', 'import', 'declare', 'override', 'extend', '_NL'],
+
+    'rule': ['rule_modifiers RULE template_params priority _COLON expansions _NL'],
+    'rule_modifiers': ['RULE_MODIFIERS',
+                       ''],
+    'priority': ['_DOT NUMBER',
+                 ''],
+    'template_params': ['_LBRACE _template_params _RBRACE',
+                        ''],
+    '_template_params': ['RULE',
+                         '_template_params _COMMA RULE'],
+    'expansions': ['_expansions'],
+    '_expansions': ['alias',
+                    '_expansions _OR alias',
+                    '_expansions _NL_OR alias'],
+
+    '?alias':     ['expansion _TO nonterminal', 'expansion'],
+    'expansion': ['_expansion'],
+
+    '_expansion': ['', '_expansion expr'],
+
+    '?expr': ['atom',
+              'atom OP',
+              'atom TILDE NUMBER',
+              'atom TILDE NUMBER _DOTDOT NUMBER',
+              ],
+
+    '?atom': ['_LPAR expansions _RPAR',
+              'maybe',
+              'value'],
+
+    'value': ['terminal',
+              'nonterminal',
+              'literal',
+              'range',
+              'template_usage'],
+
+    'terminal': ['TERMINAL'],
+    'nonterminal': ['RULE'],
+
+    '?name': ['RULE', 'TERMINAL'],
+    '?symbol': ['terminal', 'nonterminal'],
+
+    'maybe': ['_LBRA expansions _RBRA'],
+    'range': ['STRING _DOTDOT STRING'],
+
+    'template_usage': ['nonterminal _LBRACE _template_args _RBRACE'],
+    '_template_args': ['value',
+                       '_template_args _COMMA value'],
+
+    'term': ['TERMINAL _COLON expansions _NL',
+             'TERMINAL _DOT NUMBER _COLON expansions _NL'],
+    'override': ['_OVERRIDE rule',
+                 '_OVERRIDE term'],
+    'extend': ['_EXTEND rule',
+               '_EXTEND term'],
+    'ignore': ['_IGNORE expansions _NL'],
+    'declare': ['_DECLARE _declare_args _NL'],
+    'import': ['_IMPORT _import_path _NL',
+               '_IMPORT _import_path _LPAR name_list _RPAR _NL',
+               '_IMPORT _import_path _TO name _NL'],
+
+    '_import_path': ['import_lib', 'import_rel'],
+    'import_lib': ['_import_args'],
+    'import_rel': ['_DOT _import_args'],
+    '_import_args': ['name', '_import_args _DOT name'],
+
+    'name_list': ['_name_list'],
+    '_name_list': ['name', '_name_list _COMMA name'],
+
+    '_declare_args': ['symbol', '_declare_args symbol'],
+    'literal': ['REGEXP', 'STRING'],
+}
+
+
+# Value 5 keeps the number of states in the lalr parser somewhat minimal
+# It isn't optimal, but close to it. See PR #949
+SMALL_FACTOR_THRESHOLD = 5
+# The Threshold whether repeat via ~ are split up into different rules
+# 50 is chosen since it keeps the number of states low and therefore lalr analysis time low,
+# while not being to overaggressive and unnecessarily creating rules that might create shift/reduce conflicts.
+# (See PR #949)
+REPEAT_BREAK_THRESHOLD = 50
+
+
+class FindRuleSize(Transformer):
+    def __init__(self, keep_all_tokens: bool):
+        self.keep_all_tokens = keep_all_tokens
+
+    def _will_not_get_removed(self, sym: Symbol) -> bool:
+        if isinstance(sym, NonTerminal):
+            return not sym.name.startswith('_')
+        if isinstance(sym, Terminal):
+            return self.keep_all_tokens or not sym.filter_out
+        if sym is _EMPTY:
+            return False
+        assert False, sym
+
+    def _args_as_int(self, args: List[Union[int, Symbol]]) -> Generator[int, None, None]:
+        for a in args:
+            if isinstance(a, int):
+                yield a
+            elif isinstance(a, Symbol):
+                yield 1 if self._will_not_get_removed(a) else 0
+            else:
+                assert False
+
+    def expansion(self, args) -> int:
+        return sum(self._args_as_int(args))
+
+    def expansions(self, args) -> int:
+        return max(self._args_as_int(args))
+
+
+@inline_args
+class EBNF_to_BNF(Transformer_InPlace):
+    def __init__(self):
+        self.new_rules = []
+        self.rules_cache = {}
+        self.prefix = 'anon'
+        self.i = 0
+        self.rule_options = None
+
+    def _name_rule(self, inner: str):
+        new_name = '__%s_%s_%d' % (self.prefix, inner, self.i)
+        self.i += 1
+        return new_name
+
+    def _add_rule(self, key, name, expansions):
+        t = NonTerminal(name)
+        self.new_rules.append((name, expansions, self.rule_options))
+        self.rules_cache[key] = t
+        return t
+
+    def _add_recurse_rule(self, type_: str, expr: Tree):
+        try:
+            return self.rules_cache[expr]
+        except KeyError:
+            new_name = self._name_rule(type_)
+            t = NonTerminal(new_name)
+            tree = ST('expansions', [
+                ST('expansion', [expr]),
+                ST('expansion', [t, expr])
+            ])
+            return self._add_rule(expr, new_name, tree)
+
+    def _add_repeat_rule(self, a, b, target, atom):
+        """Generate a rule that repeats target ``a`` times, and repeats atom ``b`` times.
+
+        When called recursively (into target), it repeats atom for x(n) times, where:
+            x(0) = 1
+            x(n) = a(n) * x(n-1) + b
+
+        Example rule when a=3, b=4:
+
+            new_rule: target target target atom atom atom atom
+
+        """
+        key = (a, b, target, atom)
+        try:
+            return self.rules_cache[key]
+        except KeyError:
+            new_name = self._name_rule('repeat_a%d_b%d' % (a, b))
+            tree = ST('expansions', [ST('expansion', [target] * a + [atom] * b)])
+            return self._add_rule(key, new_name, tree)
+
+    def _add_repeat_opt_rule(self, a, b, target, target_opt, atom):
+        """Creates a rule that matches atom 0 to (a*n+b)-1 times.
+
+        When target matches n times atom, and target_opt 0 to n-1 times target_opt,
+
+        First we generate target * i followed by target_opt, for i from 0 to a-1
+        These match 0 to n*a - 1 times atom
+
+        Then we generate target * a followed by atom * i, for i from 0 to b-1
+        These match n*a to n*a + b-1 times atom
+
+        The created rule will not have any shift/reduce conflicts so that it can be used with lalr
+
+        Example rule when a=3, b=4:
+
+            new_rule: target_opt
+                    | target target_opt
+                    | target target target_opt
+
+                    | target target target
+                    | target target target atom
+                    | target target target atom atom
+                    | target target target atom atom atom
+
+        """
+        key = (a, b, target, atom, "opt")
+        try:
+            return self.rules_cache[key]
+        except KeyError:
+            new_name = self._name_rule('repeat_a%d_b%d_opt' % (a, b))
+            tree = ST('expansions', [
+                ST('expansion', [target]*i + [target_opt]) for i in range(a)
+            ] + [
+                ST('expansion', [target]*a + [atom]*i) for i in range(b)
+            ])
+            return self._add_rule(key, new_name, tree)
+
+    def _generate_repeats(self, rule: Tree, mn: int, mx: int):
+        """Generates a rule tree that repeats ``rule`` exactly between ``mn`` to ``mx`` times.
+        """
+        # For a small number of repeats, we can take the naive approach
+        if mx < REPEAT_BREAK_THRESHOLD:
+            return ST('expansions', [ST('expansion', [rule] * n) for n in range(mn, mx + 1)])
+
+        # For large repeat values, we break the repetition into sub-rules.
+        # We treat ``rule~mn..mx`` as ``rule~mn rule~0..(diff=mx-mn)``.
+        # We then use small_factors to split up mn and diff up into values [(a, b), ...]
+        # This values are used with the help of _add_repeat_rule and _add_repeat_rule_opt
+        # to generate a complete rule/expression that matches the corresponding number of repeats
+        mn_target = rule
+        for a, b in small_factors(mn, SMALL_FACTOR_THRESHOLD):
+            mn_target = self._add_repeat_rule(a, b, mn_target, rule)
+        if mx == mn:
+            return mn_target
+
+        diff = mx - mn + 1  # We add one because _add_repeat_opt_rule generates rules that match one less
+        diff_factors = small_factors(diff, SMALL_FACTOR_THRESHOLD)
+        diff_target = rule  # Match rule 1 times
+        diff_opt_target = ST('expansion', [])  # match rule 0 times (e.g. up to 1 -1 times)
+        for a, b in diff_factors[:-1]:
+            diff_opt_target = self._add_repeat_opt_rule(a, b, diff_target, diff_opt_target, rule)
+            diff_target = self._add_repeat_rule(a, b, diff_target, rule)
+
+        a, b = diff_factors[-1]
+        diff_opt_target = self._add_repeat_opt_rule(a, b, diff_target, diff_opt_target, rule)
+
+        return ST('expansions', [ST('expansion', [mn_target] + [diff_opt_target])])
+
+    def expr(self, rule: Tree, op: Token, *args):
+        if op.value == '?':
+            empty = ST('expansion', [])
+            return ST('expansions', [rule, empty])
+        elif op.value == '+':
+            # a : b c+ d
+            #   -->
+            # a : b _c d
+            # _c : _c c | c;
+            return self._add_recurse_rule('plus', rule)
+        elif op.value == '*':
+            # a : b c* d
+            #   -->
+            # a : b _c? d
+            # _c : _c c | c;
+            new_name = self._add_recurse_rule('star', rule)
+            return ST('expansions', [new_name, ST('expansion', [])])
+        elif op.value == '~':
+            if len(args) == 1:
+                mn = mx = int(args[0])
+            else:
+                mn, mx = map(int, args)
+                if mx < mn or mn < 0:
+                    raise GrammarError("Bad Range for %s (%d..%d isn't allowed)" % (rule, mn, mx))
+
+            return self._generate_repeats(rule, mn, mx)
+
+        assert False, op
+
+    def maybe(self, rule: Tree):
+        keep_all_tokens = self.rule_options and self.rule_options.keep_all_tokens
+        rule_size = FindRuleSize(keep_all_tokens).transform(rule)
+        empty = ST('expansion', [_EMPTY] * rule_size)
+        return ST('expansions', [rule, empty])
+
+
+class SimplifyRule_Visitor(Visitor):
+
+    @staticmethod
+    def _flatten(tree: Tree):
+        while tree.expand_kids_by_data(tree.data):
+            pass
+
+    def expansion(self, tree: Tree):
+        # rules_list unpacking
+        # a : b (c|d) e
+        #  -->
+        # a : b c e | b d e
+        #
+        # In AST terms:
+        # expansion(b, expansions(c, d), e)
+        #   -->
+        # expansions( expansion(b, c, e), expansion(b, d, e) )
+
+        self._flatten(tree)
+
+        for i, child in enumerate(tree.children):
+            if isinstance(child, Tree) and child.data == 'expansions':
+                tree.data = 'expansions'
+                tree.children = [self.visit(ST('expansion', [option if i == j else other
+                                                             for j, other in enumerate(tree.children)]))
+                                 for option in dedup_list(child.children)]
+                self._flatten(tree)
+                break
+
+    def alias(self, tree):
+        rule, alias_name = tree.children
+        if rule.data == 'expansions':
+            aliases = []
+            for child in tree.children[0].children:
+                aliases.append(ST('alias', [child, alias_name]))
+            tree.data = 'expansions'
+            tree.children = aliases
+
+    def expansions(self, tree: Tree):
+        self._flatten(tree)
+        # Ensure all children are unique
+        if len(set(tree.children)) != len(tree.children):
+            tree.children = dedup_list(tree.children)   # dedup is expensive, so try to minimize its use
+
+
+class RuleTreeToText(Transformer):
+    def expansions(self, x):
+        return x
+
+    def expansion(self, symbols):
+        return symbols, None
+
+    def alias(self, x):
+        (expansion, _alias), alias = x
+        assert _alias is None, (alias, expansion, '-', _alias)  # Double alias not allowed
+        return expansion, alias.name
+
+
+class PrepareAnonTerminals(Transformer_InPlace):
+    """Create a unique list of anonymous terminals. Attempt to give meaningful names to them when we add them"""
+
+    def __init__(self, terminals):
+        self.terminals = terminals
+        self.term_set = {td.name for td in self.terminals}
+        self.term_reverse = {td.pattern: td for td in terminals}
+        self.i = 0
+        self.rule_options = None
+
+    @inline_args
+    def pattern(self, p):
+        value = p.value
+        if p in self.term_reverse and p.flags != self.term_reverse[p].pattern.flags:
+            raise GrammarError(u'Conflicting flags for the same terminal: %s' % p)
+
+        term_name = None
+
+        if isinstance(p, PatternStr):
+            try:
+                # If already defined, use the user-defined terminal name
+                term_name = self.term_reverse[p].name
+            except KeyError:
+                # Try to assign an indicative anon-terminal name
+                try:
+                    term_name = _TERMINAL_NAMES[value]
+                except KeyError:
+                    if value and is_id_continue(value) and is_id_start(value[0]) and value.upper() not in self.term_set:
+                        term_name = value.upper()
+
+                if term_name in self.term_set:
+                    term_name = None
+
+        elif isinstance(p, PatternRE):
+            if p in self.term_reverse:  # Kind of a weird placement.name
+                term_name = self.term_reverse[p].name
+        else:
+            assert False, p
+
+        if term_name is None:
+            term_name = '__ANON_%d' % self.i
+            self.i += 1
+
+        if term_name not in self.term_set:
+            assert p not in self.term_reverse
+            self.term_set.add(term_name)
+            termdef = TerminalDef(term_name, p)
+            self.term_reverse[p] = termdef
+            self.terminals.append(termdef)
+
+        filter_out = False if self.rule_options and self.rule_options.keep_all_tokens else isinstance(p, PatternStr)
+
+        return Terminal(term_name, filter_out=filter_out)
+
+
+class _ReplaceSymbols(Transformer_InPlace):
+    """Helper for ApplyTemplates"""
+
+    def __init__(self):
+        self.names = {}
+
+    def value(self, c):
+        if len(c) == 1 and isinstance(c[0], Symbol) and c[0].name in self.names:
+            return self.names[c[0].name]
+        return self.__default__('value', c, None)
+
+    def template_usage(self, c):
+        name = c[0].name
+        if name in self.names:
+            return self.__default__('template_usage', [self.names[name]] + c[1:], None)
+        return self.__default__('template_usage', c, None)
+
+
+class ApplyTemplates(Transformer_InPlace):
+    """Apply the templates, creating new rules that represent the used templates"""
+
+    def __init__(self, rule_defs):
+        self.rule_defs = rule_defs
+        self.replacer = _ReplaceSymbols()
+        self.created_templates = set()
+
+    def template_usage(self, c):
+        name = c[0].name
+        args = c[1:]
+        result_name = "%s{%s}" % (name, ",".join(a.name for a in args))
+        if result_name not in self.created_templates:
+            self.created_templates.add(result_name)
+            (_n, params, tree, options) ,= (t for t in self.rule_defs if t[0] == name)
+            assert len(params) == len(args), args
+            result_tree = deepcopy(tree)
+            self.replacer.names = dict(zip(params, args))
+            self.replacer.transform(result_tree)
+            self.rule_defs.append((result_name, [], result_tree, deepcopy(options)))
+        return NonTerminal(result_name)
+
+
+def _rfind(s, choices):
+    return max(s.rfind(c) for c in choices)
+
+
+def eval_escaping(s):
+    w = ''
+    i = iter(s)
+    for n in i:
+        w += n
+        if n == '\\':
+            try:
+                n2 = next(i)
+            except StopIteration:
+                raise GrammarError("Literal ended unexpectedly (bad escaping): `%r`" % s)
+            if n2 == '\\':
+                w += '\\\\'
+            elif n2 not in 'Uuxnftr':
+                w += '\\'
+            w += n2
+    w = w.replace('\\"', '"').replace("'", "\\'")
+
+    to_eval = "u'''%s'''" % w
+    try:
+        s = literal_eval(to_eval)
+    except SyntaxError as e:
+        raise GrammarError(s, e)
+
+    return s
+
+
+def _literal_to_pattern(literal):
+    assert isinstance(literal, Token)
+    v = literal.value
+    flag_start = _rfind(v, '/"')+1
+    assert flag_start > 0
+    flags = v[flag_start:]
+    assert all(f in _RE_FLAGS for f in flags), flags
+
+    if literal.type == 'STRING' and '\n' in v:
+        raise GrammarError('You cannot put newlines in string literals')
+
+    if literal.type == 'REGEXP' and '\n' in v and 'x' not in flags:
+        raise GrammarError('You can only use newlines in regular expressions '
+                           'with the `x` (verbose) flag')
+
+    v = v[:flag_start]
+    assert v[0] == v[-1] and v[0] in '"/'
+    x = v[1:-1]
+
+    s = eval_escaping(x)
+
+    if s == "":
+        raise GrammarError("Empty terminals are not allowed (%s)" % literal)
+
+    if literal.type == 'STRING':
+        s = s.replace('\\\\', '\\')
+        return PatternStr(s, flags, raw=literal.value)
+    elif literal.type == 'REGEXP':
+        return PatternRE(s, flags, raw=literal.value)
+    else:
+        assert False, 'Invariant failed: literal.type not in ["STRING", "REGEXP"]'
+
+
+@inline_args
+class PrepareLiterals(Transformer_InPlace):
+    def literal(self, literal):
+        return ST('pattern', [_literal_to_pattern(literal)])
+
+    def range(self, start, end):
+        assert start.type == end.type == 'STRING'
+        start = start.value[1:-1]
+        end = end.value[1:-1]
+        assert len(eval_escaping(start)) == len(eval_escaping(end)) == 1
+        regexp = '[%s-%s]' % (start, end)
+        return ST('pattern', [PatternRE(regexp)])
+
+
+def _make_joined_pattern(regexp, flags_set) -> PatternRE:
+    return PatternRE(regexp, ())
+
+class TerminalTreeToPattern(Transformer_NonRecursive):
+    def pattern(self, ps):
+        p ,= ps
+        return p
+
+    def expansion(self, items: List[Pattern]) -> Pattern:
+        if not items:
+            return PatternStr('')
+
+        if len(items) == 1:
+            return items[0]
+
+        pattern = ''.join(i.to_regexp() for i in items)
+        return _make_joined_pattern(pattern, {i.flags for i in items})
+
+    def expansions(self, exps: List[Pattern]) -> Pattern:
+        if len(exps) == 1:
+            return exps[0]
+
+        # Do a bit of sorting to make sure that the longest option is returned
+        # (Python's re module otherwise prefers just 'l' when given (l|ll) and both could match)
+        exps.sort(key=lambda x: (-x.max_width, -x.min_width, -len(x.value)))
+
+        pattern = '(?:%s)' % ('|'.join(i.to_regexp() for i in exps))
+        return _make_joined_pattern(pattern, {i.flags for i in exps})
+
+    def expr(self, args) -> Pattern:
+        inner: Pattern
+        inner, op = args[:2]
+        if op == '~':
+            if len(args) == 3:
+                op = "{%d}" % int(args[2])
+            else:
+                mn, mx = map(int, args[2:])
+                if mx < mn:
+                    raise GrammarError("Bad Range for %s (%d..%d isn't allowed)" % (inner, mn, mx))
+                op = "{%d,%d}" % (mn, mx)
+        else:
+            assert len(args) == 2
+        return PatternRE('(?:%s)%s' % (inner.to_regexp(), op), inner.flags)
+
+    def maybe(self, expr):
+        return self.expr(expr + ['?'])
+
+    def alias(self, t):
+        raise GrammarError("Aliasing not allowed in terminals (You used -> in the wrong place)")
+
+    def value(self, v):
+        return v[0]
+
+
+class ValidateSymbols(Transformer_InPlace):
+    def value(self, v):
+        v ,= v
+        assert isinstance(v, (Tree, Symbol))
+        return v
+
+
+def nr_deepcopy_tree(t):
+    """Deepcopy tree `t` without recursion"""
+    return Transformer_NonRecursive(False).transform(t)
+
+
+class Grammar:
+
+    term_defs: List[Tuple[str, Tuple[Tree, int]]]
+    rule_defs: List[Tuple[str, Tuple[str, ...], Tree, RuleOptions]]
+    ignore: List[str]
+
+    def __init__(self, rule_defs: List[Tuple[str, Tuple[str, ...], Tree, RuleOptions]], term_defs: List[Tuple[str, Tuple[Tree, int]]], ignore: List[str]) -> None:
+        self.term_defs = term_defs
+        self.rule_defs = rule_defs
+        self.ignore = ignore
+
+    def compile(self, start, terminals_to_keep) -> Tuple[List[TerminalDef], List[Rule], List[str]]:
+        # We change the trees in-place (to support huge grammars)
+        # So deepcopy allows calling compile more than once.
+        term_defs = [(n, (nr_deepcopy_tree(t), p)) for n, (t, p) in self.term_defs]
+        rule_defs = [(n, p, nr_deepcopy_tree(t), o) for n, p, t, o in self.rule_defs]
+
+        # ===================
+        #  Compile Terminals
+        # ===================
+
+        # Convert terminal-trees to strings/regexps
+
+        for name, (term_tree, priority) in term_defs:
+            if term_tree is None:  # Terminal added through %declare
+                continue
+            expansions = list(term_tree.find_data('expansion'))
+            if len(expansions) == 1 and not expansions[0].children:
+                raise GrammarError("Terminals cannot be empty (%s)" % name)
+
+        transformer = PrepareLiterals() * TerminalTreeToPattern()
+        terminals = [TerminalDef(name, transformer.transform(term_tree), priority)
+                     for name, (term_tree, priority) in term_defs if term_tree]
+
+        # =================
+        #  Compile Rules
+        # =================
+
+        # 1. Pre-process terminals
+        anon_tokens_transf = PrepareAnonTerminals(terminals)
+        transformer = PrepareLiterals() * ValidateSymbols() * anon_tokens_transf  # Adds to terminals
+
+        # 2. Inline Templates
+
+        transformer *= ApplyTemplates(rule_defs)
+
+        # 3. Convert EBNF to BNF (and apply step 1 & 2)
+        ebnf_to_bnf = EBNF_to_BNF()
+        rules = []
+        i = 0
+        while i < len(rule_defs):  # We have to do it like this because rule_defs might grow due to templates
+            name, params, rule_tree, options = rule_defs[i]
+            i += 1
+            if len(params) != 0:  # Dont transform templates
+                continue
+            rule_options = RuleOptions(keep_all_tokens=True) if options and options.keep_all_tokens else None
+            ebnf_to_bnf.rule_options = rule_options
+            ebnf_to_bnf.prefix = name
+            anon_tokens_transf.rule_options = rule_options
+            tree = transformer.transform(rule_tree)
+            res: Tree = ebnf_to_bnf.transform(tree)
+            rules.append((name, res, options))
+        rules += ebnf_to_bnf.new_rules
+
+        assert len(rules) == len({name for name, _t, _o in rules}), "Whoops, name collision"
+
+        # 4. Compile tree to Rule objects
+        rule_tree_to_text = RuleTreeToText()
+
+        simplify_rule = SimplifyRule_Visitor()
+        compiled_rules: List[Rule] = []
+        for rule_content in rules:
+            name, tree, options = rule_content
+            simplify_rule.visit(tree)
+            expansions = rule_tree_to_text.transform(tree)
+
+            for i, (expansion, alias) in enumerate(expansions):
+                if alias and name.startswith('_'):
+                    raise GrammarError("Rule %s is marked for expansion (it starts with an underscore) and isn't allowed to have aliases (alias=%s)"% (name, alias))
+
+                empty_indices = tuple(x==_EMPTY for x in expansion)
+                if any(empty_indices):
+                    exp_options = copy(options) or RuleOptions()
+                    exp_options.empty_indices = empty_indices
+                    expansion = [x for x in expansion if x!=_EMPTY]
+                else:
+                    exp_options = options
+
+                for sym in expansion:
+                    assert isinstance(sym, Symbol)
+                    if sym.is_term and exp_options and exp_options.keep_all_tokens:
+                        assert isinstance(sym, Terminal)
+                        sym.filter_out = False
+                rule = Rule(NonTerminal(name), expansion, i, alias, exp_options)
+                compiled_rules.append(rule)
+
+        # Remove duplicates of empty rules, throw error for non-empty duplicates
+        if len(set(compiled_rules)) != len(compiled_rules):
+            duplicates = classify(compiled_rules, lambda x: x)
+            for dups in duplicates.values():
+                if len(dups) > 1:
+                    if dups[0].expansion:
+                        raise GrammarError("Rules defined twice: %s\n\n(Might happen due to colliding expansion of optionals: [] or ?)"
+                                           % ''.join('\n  * %s' % i for i in dups))
+
+                    # Empty rule; assert all other attributes are equal
+                    assert len({(r.alias, r.order, r.options) for r in dups}) == len(dups)
+
+            # Remove duplicates
+            compiled_rules = list(OrderedSet(compiled_rules))
+
+        # Filter out unused rules
+        while True:
+            c = len(compiled_rules)
+            used_rules = {s for r in compiled_rules
+                            for s in r.expansion
+                            if isinstance(s, NonTerminal)
+                            and s != r.origin}
+            used_rules |= {NonTerminal(s) for s in start}
+            compiled_rules, unused = classify_bool(compiled_rules, lambda r: r.origin in used_rules)
+            for r in unused:
+                logger.debug("Unused rule: %s", r)
+            if len(compiled_rules) == c:
+                break
+
+        # Filter out unused terminals
+        if terminals_to_keep != '*':
+            used_terms = {t.name for r in compiled_rules
+                                 for t in r.expansion
+                                 if isinstance(t, Terminal)}
+            terminals, unused = classify_bool(terminals, lambda t: t.name in used_terms or t.name in self.ignore or t.name in terminals_to_keep)
+            if unused:
+                logger.debug("Unused terminals: %s", [t.name for t in unused])
+
+        return terminals, compiled_rules, self.ignore
+
+
+PackageResource = namedtuple('PackageResource', 'pkg_name path')
+
+
+class FromPackageLoader:
+    """
+    Provides a simple way of creating custom import loaders that load from packages via ``pkgutil.get_data`` instead of using `open`.
+    This allows them to be compatible even from within zip files.
+
+    Relative imports are handled, so you can just freely use them.
+
+    pkg_name: The name of the package. You can probably provide `__name__` most of the time
+    search_paths: All the path that will be search on absolute imports.
+    """
+
+    pkg_name: str
+    search_paths: Sequence[str]
+
+    def __init__(self, pkg_name: str, search_paths: Sequence[str]=("", )) -> None:
+        self.pkg_name = pkg_name
+        self.search_paths = search_paths
+
+    def __repr__(self):
+        return "%s(%r, %r)" % (type(self).__name__, self.pkg_name, self.search_paths)
+
+    def __call__(self, base_path: Union[None, str, PackageResource], grammar_path: str) -> Tuple[PackageResource, str]:
+        if base_path is None:
+            to_try = self.search_paths
+        else:
+            # Check whether or not the importing grammar was loaded by this module.
+            if not isinstance(base_path, PackageResource) or base_path.pkg_name != self.pkg_name:
+                # Technically false, but FileNotFound doesn't exist in python2.7, and this message should never reach the end user anyway
+                raise IOError()
+            to_try = [base_path.path]
+
+        err = None
+        for path in to_try:
+            full_path = os.path.join(path, grammar_path)
+            try:
+                text: Optional[bytes] = pkgutil.get_data(self.pkg_name, full_path)
+            except IOError as e:
+                err = e
+                continue
+            else:
+                return PackageResource(self.pkg_name, full_path), (text.decode() if text else '')
+
+        raise IOError('Cannot find grammar in given paths') from err
+
+
+stdlib_loader = FromPackageLoader('lark', IMPORT_PATHS)
+
+
+
+def resolve_term_references(term_dict):
+    # TODO Solve with transitive closure (maybe)
+
+    while True:
+        changed = False
+        for name, token_tree in term_dict.items():
+            if token_tree is None:  # Terminal added through %declare
+                continue
+            for exp in token_tree.find_data('value'):
+                item ,= exp.children
+                if isinstance(item, NonTerminal):
+                    raise GrammarError("Rules aren't allowed inside terminals (%s in %s)" % (item, name))
+                elif isinstance(item, Terminal):
+                    try:
+                        term_value = term_dict[item.name]
+                    except KeyError:
+                        raise GrammarError("Terminal used but not defined: %s" % item.name)
+                    assert term_value is not None
+                    exp.children[0] = term_value
+                    changed = True
+                else:
+                    assert isinstance(item, Tree)
+        if not changed:
+            break
+
+    for name, term in term_dict.items():
+        if term:    # Not just declared
+            for child in term.children:
+                ids = [id(x) for x in child.iter_subtrees()]
+                if id(term) in ids:
+                    raise GrammarError("Recursion in terminal '%s' (recursion is only allowed in rules, not terminals)" % name)
+
+
+
+def symbol_from_strcase(s):
+    assert isinstance(s, str)
+    return Terminal(s, filter_out=s.startswith('_')) if s.isupper() else NonTerminal(s)
+
+@inline_args
+class PrepareGrammar(Transformer_InPlace):
+    def terminal(self, name):
+        return Terminal(str(name), filter_out=name.startswith('_'))
+
+    def nonterminal(self, name):
+        return NonTerminal(name.value)
+
+
+def _find_used_symbols(tree):
+    assert tree.data == 'expansions'
+    return {t.name for x in tree.find_data('expansion')
+            for t in x.scan_values(lambda t: isinstance(t, Symbol))}
+
+
+def _get_parser():
+    try:
+        return _get_parser.cache
+    except AttributeError:
+        terminals = [TerminalDef(name, PatternRE(value)) for name, value in TERMINALS.items()]
+
+        rules = [(name.lstrip('?'), x, RuleOptions(expand1=name.startswith('?')))
+                for name, x in RULES.items()]
+        rules = [Rule(NonTerminal(r), [symbol_from_strcase(s) for s in x.split()], i, None, o)
+                 for r, xs, o in rules for i, x in enumerate(xs)]
+
+        callback = ParseTreeBuilder(rules, ST).create_callback()
+        import re
+        lexer_conf = LexerConf(terminals, re, ['WS', 'COMMENT', 'BACKSLASH'])
+        parser_conf = ParserConf(rules, callback, ['start'])
+        lexer_conf.lexer_type = 'basic'
+        parser_conf.parser_type = 'lalr'
+        _get_parser.cache = ParsingFrontend(lexer_conf, parser_conf, None)
+        return _get_parser.cache
+
+GRAMMAR_ERRORS = [
+        ('Incorrect type of value', ['a: 1\n']),
+        ('Unclosed parenthesis', ['a: (\n']),
+        ('Unmatched closing parenthesis', ['a: )\n', 'a: [)\n', 'a: (]\n']),
+        ('Expecting rule or terminal definition (missing colon)', ['a\n', 'A\n', 'a->\n', 'A->\n', 'a A\n']),
+        ('Illegal name for rules or terminals', ['Aa:\n']),
+        ('Alias expects lowercase name', ['a: -> "a"\n']),
+        ('Unexpected colon', ['a::\n', 'a: b:\n', 'a: B:\n', 'a: "a":\n']),
+        ('Misplaced operator', ['a: b??', 'a: b(?)', 'a:+\n', 'a:?\n', 'a:*\n', 'a:|*\n']),
+        ('Expecting option ("|") or a new rule or terminal definition', ['a:a\n()\n']),
+        ('Terminal names cannot contain dots', ['A.B\n']),
+        ('Expecting rule or terminal definition', ['"a"\n']),
+        ('%import expects a name', ['%import "a"\n']),
+        ('%ignore expects a value', ['%ignore %import\n']),
+    ]
+
+def _translate_parser_exception(parse, e):
+        error = e.match_examples(parse, GRAMMAR_ERRORS, use_accepts=True)
+        if error:
+            return error
+        elif 'STRING' in e.expected:
+            return "Expecting a value"
+
+def _parse_grammar(text, name, start='start'):
+    try:
+        tree = _get_parser().parse(text + '\n', start)
+    except UnexpectedCharacters as e:
+        context = e.get_context(text)
+        raise GrammarError("Unexpected input at line %d column %d in %s: \n\n%s" %
+                           (e.line, e.column, name, context))
+    except UnexpectedToken as e:
+        context = e.get_context(text)
+        error = _translate_parser_exception(_get_parser().parse, e)
+        if error:
+            raise GrammarError("%s, at line %s column %s\n\n%s" % (error, e.line, e.column, context))
+        raise
+
+    return PrepareGrammar().transform(tree)
+
+
+def _error_repr(error):
+    if isinstance(error, UnexpectedToken):
+        error2 = _translate_parser_exception(_get_parser().parse, error)
+        if error2:
+            return error2
+        expected = ', '.join(error.accepts or error.expected)
+        return "Unexpected token %r. Expected one of: {%s}" % (str(error.token), expected)
+    else:
+        return str(error)
+
+def _search_interactive_parser(interactive_parser, predicate):
+    def expand(node):
+        path, p = node
+        for choice in p.choices():
+            t = Token(choice, '')
+            try:
+                new_p = p.feed_token(t)
+            except ParseError:    # Illegal
+                pass
+            else:
+                yield path + (choice,), new_p
+
+    for path, p in bfs_all_unique([((), interactive_parser)], expand):
+        if predicate(p):
+            return path, p
+
+def find_grammar_errors(text: str, start: str='start') -> List[Tuple[UnexpectedInput, str]]:
+    errors = []
+    def on_error(e):
+        errors.append((e, _error_repr(e)))
+
+        # recover to a new line
+        token_path, _ = _search_interactive_parser(e.interactive_parser.as_immutable(), lambda p: '_NL' in p.choices())
+        for token_type in token_path:
+            e.interactive_parser.feed_token(Token(token_type, ''))
+        e.interactive_parser.feed_token(Token('_NL', '\n'))
+        return True
+
+    _tree = _get_parser().parse(text + '\n', start, on_error=on_error)
+
+    errors_by_line = classify(errors, lambda e: e[0].line)
+    errors = [el[0] for el in errors_by_line.values()]      # already sorted
+
+    for e in errors:
+        e[0].interactive_parser = None
+    return errors
+
+
+def _get_mangle(prefix, aliases, base_mangle=None):
+    def mangle(s):
+        if s in aliases:
+            s = aliases[s]
+        else:
+            if s[0] == '_':
+                s = '_%s__%s' % (prefix, s[1:])
+            else:
+                s = '%s__%s' % (prefix, s)
+        if base_mangle is not None:
+            s = base_mangle(s)
+        return s
+    return mangle
+
+def _mangle_definition_tree(exp, mangle):
+    if mangle is None:
+        return exp
+    exp = deepcopy(exp) # TODO: is this needed?
+    for t in exp.iter_subtrees():
+        for i, c in enumerate(t.children):
+            if isinstance(c, Symbol):
+                t.children[i] = c.renamed(mangle)
+
+    return exp
+
+def _make_rule_tuple(modifiers_tree, name, params, priority_tree, expansions):
+    if modifiers_tree.children:
+        m ,= modifiers_tree.children
+        expand1 = '?' in m
+        if expand1 and name.startswith('_'):
+            raise GrammarError("Inlined rules (_rule) cannot use the ?rule modifier.")
+        keep_all_tokens = '!' in m
+    else:
+        keep_all_tokens = False
+        expand1 = False
+
+    if priority_tree.children:
+        p ,= priority_tree.children
+        priority = int(p)
+    else:
+        priority = None
+
+    if params is not None:
+        params = [t.value for t in params.children]  # For the grammar parser
+
+    return name, params, expansions, RuleOptions(keep_all_tokens, expand1, priority=priority,
+                                                 template_source=(name if params else None))
+
+
+class Definition:
+    def __init__(self, is_term, tree, params=(), options=None):
+        self.is_term = is_term
+        self.tree = tree
+        self.params = tuple(params)
+        self.options = options
+
+class GrammarBuilder:
+
+    global_keep_all_tokens: bool
+    import_paths: List[Union[str, Callable]]
+    used_files: Dict[str, str]
+
+    _definitions: Dict[str, Definition]
+    _ignore_names: List[str]
+
+    def __init__(self, global_keep_all_tokens: bool=False, import_paths: Optional[List[Union[str, Callable]]]=None, used_files: Optional[Dict[str, str]]=None) -> None:
+        self.global_keep_all_tokens = global_keep_all_tokens
+        self.import_paths = import_paths or []
+        self.used_files = used_files or {}
+
+        self._definitions: Dict[str, Definition] = {}
+        self._ignore_names: List[str] = []
+
+    def _grammar_error(self, is_term, msg, *names):
+        args = {}
+        for i, name in enumerate(names, start=1):
+            postfix = '' if i == 1 else str(i)
+            args['name' + postfix] = name
+            args['type' + postfix] = lowercase_type = ("rule", "terminal")[is_term]
+            args['Type' + postfix] = lowercase_type.title()
+        raise GrammarError(msg.format(**args))
+
+    def _check_options(self, is_term, options):
+        if is_term:
+            if options is None:
+                options = 1
+            elif not isinstance(options, int):
+                raise GrammarError("Terminal require a single int as 'options' (e.g. priority), got %s" % (type(options),))
+        else:
+            if options is None:
+                options = RuleOptions()
+            elif not isinstance(options, RuleOptions):
+                raise GrammarError("Rules require a RuleOptions instance as 'options'")
+            if self.global_keep_all_tokens:
+                options.keep_all_tokens = True
+        return options
+
+
+    def _define(self, name, is_term, exp, params=(), options=None, *, override=False):
+        if name in self._definitions:
+            if not override:
+                self._grammar_error(is_term, "{Type} '{name}' defined more than once", name)
+        elif override:
+            self._grammar_error(is_term, "Cannot override a nonexisting {type} {name}", name)
+
+        if name.startswith('__'):
+            self._grammar_error(is_term, 'Names starting with double-underscore are reserved (Error at {name})', name)
+
+        self._definitions[name] = Definition(is_term, exp, params, self._check_options(is_term, options))
+
+    def _extend(self, name, is_term, exp, params=(), options=None):
+        if name not in self._definitions:
+            self._grammar_error(is_term, "Can't extend {type} {name} as it wasn't defined before", name)
+
+        d = self._definitions[name]
+
+        if is_term != d.is_term:
+            self._grammar_error(is_term, "Cannot extend {type} {name} - one is a terminal, while the other is not.", name)
+        if tuple(params) != d.params:
+            self._grammar_error(is_term, "Cannot extend {type} with different parameters: {name}", name)
+
+        if d.tree is None:
+            self._grammar_error(is_term, "Can't extend {type} {name} - it is abstract.", name)
+
+        # TODO: think about what to do with 'options'
+        base = d.tree
+
+        assert isinstance(base, Tree) and base.data == 'expansions'
+        base.children.insert(0, exp)
+
+    def _ignore(self, exp_or_name):
+        if isinstance(exp_or_name, str):
+            self._ignore_names.append(exp_or_name)
+        else:
+            assert isinstance(exp_or_name, Tree)
+            t = exp_or_name
+            if t.data == 'expansions' and len(t.children) == 1:
+                t2 ,= t.children
+                if t2.data=='expansion' and len(t2.children) == 1:
+                    item ,= t2.children
+                    if item.data == 'value':
+                        item ,= item.children
+                        if isinstance(item, Terminal):
+                            # Keep terminal name, no need to create a new definition
+                            self._ignore_names.append(item.name)
+                            return
+
+            name = '__IGNORE_%d'% len(self._ignore_names)
+            self._ignore_names.append(name)
+            self._definitions[name] = Definition(True, t, options=TOKEN_DEFAULT_PRIORITY)
+
+    def _unpack_import(self, stmt, grammar_name):
+        if len(stmt.children) > 1:
+            path_node, arg1 = stmt.children
+        else:
+            path_node, = stmt.children
+            arg1 = None
+
+        if isinstance(arg1, Tree):  # Multi import
+            dotted_path = tuple(path_node.children)
+            names = arg1.children
+            aliases = dict(zip(names, names))  # Can't have aliased multi import, so all aliases will be the same as names
+        else:  # Single import
+            dotted_path = tuple(path_node.children[:-1])
+            if not dotted_path:
+                name ,= path_node.children
+                raise GrammarError("Nothing was imported from grammar `%s`" % name)
+            name = path_node.children[-1]  # Get name from dotted path
+            aliases = {name.value: (arg1 or name).value}  # Aliases if exist
+
+        if path_node.data == 'import_lib':  # Import from library
+            base_path = None
+        else:  # Relative import
+            if grammar_name == '':  # Import relative to script file path if grammar is coded in script
+                try:
+                    base_file = os.path.abspath(sys.modules['__main__'].__file__)
+                except AttributeError:
+                    base_file = None
+            else:
+                base_file = grammar_name  # Import relative to grammar file path if external grammar file
+            if base_file:
+                if isinstance(base_file, PackageResource):
+                    base_path = PackageResource(base_file.pkg_name, os.path.split(base_file.path)[0])
+                else:
+                    base_path = os.path.split(base_file)[0]
+            else:
+                base_path = os.path.abspath(os.path.curdir)
+
+        return dotted_path, base_path, aliases
+
+    def _unpack_definition(self, tree, mangle):
+
+        if tree.data == 'rule':
+            name, params, exp, opts = _make_rule_tuple(*tree.children)
+            is_term = False
+        else:
+            name = tree.children[0].value
+            params = ()     # TODO terminal templates
+            opts = int(tree.children[1]) if len(tree.children) == 3 else TOKEN_DEFAULT_PRIORITY # priority
+            exp = tree.children[-1]
+            is_term = True
+
+        if mangle is not None:
+            params = tuple(mangle(p) for p in params)
+            name = mangle(name)
+
+        exp = _mangle_definition_tree(exp, mangle)
+        return name, is_term, exp, params, opts
+
+
+    def load_grammar(self, grammar_text: str, grammar_name: str="", mangle: Optional[Callable[[str], str]]=None) -> None:
+        tree = _parse_grammar(grammar_text, grammar_name)
+
+        imports: Dict[Tuple[str, ...], Tuple[Optional[str], Dict[str, str]]] = {}
+
+        for stmt in tree.children:
+            if stmt.data == 'import':
+                dotted_path, base_path, aliases = self._unpack_import(stmt, grammar_name)
+                try:
+                    import_base_path, import_aliases = imports[dotted_path]
+                    assert base_path == import_base_path, 'Inconsistent base_path for %s.' % '.'.join(dotted_path)
+                    import_aliases.update(aliases)
+                except KeyError:
+                    imports[dotted_path] = base_path, aliases
+
+        for dotted_path, (base_path, aliases) in imports.items():
+            self.do_import(dotted_path, base_path, aliases, mangle)
+
+        for stmt in tree.children:
+            if stmt.data in ('term', 'rule'):
+                self._define(*self._unpack_definition(stmt, mangle))
+            elif stmt.data == 'override':
+                r ,= stmt.children
+                self._define(*self._unpack_definition(r, mangle), override=True)
+            elif stmt.data == 'extend':
+                r ,= stmt.children
+                self._extend(*self._unpack_definition(r, mangle))
+            elif stmt.data == 'ignore':
+                # if mangle is not None, we shouldn't apply ignore, since we aren't in a toplevel grammar
+                if mangle is None:
+                    self._ignore(*stmt.children)
+            elif stmt.data == 'declare':
+                for symbol in stmt.children:
+                    assert isinstance(symbol, Symbol), symbol
+                    is_term = isinstance(symbol, Terminal)
+                    if mangle is None:
+                        name = symbol.name
+                    else:
+                        name = mangle(symbol.name)
+                    self._define(name, is_term, None)
+            elif stmt.data == 'import':
+                pass
+            else:
+                assert False, stmt
+
+
+        term_defs = { name: d.tree
+            for name, d in self._definitions.items()
+            if d.is_term
+        }
+        resolve_term_references(term_defs)
+
+
+    def _remove_unused(self, used):
+        def rule_dependencies(symbol):
+            try:
+                d = self._definitions[symbol]
+            except KeyError:
+                return []
+            if d.is_term:
+                return []
+            return _find_used_symbols(d.tree) - set(d.params)
+
+        _used = set(bfs(used, rule_dependencies))
+        self._definitions = {k: v for k, v in self._definitions.items() if k in _used}
+
+
+    def do_import(self, dotted_path: Tuple[str, ...], base_path: Optional[str], aliases: Dict[str, str], base_mangle: Optional[Callable[[str], str]]=None) -> None:
+        assert dotted_path
+        mangle = _get_mangle('__'.join(dotted_path), aliases, base_mangle)
+        grammar_path = os.path.join(*dotted_path) + EXT
+        to_try = self.import_paths + ([base_path] if base_path is not None else []) + [stdlib_loader]
+        for source in to_try:
+            try:
+                if callable(source):
+                    joined_path, text = source(base_path, grammar_path)
+                else:
+                    joined_path = os.path.join(source, grammar_path)
+                    with open(joined_path, encoding='utf8') as f:
+                        text = f.read()
+            except IOError:
+                continue
+            else:
+                h = sha256_digest(text)
+                if self.used_files.get(joined_path, h) != h:
+                    raise RuntimeError("Grammar file was changed during importing")
+                self.used_files[joined_path] = h
+
+                gb = GrammarBuilder(self.global_keep_all_tokens, self.import_paths, self.used_files)
+                gb.load_grammar(text, joined_path, mangle)
+                gb._remove_unused(map(mangle, aliases))
+                for name in gb._definitions:
+                    if name in self._definitions:
+                        raise GrammarError("Cannot import '%s' from '%s': Symbol already defined." % (name, grammar_path))
+
+                self._definitions.update(**gb._definitions)
+                break
+        else:
+            # Search failed. Make Python throw a nice error.
+            open(grammar_path, encoding='utf8')
+            assert False, "Couldn't import grammar %s, but a corresponding file was found at a place where lark doesn't search for it" % (dotted_path,)
+
+
+    def validate(self) -> None:
+        for name, d in self._definitions.items():
+            params = d.params
+            exp = d.tree
+
+            for i, p in enumerate(params):
+                if p in self._definitions:
+                    raise GrammarError("Template Parameter conflicts with rule %s (in template %s)" % (p, name))
+                if p in params[:i]:
+                    raise GrammarError("Duplicate Template Parameter %s (in template %s)" % (p, name))
+
+            if exp is None: # Remaining checks don't apply to abstract rules/terminals (created with %declare)
+                continue
+
+            for temp in exp.find_data('template_usage'):
+                sym = temp.children[0].name
+                args = temp.children[1:]
+                if sym not in params:
+                    if sym not in self._definitions:
+                        self._grammar_error(d.is_term, "Template '%s' used but not defined (in {type} {name})" % sym, name)
+                    if len(args) != len(self._definitions[sym].params):
+                        expected, actual = len(self._definitions[sym].params), len(args)
+                        self._grammar_error(d.is_term, "Wrong number of template arguments used for {name} "
+                                            "(expected %s, got %s) (in {type2} {name2})" % (expected, actual), sym, name)
+
+            for sym in _find_used_symbols(exp):
+                if sym not in self._definitions and sym not in params:
+                    self._grammar_error(d.is_term, "{Type} '{name}' used but not defined (in {type2} {name2})", sym, name)
+
+        if not set(self._definitions).issuperset(self._ignore_names):
+            raise GrammarError("Terminals %s were marked to ignore but were not defined!" % (set(self._ignore_names) - set(self._definitions)))
+
+    def build(self) -> Grammar:
+        self.validate()
+        rule_defs = []
+        term_defs = []
+        for name, d in self._definitions.items():
+            (params, exp, options) = d.params, d.tree, d.options
+            if d.is_term:
+                assert len(params) == 0
+                term_defs.append((name, (exp, options)))
+            else:
+                rule_defs.append((name, params, exp, options))
+        # resolve_term_references(term_defs)
+        return Grammar(rule_defs, term_defs, self._ignore_names)
+
+
+def verify_used_files(file_hashes):
+    for path, old in file_hashes.items():
+        text = None
+        if isinstance(path, str) and os.path.exists(path):
+            with open(path, encoding='utf8') as f:
+                text = f.read()
+        elif isinstance(path, PackageResource):
+            with suppress(IOError):
+                text = pkgutil.get_data(*path).decode('utf-8')
+        if text is None: # We don't know how to load the path. ignore it.
+            continue
+
+        current = sha256_digest(text)
+        if old != current:
+            logger.info("File %r changed, rebuilding Parser" % path)
+            return False
+    return True
+
+def list_grammar_imports(grammar, import_paths=[]):
+    "Returns a list of paths to the lark grammars imported by the given grammar (recursively)"
+    builder = GrammarBuilder(False, import_paths)
+    builder.load_grammar(grammar, '')
+    return list(builder.used_files.keys())
+
+def load_grammar(grammar, source, import_paths, global_keep_all_tokens):
+    builder = GrammarBuilder(global_keep_all_tokens, import_paths)
+    builder.load_grammar(grammar, source)
+    return builder.build(), builder.used_files
+
+
+def sha256_digest(s: str) -> str:
+    """Get the sha256 digest of a string
+
+    Supports the `usedforsecurity` argument for Python 3.9+ to allow running on
+    a FIPS-enabled system.
+    """
+    if sys.version_info >= (3, 9):
+        return hashlib.sha256(s.encode('utf8'), usedforsecurity=False).hexdigest()
+    else:
+        return hashlib.sha256(s.encode('utf8')).hexdigest()
diff --git a/server/libs/lark/parse_tree_builder.py b/server/libs/lark/parse_tree_builder.py
new file mode 100644
index 0000000..e3a4171
--- /dev/null
+++ b/server/libs/lark/parse_tree_builder.py
@@ -0,0 +1,391 @@
+"""Provides functions for the automatic building and shaping of the parse-tree."""
+
+from typing import List
+
+from .exceptions import GrammarError, ConfigurationError
+from .lexer import Token
+from .tree import Tree
+from .visitors import Transformer_InPlace
+from .visitors import _vargs_meta, _vargs_meta_inline
+
+###{standalone
+from functools import partial, wraps
+from itertools import product
+
+
+class ExpandSingleChild:
+    def __init__(self, node_builder):
+        self.node_builder = node_builder
+
+    def __call__(self, children):
+        if len(children) == 1:
+            return children[0]
+        else:
+            return self.node_builder(children)
+
+
+
+class PropagatePositions:
+    def __init__(self, node_builder, node_filter=None):
+        self.node_builder = node_builder
+        self.node_filter = node_filter
+
+    def __call__(self, children):
+        res = self.node_builder(children)
+
+        if isinstance(res, Tree):
+            # Calculate positions while the tree is streaming, according to the rule:
+            # - nodes start at the start of their first child's container,
+            #   and end at the end of their last child's container.
+            # Containers are nodes that take up space in text, but have been inlined in the tree.
+
+            res_meta = res.meta
+
+            first_meta = self._pp_get_meta(children)
+            if first_meta is not None:
+                if not hasattr(res_meta, 'line'):
+                    # meta was already set, probably because the rule has been inlined (e.g. `?rule`)
+                    res_meta.line = getattr(first_meta, 'container_line', first_meta.line)
+                    res_meta.column = getattr(first_meta, 'container_column', first_meta.column)
+                    res_meta.start_pos = getattr(first_meta, 'container_start_pos', first_meta.start_pos)
+                    res_meta.empty = False
+
+                res_meta.container_line = getattr(first_meta, 'container_line', first_meta.line)
+                res_meta.container_column = getattr(first_meta, 'container_column', first_meta.column)
+                res_meta.container_start_pos = getattr(first_meta, 'container_start_pos', first_meta.start_pos)
+
+            last_meta = self._pp_get_meta(reversed(children))
+            if last_meta is not None:
+                if not hasattr(res_meta, 'end_line'):
+                    res_meta.end_line = getattr(last_meta, 'container_end_line', last_meta.end_line)
+                    res_meta.end_column = getattr(last_meta, 'container_end_column', last_meta.end_column)
+                    res_meta.end_pos = getattr(last_meta, 'container_end_pos', last_meta.end_pos)
+                    res_meta.empty = False
+
+                res_meta.container_end_line = getattr(last_meta, 'container_end_line', last_meta.end_line)
+                res_meta.container_end_column = getattr(last_meta, 'container_end_column', last_meta.end_column)
+                res_meta.container_end_pos = getattr(last_meta, 'container_end_pos', last_meta.end_pos)
+
+        return res
+
+    def _pp_get_meta(self, children):
+        for c in children:
+            if self.node_filter is not None and not self.node_filter(c):
+                continue
+            if isinstance(c, Tree):
+                if not c.meta.empty:
+                    return c.meta
+            elif isinstance(c, Token):
+                return c
+            elif hasattr(c, '__lark_meta__'):
+                return c.__lark_meta__()
+
+def make_propagate_positions(option):
+    if callable(option):
+        return partial(PropagatePositions, node_filter=option)
+    elif option is True:
+        return PropagatePositions
+    elif option is False:
+        return None
+
+    raise ConfigurationError('Invalid option for propagate_positions: %r' % option)
+
+
+class ChildFilter:
+    def __init__(self, to_include, append_none, node_builder):
+        self.node_builder = node_builder
+        self.to_include = to_include
+        self.append_none = append_none
+
+    def __call__(self, children):
+        filtered = []
+
+        for i, to_expand, add_none in self.to_include:
+            if add_none:
+                filtered += [None] * add_none
+            if to_expand:
+                filtered += children[i].children
+            else:
+                filtered.append(children[i])
+
+        if self.append_none:
+            filtered += [None] * self.append_none
+
+        return self.node_builder(filtered)
+
+
+class ChildFilterLALR(ChildFilter):
+    """Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)"""
+
+    def __call__(self, children):
+        filtered = []
+        for i, to_expand, add_none in self.to_include:
+            if add_none:
+                filtered += [None] * add_none
+            if to_expand:
+                if filtered:
+                    filtered += children[i].children
+                else:   # Optimize for left-recursion
+                    filtered = children[i].children
+            else:
+                filtered.append(children[i])
+
+        if self.append_none:
+            filtered += [None] * self.append_none
+
+        return self.node_builder(filtered)
+
+
+class ChildFilterLALR_NoPlaceholders(ChildFilter):
+    "Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)"
+    def __init__(self, to_include, node_builder):
+        self.node_builder = node_builder
+        self.to_include = to_include
+
+    def __call__(self, children):
+        filtered = []
+        for i, to_expand in self.to_include:
+            if to_expand:
+                if filtered:
+                    filtered += children[i].children
+                else:   # Optimize for left-recursion
+                    filtered = children[i].children
+            else:
+                filtered.append(children[i])
+        return self.node_builder(filtered)
+
+
+def _should_expand(sym):
+    return not sym.is_term and sym.name.startswith('_')
+
+
+def maybe_create_child_filter(expansion, keep_all_tokens, ambiguous, _empty_indices: List[bool]):
+    # Prepare empty_indices as: How many Nones to insert at each index?
+    if _empty_indices:
+        assert _empty_indices.count(False) == len(expansion)
+        s = ''.join(str(int(b)) for b in _empty_indices)
+        empty_indices = [len(ones) for ones in s.split('0')]
+        assert len(empty_indices) == len(expansion)+1, (empty_indices, len(expansion))
+    else:
+        empty_indices = [0] * (len(expansion)+1)
+
+    to_include = []
+    nones_to_add = 0
+    for i, sym in enumerate(expansion):
+        nones_to_add += empty_indices[i]
+        if keep_all_tokens or not (sym.is_term and sym.filter_out):
+            to_include.append((i, _should_expand(sym), nones_to_add))
+            nones_to_add = 0
+
+    nones_to_add += empty_indices[len(expansion)]
+
+    if _empty_indices or len(to_include) < len(expansion) or any(to_expand for i, to_expand,_ in to_include):
+        if _empty_indices or ambiguous:
+            return partial(ChildFilter if ambiguous else ChildFilterLALR, to_include, nones_to_add)
+        else:
+            # LALR without placeholders
+            return partial(ChildFilterLALR_NoPlaceholders, [(i, x) for i,x,_ in to_include])
+
+
+class AmbiguousExpander:
+    """Deal with the case where we're expanding children ('_rule') into a parent but the children
+       are ambiguous. i.e. (parent->_ambig->_expand_this_rule). In this case, make the parent itself
+       ambiguous with as many copies as there are ambiguous children, and then copy the ambiguous children
+       into the right parents in the right places, essentially shifting the ambiguity up the tree."""
+    def __init__(self, to_expand, tree_class, node_builder):
+        self.node_builder = node_builder
+        self.tree_class = tree_class
+        self.to_expand = to_expand
+
+    def __call__(self, children):
+        def _is_ambig_tree(t):
+            return hasattr(t, 'data') and t.data == '_ambig'
+
+        # -- When we're repeatedly expanding ambiguities we can end up with nested ambiguities.
+        #    All children of an _ambig node should be a derivation of that ambig node, hence
+        #    it is safe to assume that if we see an _ambig node nested within an ambig node
+        #    it is safe to simply expand it into the parent _ambig node as an alternative derivation.
+        ambiguous = []
+        for i, child in enumerate(children):
+            if _is_ambig_tree(child):
+                if i in self.to_expand:
+                    ambiguous.append(i)
+
+                child.expand_kids_by_data('_ambig')
+
+        if not ambiguous:
+            return self.node_builder(children)
+
+        expand = [child.children if i in ambiguous else (child,) for i, child in enumerate(children)]
+        return self.tree_class('_ambig', [self.node_builder(list(f)) for f in product(*expand)])
+
+
+def maybe_create_ambiguous_expander(tree_class, expansion, keep_all_tokens):
+    to_expand = [i for i, sym in enumerate(expansion)
+                 if keep_all_tokens or ((not (sym.is_term and sym.filter_out)) and _should_expand(sym))]
+    if to_expand:
+        return partial(AmbiguousExpander, to_expand, tree_class)
+
+
+class AmbiguousIntermediateExpander:
+    """
+    Propagate ambiguous intermediate nodes and their derivations up to the
+    current rule.
+
+    In general, converts
+
+    rule
+      _iambig
+        _inter
+          someChildren1
+          ...
+        _inter
+          someChildren2
+          ...
+      someChildren3
+      ...
+
+    to
+
+    _ambig
+      rule
+        someChildren1
+        ...
+        someChildren3
+        ...
+      rule
+        someChildren2
+        ...
+        someChildren3
+        ...
+      rule
+        childrenFromNestedIambigs
+        ...
+        someChildren3
+        ...
+      ...
+
+    propagating up any nested '_iambig' nodes along the way.
+    """
+
+    def __init__(self, tree_class, node_builder):
+        self.node_builder = node_builder
+        self.tree_class = tree_class
+
+    def __call__(self, children):
+        def _is_iambig_tree(child):
+            return hasattr(child, 'data') and child.data == '_iambig'
+
+        def _collapse_iambig(children):
+            """
+            Recursively flatten the derivations of the parent of an '_iambig'
+            node. Returns a list of '_inter' nodes guaranteed not
+            to contain any nested '_iambig' nodes, or None if children does
+            not contain an '_iambig' node.
+            """
+
+            # Due to the structure of the SPPF,
+            # an '_iambig' node can only appear as the first child
+            if children and _is_iambig_tree(children[0]):
+                iambig_node = children[0]
+                result = []
+                for grandchild in iambig_node.children:
+                    collapsed = _collapse_iambig(grandchild.children)
+                    if collapsed:
+                        for child in collapsed:
+                            child.children += children[1:]
+                        result += collapsed
+                    else:
+                        new_tree = self.tree_class('_inter', grandchild.children + children[1:])
+                        result.append(new_tree)
+                return result
+
+        collapsed = _collapse_iambig(children)
+        if collapsed:
+            processed_nodes = [self.node_builder(c.children) for c in collapsed]
+            return self.tree_class('_ambig', processed_nodes)
+
+        return self.node_builder(children)
+
+
+
+def inplace_transformer(func):
+    @wraps(func)
+    def f(children):
+        # function name in a Transformer is a rule name.
+        tree = Tree(func.__name__, children)
+        return func(tree)
+    return f
+
+
+def apply_visit_wrapper(func, name, wrapper):
+    if wrapper is _vargs_meta or wrapper is _vargs_meta_inline:
+        raise NotImplementedError("Meta args not supported for internal transformer")
+
+    @wraps(func)
+    def f(children):
+        return wrapper(func, name, children, None)
+    return f
+
+
+class ParseTreeBuilder:
+    def __init__(self, rules, tree_class, propagate_positions=False, ambiguous=False, maybe_placeholders=False):
+        self.tree_class = tree_class
+        self.propagate_positions = propagate_positions
+        self.ambiguous = ambiguous
+        self.maybe_placeholders = maybe_placeholders
+
+        self.rule_builders = list(self._init_builders(rules))
+
+    def _init_builders(self, rules):
+        propagate_positions = make_propagate_positions(self.propagate_positions)
+
+        for rule in rules:
+            options = rule.options
+            keep_all_tokens = options.keep_all_tokens
+            expand_single_child = options.expand1
+
+            wrapper_chain = list(filter(None, [
+                (expand_single_child and not rule.alias) and ExpandSingleChild,
+                maybe_create_child_filter(rule.expansion, keep_all_tokens, self.ambiguous, options.empty_indices if self.maybe_placeholders else None),
+                propagate_positions,
+                self.ambiguous and maybe_create_ambiguous_expander(self.tree_class, rule.expansion, keep_all_tokens),
+                self.ambiguous and partial(AmbiguousIntermediateExpander, self.tree_class)
+            ]))
+
+            yield rule, wrapper_chain
+
+    def create_callback(self, transformer=None):
+        callbacks = {}
+
+        default_handler = getattr(transformer, '__default__', None)
+        if default_handler:
+            def default_callback(data, children):
+                return default_handler(data, children, None)
+        else:
+            default_callback = self.tree_class
+
+        for rule, wrapper_chain in self.rule_builders:
+
+            user_callback_name = rule.alias or rule.options.template_source or rule.origin.name
+            try:
+                f = getattr(transformer, user_callback_name)
+                wrapper = getattr(f, 'visit_wrapper', None)
+                if wrapper is not None:
+                    f = apply_visit_wrapper(f, user_callback_name, wrapper)
+                elif isinstance(transformer, Transformer_InPlace):
+                    f = inplace_transformer(f)
+            except AttributeError:
+                f = partial(default_callback, user_callback_name)
+
+            for w in wrapper_chain:
+                f = w(f)
+
+            if rule in callbacks:
+                raise GrammarError("Rule '%s' already exists" % (rule,))
+
+            callbacks[rule] = f
+
+        return callbacks
+
+###}
diff --git a/server/libs/lark/parser_frontends.py b/server/libs/lark/parser_frontends.py
new file mode 100644
index 0000000..186058a
--- /dev/null
+++ b/server/libs/lark/parser_frontends.py
@@ -0,0 +1,257 @@
+from typing import Any, Callable, Dict, Optional, Collection, Union, TYPE_CHECKING
+
+from .exceptions import ConfigurationError, GrammarError, assert_config
+from .utils import get_regexp_width, Serialize
+from .lexer import LexerThread, BasicLexer, ContextualLexer, Lexer
+from .parsers import earley, xearley, cyk
+from .parsers.lalr_parser import LALR_Parser
+from .tree import Tree
+from .common import LexerConf, ParserConf, _ParserArgType, _LexerArgType
+
+if TYPE_CHECKING:
+    from .parsers.lalr_analysis import ParseTableBase
+
+
+###{standalone
+
+def _wrap_lexer(lexer_class):
+    future_interface = getattr(lexer_class, '__future_interface__', False)
+    if future_interface:
+        return lexer_class
+    else:
+        class CustomLexerWrapper(Lexer):
+            def __init__(self, lexer_conf):
+                self.lexer = lexer_class(lexer_conf)
+            def lex(self, lexer_state, parser_state):
+                return self.lexer.lex(lexer_state.text)
+        return CustomLexerWrapper
+
+
+def _deserialize_parsing_frontend(data, memo, lexer_conf, callbacks, options):
+    parser_conf = ParserConf.deserialize(data['parser_conf'], memo)
+    cls = (options and options._plugins.get('LALR_Parser')) or LALR_Parser
+    parser = cls.deserialize(data['parser'], memo, callbacks, options.debug)
+    parser_conf.callbacks = callbacks
+    return ParsingFrontend(lexer_conf, parser_conf, options, parser=parser)
+
+
+_parser_creators: 'Dict[str, Callable[[LexerConf, Any, Any], Any]]' = {}
+
+
+class ParsingFrontend(Serialize):
+    __serialize_fields__ = 'lexer_conf', 'parser_conf', 'parser'
+
+    lexer_conf: LexerConf
+    parser_conf: ParserConf
+    options: Any
+
+    def __init__(self, lexer_conf: LexerConf, parser_conf: ParserConf, options, parser=None):
+        self.parser_conf = parser_conf
+        self.lexer_conf = lexer_conf
+        self.options = options
+
+        # Set-up parser
+        if parser:  # From cache
+            self.parser = parser
+        else:
+            create_parser = _parser_creators.get(parser_conf.parser_type)
+            assert create_parser is not None, "{} is not supported in standalone mode".format(
+                    parser_conf.parser_type
+                )
+            self.parser = create_parser(lexer_conf, parser_conf, options)
+
+        # Set-up lexer
+        lexer_type = lexer_conf.lexer_type
+        self.skip_lexer = False
+        if lexer_type in ('dynamic', 'dynamic_complete'):
+            assert lexer_conf.postlex is None
+            self.skip_lexer = True
+            return
+
+        if isinstance(lexer_type, type):
+            assert issubclass(lexer_type, Lexer)
+            self.lexer = _wrap_lexer(lexer_type)(lexer_conf)
+        elif isinstance(lexer_type, str):
+            create_lexer = {
+                'basic': create_basic_lexer,
+                'contextual': create_contextual_lexer,
+            }[lexer_type]
+            self.lexer = create_lexer(lexer_conf, self.parser, lexer_conf.postlex, options)
+        else:
+            raise TypeError("Bad value for lexer_type: {lexer_type}")
+
+        if lexer_conf.postlex:
+            self.lexer = PostLexConnector(self.lexer, lexer_conf.postlex)
+
+    def _verify_start(self, start=None):
+        if start is None:
+            start_decls = self.parser_conf.start
+            if len(start_decls) > 1:
+                raise ConfigurationError("Lark initialized with more than 1 possible start rule. Must specify which start rule to parse", start_decls)
+            start ,= start_decls
+        elif start not in self.parser_conf.start:
+            raise ConfigurationError("Unknown start rule %s. Must be one of %r" % (start, self.parser_conf.start))
+        return start
+
+    def _make_lexer_thread(self, text: str) -> Union[str, LexerThread]:
+        cls = (self.options and self.options._plugins.get('LexerThread')) or LexerThread
+        return text if self.skip_lexer else cls.from_text(self.lexer, text)
+
+    def parse(self, text: str, start=None, on_error=None):
+        chosen_start = self._verify_start(start)
+        kw = {} if on_error is None else {'on_error': on_error}
+        stream = self._make_lexer_thread(text)
+        return self.parser.parse(stream, chosen_start, **kw)
+
+    def parse_interactive(self, text: Optional[str]=None, start=None):
+        # TODO BREAK - Change text from Optional[str] to text: str = ''.
+        #   Would break behavior of exhaust_lexer(), which currently raises TypeError, and after the change would just return []
+        chosen_start = self._verify_start(start)
+        if self.parser_conf.parser_type != 'lalr':
+            raise ConfigurationError("parse_interactive() currently only works with parser='lalr' ")
+        stream = self._make_lexer_thread(text)  # type: ignore[arg-type]
+        return self.parser.parse_interactive(stream, chosen_start)
+
+
+def _validate_frontend_args(parser, lexer) -> None:
+    assert_config(parser, ('lalr', 'earley', 'cyk'))
+    if not isinstance(lexer, type):     # not custom lexer?
+        expected = {
+            'lalr': ('basic', 'contextual'),
+            'earley': ('basic', 'dynamic', 'dynamic_complete'),
+            'cyk': ('basic', ),
+         }[parser]
+        assert_config(lexer, expected, 'Parser %r does not support lexer %%r, expected one of %%s' % parser)
+
+
+def _get_lexer_callbacks(transformer, terminals):
+    result = {}
+    for terminal in terminals:
+        callback = getattr(transformer, terminal.name, None)
+        if callback is not None:
+            result[terminal.name] = callback
+    return result
+
+class PostLexConnector:
+    def __init__(self, lexer, postlexer):
+        self.lexer = lexer
+        self.postlexer = postlexer
+
+    def lex(self, lexer_state, parser_state):
+        i = self.lexer.lex(lexer_state, parser_state)
+        return self.postlexer.process(i)
+
+
+
+def create_basic_lexer(lexer_conf, parser, postlex, options) -> BasicLexer:
+    cls = (options and options._plugins.get('BasicLexer')) or BasicLexer
+    return cls(lexer_conf)
+
+def create_contextual_lexer(lexer_conf: LexerConf, parser, postlex, options) -> ContextualLexer:
+    cls = (options and options._plugins.get('ContextualLexer')) or ContextualLexer
+    parse_table: ParseTableBase[int] = parser._parse_table
+    states: Dict[int, Collection[str]] = {idx:list(t.keys()) for idx, t in parse_table.states.items()}
+    always_accept: Collection[str] = postlex.always_accept if postlex else ()
+    return cls(lexer_conf, states, always_accept=always_accept)
+
+def create_lalr_parser(lexer_conf: LexerConf, parser_conf: ParserConf, options=None) -> LALR_Parser:
+    debug = options.debug if options else False
+    strict = options.strict if options else False
+    cls = (options and options._plugins.get('LALR_Parser')) or LALR_Parser
+    return cls(parser_conf, debug=debug, strict=strict)
+
+_parser_creators['lalr'] = create_lalr_parser
+
+###}
+
+class EarleyRegexpMatcher:
+    def __init__(self, lexer_conf):
+        self.regexps = {}
+        for t in lexer_conf.terminals:
+            regexp = t.pattern.to_regexp()
+            try:
+                width = get_regexp_width(regexp)[0]
+            except ValueError:
+                raise GrammarError("Bad regexp in token %s: %s" % (t.name, regexp))
+            else:
+                if width == 0:
+                    raise GrammarError("Dynamic Earley doesn't allow zero-width regexps", t)
+            if lexer_conf.use_bytes:
+                regexp = regexp.encode('utf-8')
+
+            self.regexps[t.name] = lexer_conf.re_module.compile(regexp, lexer_conf.g_regex_flags)
+
+    def match(self, term, text, index=0):
+        return self.regexps[term.name].match(text, index)
+
+
+def create_earley_parser__dynamic(lexer_conf: LexerConf, parser_conf: ParserConf, **kw):
+    if lexer_conf.callbacks:
+        raise GrammarError("Earley's dynamic lexer doesn't support lexer_callbacks.")
+
+    earley_matcher = EarleyRegexpMatcher(lexer_conf)
+    return xearley.Parser(lexer_conf, parser_conf, earley_matcher.match, **kw)
+
+def _match_earley_basic(term, token):
+    return term.name == token.type
+
+def create_earley_parser__basic(lexer_conf: LexerConf, parser_conf: ParserConf, **kw):
+    return earley.Parser(lexer_conf, parser_conf, _match_earley_basic, **kw)
+
+def create_earley_parser(lexer_conf: LexerConf, parser_conf: ParserConf, options) -> earley.Parser:
+    resolve_ambiguity = options.ambiguity == 'resolve'
+    debug = options.debug if options else False
+    tree_class = options.tree_class or Tree if options.ambiguity != 'forest' else None
+
+    extra = {}
+    if lexer_conf.lexer_type == 'dynamic':
+        f = create_earley_parser__dynamic
+    elif lexer_conf.lexer_type == 'dynamic_complete':
+        extra['complete_lex'] = True
+        f = create_earley_parser__dynamic
+    else:
+        f = create_earley_parser__basic
+
+    return f(lexer_conf, parser_conf, resolve_ambiguity=resolve_ambiguity,
+             debug=debug, tree_class=tree_class, ordered_sets=options.ordered_sets, **extra)
+
+
+
+class CYK_FrontEnd:
+    def __init__(self, lexer_conf, parser_conf, options=None):
+        self.parser = cyk.Parser(parser_conf.rules)
+
+        self.callbacks = parser_conf.callbacks
+
+    def parse(self, lexer_thread, start):
+        tokens = list(lexer_thread.lex(None))
+        tree = self.parser.parse(tokens, start)
+        return self._transform(tree)
+
+    def _transform(self, tree):
+        subtrees = list(tree.iter_subtrees())
+        for subtree in subtrees:
+            subtree.children = [self._apply_callback(c) if isinstance(c, Tree) else c for c in subtree.children]
+
+        return self._apply_callback(tree)
+
+    def _apply_callback(self, tree):
+        return self.callbacks[tree.rule](tree.children)
+
+
+_parser_creators['earley'] = create_earley_parser
+_parser_creators['cyk'] = CYK_FrontEnd
+
+
+def _construct_parsing_frontend(
+        parser_type: _ParserArgType,
+        lexer_type: _LexerArgType,
+        lexer_conf,
+        parser_conf,
+        options
+):
+    assert isinstance(lexer_conf, LexerConf)
+    assert isinstance(parser_conf, ParserConf)
+    parser_conf.parser_type = parser_type
+    lexer_conf.lexer_type = lexer_type
+    return ParsingFrontend(lexer_conf, parser_conf, options)
diff --git a/server/libs/lark/parsers/__init__.py b/server/libs/lark/parsers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/server/libs/lark/parsers/cyk.py b/server/libs/lark/parsers/cyk.py
new file mode 100644
index 0000000..b5334f9
--- /dev/null
+++ b/server/libs/lark/parsers/cyk.py
@@ -0,0 +1,340 @@
+"""This module implements a CYK parser."""
+
+# Author: https://github.com/ehudt (2018)
+#
+# Adapted by Erez
+
+
+from collections import defaultdict
+import itertools
+
+from ..exceptions import ParseError
+from ..lexer import Token
+from ..tree import Tree
+from ..grammar import Terminal as T, NonTerminal as NT, Symbol
+
+def match(t, s):
+    assert isinstance(t, T)
+    return t.name == s.type
+
+
+class Rule:
+    """Context-free grammar rule."""
+
+    def __init__(self, lhs, rhs, weight, alias):
+        super(Rule, self).__init__()
+        assert isinstance(lhs, NT), lhs
+        assert all(isinstance(x, NT) or isinstance(x, T) for x in rhs), rhs
+        self.lhs = lhs
+        self.rhs = rhs
+        self.weight = weight
+        self.alias = alias
+
+    def __str__(self):
+        return '%s -> %s' % (str(self.lhs), ' '.join(str(x) for x in self.rhs))
+
+    def __repr__(self):
+        return str(self)
+
+    def __hash__(self):
+        return hash((self.lhs, tuple(self.rhs)))
+
+    def __eq__(self, other):
+        return self.lhs == other.lhs and self.rhs == other.rhs
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
+class Grammar:
+    """Context-free grammar."""
+
+    def __init__(self, rules):
+        self.rules = frozenset(rules)
+
+    def __eq__(self, other):
+        return self.rules == other.rules
+
+    def __str__(self):
+        return '\n' + '\n'.join(sorted(repr(x) for x in self.rules)) + '\n'
+
+    def __repr__(self):
+        return str(self)
+
+
+# Parse tree data structures
+class RuleNode:
+    """A node in the parse tree, which also contains the full rhs rule."""
+
+    def __init__(self, rule, children, weight=0):
+        self.rule = rule
+        self.children = children
+        self.weight = weight
+
+    def __repr__(self):
+        return 'RuleNode(%s, [%s])' % (repr(self.rule.lhs), ', '.join(str(x) for x in self.children))
+
+
+
+class Parser:
+    """Parser wrapper."""
+
+    def __init__(self, rules):
+        super(Parser, self).__init__()
+        self.orig_rules = {rule: rule for rule in rules}
+        rules = [self._to_rule(rule) for rule in rules]
+        self.grammar = to_cnf(Grammar(rules))
+
+    def _to_rule(self, lark_rule):
+        """Converts a lark rule, (lhs, rhs, callback, options), to a Rule."""
+        assert isinstance(lark_rule.origin, NT)
+        assert all(isinstance(x, Symbol) for x in lark_rule.expansion)
+        return Rule(
+            lark_rule.origin, lark_rule.expansion,
+            weight=lark_rule.options.priority if lark_rule.options.priority else 0,
+            alias=lark_rule)
+
+    def parse(self, tokenized, start):  # pylint: disable=invalid-name
+        """Parses input, which is a list of tokens."""
+        assert start
+        start = NT(start)
+
+        table, trees = _parse(tokenized, self.grammar)
+        # Check if the parse succeeded.
+        if all(r.lhs != start for r in table[(0, len(tokenized) - 1)]):
+            raise ParseError('Parsing failed.')
+        parse = trees[(0, len(tokenized) - 1)][start]
+        return self._to_tree(revert_cnf(parse))
+
+    def _to_tree(self, rule_node):
+        """Converts a RuleNode parse tree to a lark Tree."""
+        orig_rule = self.orig_rules[rule_node.rule.alias]
+        children = []
+        for child in rule_node.children:
+            if isinstance(child, RuleNode):
+                children.append(self._to_tree(child))
+            else:
+                assert isinstance(child.name, Token)
+                children.append(child.name)
+        t = Tree(orig_rule.origin, children)
+        t.rule=orig_rule
+        return t
+
+
+def print_parse(node, indent=0):
+    if isinstance(node, RuleNode):
+        print(' ' * (indent * 2) + str(node.rule.lhs))
+        for child in node.children:
+            print_parse(child, indent + 1)
+    else:
+        print(' ' * (indent * 2) + str(node.s))
+
+
+def _parse(s, g):
+    """Parses sentence 's' using CNF grammar 'g'."""
+    # The CYK table. Indexed with a 2-tuple: (start pos, end pos)
+    table = defaultdict(set)
+    # Top-level structure is similar to the CYK table. Each cell is a dict from
+    # rule name to the best (lightest) tree for that rule.
+    trees = defaultdict(dict)
+    # Populate base case with existing terminal production rules
+    for i, w in enumerate(s):
+        for terminal, rules in g.terminal_rules.items():
+            if match(terminal, w):
+                for rule in rules:
+                    table[(i, i)].add(rule)
+                    if (rule.lhs not in trees[(i, i)] or
+                        rule.weight < trees[(i, i)][rule.lhs].weight):
+                        trees[(i, i)][rule.lhs] = RuleNode(rule, [T(w)], weight=rule.weight)
+
+    # Iterate over lengths of sub-sentences
+    for l in range(2, len(s) + 1):
+        # Iterate over sub-sentences with the given length
+        for i in range(len(s) - l + 1):
+            # Choose partition of the sub-sentence in [1, l)
+            for p in range(i + 1, i + l):
+                span1 = (i, p - 1)
+                span2 = (p, i + l - 1)
+                for r1, r2 in itertools.product(table[span1], table[span2]):
+                    for rule in g.nonterminal_rules.get((r1.lhs, r2.lhs), []):
+                        table[(i, i + l - 1)].add(rule)
+                        r1_tree = trees[span1][r1.lhs]
+                        r2_tree = trees[span2][r2.lhs]
+                        rule_total_weight = rule.weight + r1_tree.weight + r2_tree.weight
+                        if (rule.lhs not in trees[(i, i + l - 1)]
+                            or rule_total_weight < trees[(i, i + l - 1)][rule.lhs].weight):
+                            trees[(i, i + l - 1)][rule.lhs] = RuleNode(rule, [r1_tree, r2_tree], weight=rule_total_weight)
+    return table, trees
+
+
+# This section implements context-free grammar converter to Chomsky normal form.
+# It also implements a conversion of parse trees from its CNF to the original
+# grammar.
+# Overview:
+# Applies the following operations in this order:
+# * TERM: Eliminates non-solitary terminals from all rules
+# * BIN: Eliminates rules with more than 2 symbols on their right-hand-side.
+# * UNIT: Eliminates non-terminal unit rules
+#
+# The following grammar characteristics aren't featured:
+# * Start symbol appears on RHS
+# * Empty rules (epsilon rules)
+
+
+class CnfWrapper:
+    """CNF wrapper for grammar.
+
+  Validates that the input grammar is CNF and provides helper data structures.
+  """
+
+    def __init__(self, grammar):
+        super(CnfWrapper, self).__init__()
+        self.grammar = grammar
+        self.rules = grammar.rules
+        self.terminal_rules = defaultdict(list)
+        self.nonterminal_rules = defaultdict(list)
+        for r in self.rules:
+            # Validate that the grammar is CNF and populate auxiliary data structures.
+            assert isinstance(r.lhs, NT), r
+            if len(r.rhs) not in [1, 2]:
+                raise ParseError("CYK doesn't support empty rules")
+            if len(r.rhs) == 1 and isinstance(r.rhs[0], T):
+                self.terminal_rules[r.rhs[0]].append(r)
+            elif len(r.rhs) == 2 and all(isinstance(x, NT) for x in r.rhs):
+                self.nonterminal_rules[tuple(r.rhs)].append(r)
+            else:
+                assert False, r
+
+    def __eq__(self, other):
+        return self.grammar == other.grammar
+
+    def __repr__(self):
+        return repr(self.grammar)
+
+
+class UnitSkipRule(Rule):
+    """A rule that records NTs that were skipped during transformation."""
+
+    def __init__(self, lhs, rhs, skipped_rules, weight, alias):
+        super(UnitSkipRule, self).__init__(lhs, rhs, weight, alias)
+        self.skipped_rules = skipped_rules
+
+    def __eq__(self, other):
+        return isinstance(other, type(self)) and self.skipped_rules == other.skipped_rules
+
+    __hash__ = Rule.__hash__
+
+
+def build_unit_skiprule(unit_rule, target_rule):
+    skipped_rules = []
+    if isinstance(unit_rule, UnitSkipRule):
+        skipped_rules += unit_rule.skipped_rules
+    skipped_rules.append(target_rule)
+    if isinstance(target_rule, UnitSkipRule):
+        skipped_rules += target_rule.skipped_rules
+    return UnitSkipRule(unit_rule.lhs, target_rule.rhs, skipped_rules,
+                      weight=unit_rule.weight + target_rule.weight, alias=unit_rule.alias)
+
+
+def get_any_nt_unit_rule(g):
+    """Returns a non-terminal unit rule from 'g', or None if there is none."""
+    for rule in g.rules:
+        if len(rule.rhs) == 1 and isinstance(rule.rhs[0], NT):
+            return rule
+    return None
+
+
+def _remove_unit_rule(g, rule):
+    """Removes 'rule' from 'g' without changing the language produced by 'g'."""
+    new_rules = [x for x in g.rules if x != rule]
+    refs = [x for x in g.rules if x.lhs == rule.rhs[0]]
+    new_rules += [build_unit_skiprule(rule, ref) for ref in refs]
+    return Grammar(new_rules)
+
+
+def _split(rule):
+    """Splits a rule whose len(rhs) > 2 into shorter rules."""
+    rule_str = str(rule.lhs) + '__' + '_'.join(str(x) for x in rule.rhs)
+    rule_name = '__SP_%s' % (rule_str) + '_%d'
+    yield Rule(rule.lhs, [rule.rhs[0], NT(rule_name % 1)], weight=rule.weight, alias=rule.alias)
+    for i in range(1, len(rule.rhs) - 2):
+        yield Rule(NT(rule_name % i), [rule.rhs[i], NT(rule_name % (i + 1))], weight=0, alias='Split')
+    yield Rule(NT(rule_name % (len(rule.rhs) - 2)), rule.rhs[-2:], weight=0, alias='Split')
+
+
+def _term(g):
+    """Applies the TERM rule on 'g' (see top comment)."""
+    all_t = {x for rule in g.rules for x in rule.rhs if isinstance(x, T)}
+    t_rules = {t: Rule(NT('__T_%s' % str(t)), [t], weight=0, alias='Term') for t in all_t}
+    new_rules = []
+    for rule in g.rules:
+        if len(rule.rhs) > 1 and any(isinstance(x, T) for x in rule.rhs):
+            new_rhs = [t_rules[x].lhs if isinstance(x, T) else x for x in rule.rhs]
+            new_rules.append(Rule(rule.lhs, new_rhs, weight=rule.weight, alias=rule.alias))
+            new_rules.extend(v for k, v in t_rules.items() if k in rule.rhs)
+        else:
+            new_rules.append(rule)
+    return Grammar(new_rules)
+
+
+def _bin(g):
+    """Applies the BIN rule to 'g' (see top comment)."""
+    new_rules = []
+    for rule in g.rules:
+        if len(rule.rhs) > 2:
+            new_rules += _split(rule)
+        else:
+            new_rules.append(rule)
+    return Grammar(new_rules)
+
+
+def _unit(g):
+    """Applies the UNIT rule to 'g' (see top comment)."""
+    nt_unit_rule = get_any_nt_unit_rule(g)
+    while nt_unit_rule:
+        g = _remove_unit_rule(g, nt_unit_rule)
+        nt_unit_rule = get_any_nt_unit_rule(g)
+    return g
+
+
+def to_cnf(g):
+    """Creates a CNF grammar from a general context-free grammar 'g'."""
+    g = _unit(_bin(_term(g)))
+    return CnfWrapper(g)
+
+
+def unroll_unit_skiprule(lhs, orig_rhs, skipped_rules, children, weight, alias):
+    if not skipped_rules:
+        return RuleNode(Rule(lhs, orig_rhs, weight=weight, alias=alias), children, weight=weight)
+    else:
+        weight = weight - skipped_rules[0].weight
+        return RuleNode(
+            Rule(lhs, [skipped_rules[0].lhs], weight=weight, alias=alias), [
+                unroll_unit_skiprule(skipped_rules[0].lhs, orig_rhs,
+                                skipped_rules[1:], children,
+                                skipped_rules[0].weight, skipped_rules[0].alias)
+            ], weight=weight)
+
+
+def revert_cnf(node):
+    """Reverts a parse tree (RuleNode) to its original non-CNF form (Node)."""
+    if isinstance(node, T):
+        return node
+    # Reverts TERM rule.
+    if node.rule.lhs.name.startswith('__T_'):
+        return node.children[0]
+    else:
+        children = []
+        for child in map(revert_cnf, node.children):
+            # Reverts BIN rule.
+            if isinstance(child, RuleNode) and child.rule.lhs.name.startswith('__SP_'):
+                children += child.children
+            else:
+                children.append(child)
+        # Reverts UNIT rule.
+        if isinstance(node.rule, UnitSkipRule):
+            return unroll_unit_skiprule(node.rule.lhs, node.rule.rhs,
+                                    node.rule.skipped_rules, children,
+                                    node.rule.weight, node.rule.alias)
+        else:
+            return RuleNode(node.rule, children)
diff --git a/server/libs/lark/parsers/earley.py b/server/libs/lark/parsers/earley.py
new file mode 100644
index 0000000..d173129
--- /dev/null
+++ b/server/libs/lark/parsers/earley.py
@@ -0,0 +1,317 @@
+"""This module implements an Earley parser.
+
+The core Earley algorithm used here is based on Elizabeth Scott's implementation, here:
+    https://www.sciencedirect.com/science/article/pii/S1571066108001497
+
+That is probably the best reference for understanding the algorithm here.
+
+The Earley parser outputs an SPPF-tree as per that document. The SPPF tree format
+is explained here: https://lark-parser.readthedocs.io/en/latest/_static/sppf/sppf.html
+"""
+
+from typing import TYPE_CHECKING, Callable, Optional, List, Any
+from collections import deque
+
+from ..lexer import Token
+from ..tree import Tree
+from ..exceptions import UnexpectedEOF, UnexpectedToken
+from ..utils import logger, OrderedSet, dedup_list
+from .grammar_analysis import GrammarAnalyzer
+from ..grammar import NonTerminal
+from .earley_common import Item
+from .earley_forest import ForestSumVisitor, SymbolNode, StableSymbolNode, TokenNode, ForestToParseTree
+
+if TYPE_CHECKING:
+    from ..common import LexerConf, ParserConf
+
+class Parser:
+    lexer_conf: 'LexerConf'
+    parser_conf: 'ParserConf'
+    debug: bool
+
+    def __init__(self, lexer_conf: 'LexerConf', parser_conf: 'ParserConf', term_matcher: Callable,
+                 resolve_ambiguity: bool=True, debug: bool=False,
+                 tree_class: Optional[Callable[[str, List], Any]]=Tree, ordered_sets: bool=True):
+        analysis = GrammarAnalyzer(parser_conf)
+        self.lexer_conf = lexer_conf
+        self.parser_conf = parser_conf
+        self.resolve_ambiguity = resolve_ambiguity
+        self.debug = debug
+        self.Tree = tree_class
+        self.Set = OrderedSet if ordered_sets else set
+        self.SymbolNode = StableSymbolNode if ordered_sets else SymbolNode
+
+        self.FIRST = analysis.FIRST
+        self.NULLABLE = analysis.NULLABLE
+        self.callbacks = parser_conf.callbacks
+        # TODO add typing info
+        self.predictions = {}   # type: ignore[var-annotated]
+
+        ## These could be moved to the grammar analyzer. Pre-computing these is *much* faster than
+        #  the slow 'isupper' in is_terminal.
+        self.TERMINALS = { sym for r in parser_conf.rules for sym in r.expansion if sym.is_term }
+        self.NON_TERMINALS = { sym for r in parser_conf.rules for sym in r.expansion if not sym.is_term }
+
+        self.forest_sum_visitor = None
+        for rule in parser_conf.rules:
+            if rule.origin not in self.predictions:
+                self.predictions[rule.origin] = [x.rule for x in analysis.expand_rule(rule.origin)]
+
+            ## Detect if any rules/terminals have priorities set. If the user specified priority = None, then
+            #  the priorities will be stripped from all rules/terminals before they reach us, allowing us to
+            #  skip the extra tree walk. We'll also skip this if the user just didn't specify priorities
+            #  on any rules/terminals.
+            if self.forest_sum_visitor is None and rule.options.priority is not None:
+                self.forest_sum_visitor = ForestSumVisitor
+
+        # Check terminals for priorities
+        # Ignore terminal priorities if the basic lexer is used
+        if self.lexer_conf.lexer_type != 'basic' and self.forest_sum_visitor is None:
+            for term in self.lexer_conf.terminals:
+                if term.priority:
+                    self.forest_sum_visitor = ForestSumVisitor
+                    break
+
+        self.term_matcher = term_matcher
+
+
+    def predict_and_complete(self, i, to_scan, columns, transitives):
+        """The core Earley Predictor and Completer.
+
+        At each stage of the input, we handling any completed items (things
+        that matched on the last cycle) and use those to predict what should
+        come next in the input stream. The completions and any predicted
+        non-terminals are recursively processed until we reach a set of,
+        which can be added to the scan list for the next scanner cycle."""
+        # Held Completions (H in E.Scotts paper).
+        node_cache = {}
+        held_completions = {}
+
+        column = columns[i]
+        # R (items) = Ei (column.items)
+        items = deque(column)
+        while items:
+            item = items.pop()    # remove an element, A say, from R
+
+            ### The Earley completer
+            if item.is_complete:   ### (item.s == string)
+                if item.node is None:
+                    label = (item.s, item.start, i)
+                    item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
+                    item.node.add_family(item.s, item.rule, item.start, None, None)
+
+                # create_leo_transitives(item.rule.origin, item.start)
+
+                ###R Joop Leo right recursion Completer
+                if item.rule.origin in transitives[item.start]:
+                    transitive = transitives[item.start][item.s]
+                    if transitive.previous in transitives[transitive.column]:
+                        root_transitive = transitives[transitive.column][transitive.previous]
+                    else:
+                        root_transitive = transitive
+
+                    new_item = Item(transitive.rule, transitive.ptr, transitive.start)
+                    label = (root_transitive.s, root_transitive.start, i)
+                    new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
+                    new_item.node.add_path(root_transitive, item.node)
+                    if new_item.expect in self.TERMINALS:
+                        # Add (B :: aC.B, h, y) to Q
+                        to_scan.add(new_item)
+                    elif new_item not in column:
+                        # Add (B :: aC.B, h, y) to Ei and R
+                        column.add(new_item)
+                        items.append(new_item)
+                ###R Regular Earley completer
+                else:
+                    # Empty has 0 length. If we complete an empty symbol in a particular
+                    # parse step, we need to be able to use that same empty symbol to complete
+                    # any predictions that result, that themselves require empty. Avoids
+                    # infinite recursion on empty symbols.
+                    # held_completions is 'H' in E.Scott's paper.
+                    is_empty_item = item.start == i
+                    if is_empty_item:
+                        held_completions[item.rule.origin] = item.node
+
+                    originators = [originator for originator in columns[item.start] if originator.expect is not None and originator.expect == item.s]
+                    for originator in originators:
+                        new_item = originator.advance()
+                        label = (new_item.s, originator.start, i)
+                        new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
+                        new_item.node.add_family(new_item.s, new_item.rule, i, originator.node, item.node)
+                        if new_item.expect in self.TERMINALS:
+                            # Add (B :: aC.B, h, y) to Q
+                            to_scan.add(new_item)
+                        elif new_item not in column:
+                            # Add (B :: aC.B, h, y) to Ei and R
+                            column.add(new_item)
+                            items.append(new_item)
+
+            ### The Earley predictor
+            elif item.expect in self.NON_TERMINALS: ### (item.s == lr0)
+                new_items = []
+                for rule in self.predictions[item.expect]:
+                    new_item = Item(rule, 0, i)
+                    new_items.append(new_item)
+
+                # Process any held completions (H).
+                if item.expect in held_completions:
+                    new_item = item.advance()
+                    label = (new_item.s, item.start, i)
+                    new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
+                    new_item.node.add_family(new_item.s, new_item.rule, new_item.start, item.node, held_completions[item.expect])
+                    new_items.append(new_item)
+
+                for new_item in new_items:
+                    if new_item.expect in self.TERMINALS:
+                        to_scan.add(new_item)
+                    elif new_item not in column:
+                        column.add(new_item)
+                        items.append(new_item)
+
+    def _parse(self, lexer, columns, to_scan, start_symbol=None):
+
+        def is_quasi_complete(item):
+            if item.is_complete:
+                return True
+
+            quasi = item.advance()
+            while not quasi.is_complete:
+                if quasi.expect not in self.NULLABLE:
+                    return False
+                if quasi.rule.origin == start_symbol and quasi.expect == start_symbol:
+                    return False
+                quasi = quasi.advance()
+            return True
+
+        # def create_leo_transitives(origin, start):
+        #   ...   # removed at commit 4c1cfb2faf24e8f8bff7112627a00b94d261b420
+
+        def scan(i, token, to_scan):
+            """The core Earley Scanner.
+
+            This is a custom implementation of the scanner that uses the
+            Lark lexer to match tokens. The scan list is built by the
+            Earley predictor, based on the previously completed tokens.
+            This ensures that at each phase of the parse we have a custom
+            lexer context, allowing for more complex ambiguities."""
+            next_to_scan = self.Set()
+            next_set = self.Set()
+            columns.append(next_set)
+            transitives.append({})
+            node_cache = {}
+
+            for item in self.Set(to_scan):
+                if match(item.expect, token):
+                    new_item = item.advance()
+                    label = (new_item.s, new_item.start, i)
+                    # 'terminals' may not contain token.type when using %declare
+                    # Additionally, token is not always a Token
+                    # For example, it can be a Tree when using TreeMatcher
+                    term = terminals.get(token.type) if isinstance(token, Token) else None
+                    # Set the priority of the token node to 0 so that the
+                    # terminal priorities do not affect the Tree chosen by
+                    # ForestSumVisitor after the basic lexer has already
+                    # "used up" the terminal priorities
+                    token_node = TokenNode(token, term, priority=0)
+                    new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
+                    new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token_node)
+
+                    if new_item.expect in self.TERMINALS:
+                        # add (B ::= Aai+1.B, h, y) to Q'
+                        next_to_scan.add(new_item)
+                    else:
+                        # add (B ::= Aa+1.B, h, y) to Ei+1
+                        next_set.add(new_item)
+
+            if not next_set and not next_to_scan:
+                expect = {i.expect.name for i in to_scan}
+                raise UnexpectedToken(token, expect, considered_rules=set(to_scan), state=frozenset(i.s for i in to_scan))
+
+            return next_to_scan
+
+
+        # Define parser functions
+        match = self.term_matcher
+
+        terminals = self.lexer_conf.terminals_by_name
+
+        # Cache for nodes & tokens created in a particular parse step.
+        transitives = [{}]
+
+        ## The main Earley loop.
+        # Run the Prediction/Completion cycle for any Items in the current Earley set.
+        # Completions will be added to the SPPF tree, and predictions will be recursively
+        # processed down to terminals/empty nodes to be added to the scanner for the next
+        # step.
+        expects = {i.expect for i in to_scan}
+        i = 0
+        for token in lexer.lex(expects):
+            self.predict_and_complete(i, to_scan, columns, transitives)
+
+            to_scan = scan(i, token, to_scan)
+            i += 1
+
+            expects.clear()
+            expects |= {i.expect for i in to_scan}
+
+        self.predict_and_complete(i, to_scan, columns, transitives)
+
+        ## Column is now the final column in the parse.
+        assert i == len(columns)-1
+        return to_scan
+
+    def parse(self, lexer, start):
+        assert start, start
+        start_symbol = NonTerminal(start)
+
+        columns = [self.Set()]
+        to_scan = self.Set()     # The scan buffer. 'Q' in E.Scott's paper.
+
+        ## Predict for the start_symbol.
+        # Add predicted items to the first Earley set (for the predictor) if they
+        # result in a non-terminal, or the scanner if they result in a terminal.
+        for rule in self.predictions[start_symbol]:
+            item = Item(rule, 0, 0)
+            if item.expect in self.TERMINALS:
+                to_scan.add(item)
+            else:
+                columns[0].add(item)
+
+        to_scan = self._parse(lexer, columns, to_scan, start_symbol)
+
+        # If the parse was successful, the start
+        # symbol should have been completed in the last step of the Earley cycle, and will be in
+        # this column. Find the item for the start_symbol, which is the root of the SPPF tree.
+        solutions = dedup_list(n.node for n in columns[-1] if n.is_complete and n.node is not None and n.s == start_symbol and n.start == 0)
+        if not solutions:
+            expected_terminals = [t.expect.name for t in to_scan]
+            raise UnexpectedEOF(expected_terminals, state=frozenset(i.s for i in to_scan))
+
+        if self.debug:
+            from .earley_forest import ForestToPyDotVisitor
+            try:
+                debug_walker = ForestToPyDotVisitor()
+            except ImportError:
+                logger.warning("Cannot find dependency 'pydot', will not generate sppf debug image")
+            else:
+                for i, s in enumerate(solutions):
+                    debug_walker.visit(s, f"sppf{i}.png")
+
+
+        if self.Tree is not None:
+            # Perform our SPPF -> AST conversion
+            # Disable the ForestToParseTree cache when ambiguity='resolve'
+            # to prevent a tree construction bug. See issue #1283
+            use_cache = not self.resolve_ambiguity
+            transformer = ForestToParseTree(self.Tree, self.callbacks, self.forest_sum_visitor and self.forest_sum_visitor(), self.resolve_ambiguity, use_cache)
+            solutions = [transformer.transform(s) for s in solutions]
+
+            if len(solutions) > 1 and not self.resolve_ambiguity:
+                t: Tree = self.Tree('_ambig', solutions)
+                t.expand_kids_by_data('_ambig')     # solutions may themselves be _ambig nodes
+                return t
+            return solutions[0]
+
+        # return the root of the SPPF
+        # TODO return a list of solutions, or join them together somehow
+        return solutions[0]
diff --git a/server/libs/lark/parsers/earley_common.py b/server/libs/lark/parsers/earley_common.py
new file mode 100644
index 0000000..0ea2d4f
--- /dev/null
+++ b/server/libs/lark/parsers/earley_common.py
@@ -0,0 +1,42 @@
+"""This module implements useful building blocks for the Earley parser
+"""
+
+
+class Item:
+    "An Earley Item, the atom of the algorithm."
+
+    __slots__ = ('s', 'rule', 'ptr', 'start', 'is_complete', 'expect', 'previous', 'node', '_hash')
+    def __init__(self, rule, ptr, start):
+        self.is_complete = len(rule.expansion) == ptr
+        self.rule = rule    # rule
+        self.ptr = ptr      # ptr
+        self.start = start  # j
+        self.node = None    # w
+        if self.is_complete:
+            self.s = rule.origin
+            self.expect = None
+            self.previous = rule.expansion[ptr - 1] if ptr > 0 and len(rule.expansion) else None
+        else:
+            self.s = (rule, ptr)
+            self.expect = rule.expansion[ptr]
+            self.previous = rule.expansion[ptr - 1] if ptr > 0 and len(rule.expansion) else None
+        self._hash = hash((self.s, self.start, self.rule))
+
+    def advance(self):
+        return Item(self.rule, self.ptr + 1, self.start)
+
+    def __eq__(self, other):
+        return self is other or (self.s == other.s and self.start == other.start and self.rule == other.rule)
+
+    def __hash__(self):
+        return self._hash
+
+    def __repr__(self):
+        before = ( expansion.name for expansion in self.rule.expansion[:self.ptr] )
+        after = ( expansion.name for expansion in self.rule.expansion[self.ptr:] )
+        symbol = "{} ::= {}* {}".format(self.rule.origin.name, ' '.join(before), ' '.join(after))
+        return '%s (%d)' % (symbol, self.start)
+
+
+# class TransitiveItem(Item):
+#   ...   # removed at commit 4c1cfb2faf24e8f8bff7112627a00b94d261b420
diff --git a/server/libs/lark/parsers/earley_forest.py b/server/libs/lark/parsers/earley_forest.py
new file mode 100644
index 0000000..c60f3a6
--- /dev/null
+++ b/server/libs/lark/parsers/earley_forest.py
@@ -0,0 +1,802 @@
+""""This module implements an SPPF implementation
+
+This is used as the primary output mechanism for the Earley parser
+in order to store complex ambiguities.
+
+Full reference and more details is here:
+https://web.archive.org/web/20190616123959/http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/
+"""
+
+from typing import Type, AbstractSet
+from random import randint
+from collections import deque
+from operator import attrgetter
+from importlib import import_module
+from functools import partial
+
+from ..parse_tree_builder import AmbiguousIntermediateExpander
+from ..visitors import Discard
+from ..utils import logger, OrderedSet
+from ..tree import Tree
+
+class ForestNode:
+    pass
+
+class SymbolNode(ForestNode):
+    """
+    A Symbol Node represents a symbol (or Intermediate LR0).
+
+    Symbol nodes are keyed by the symbol (s). For intermediate nodes
+    s will be an LR0, stored as a tuple of (rule, ptr). For completed symbol
+    nodes, s will be a string representing the non-terminal origin (i.e.
+    the left hand side of the rule).
+
+    The children of a Symbol or Intermediate Node will always be Packed Nodes;
+    with each Packed Node child representing a single derivation of a production.
+
+    Hence a Symbol Node with a single child is unambiguous.
+
+    Parameters:
+        s: A Symbol, or a tuple of (rule, ptr) for an intermediate node.
+        start: For dynamic lexers, the index of the start of the substring matched by this symbol (inclusive).
+        end: For dynamic lexers, the index of the end of the substring matched by this symbol (exclusive).
+
+    Properties:
+        is_intermediate: True if this node is an intermediate node.
+        priority: The priority of the node's symbol.
+    """
+    Set: Type[AbstractSet] = set   # Overridden by StableSymbolNode
+    __slots__ = ('s', 'start', 'end', '_children', 'paths', 'paths_loaded', 'priority', 'is_intermediate')
+    def __init__(self, s, start, end):
+        self.s = s
+        self.start = start
+        self.end = end
+        self._children = self.Set()
+        self.paths = self.Set()
+        self.paths_loaded = False
+
+        ### We use inf here as it can be safely negated without resorting to conditionals,
+        #   unlike None or float('NaN'), and sorts appropriately.
+        self.priority = float('-inf')
+        self.is_intermediate = isinstance(s, tuple)
+
+    def add_family(self, lr0, rule, start, left, right):
+        self._children.add(PackedNode(self, lr0, rule, start, left, right))
+
+    def add_path(self, transitive, node):
+        self.paths.add((transitive, node))
+
+    def load_paths(self):
+        for transitive, node in self.paths:
+            if transitive.next_titem is not None:
+                vn = type(self)(transitive.next_titem.s, transitive.next_titem.start, self.end)
+                vn.add_path(transitive.next_titem, node)
+                self.add_family(transitive.reduction.rule.origin, transitive.reduction.rule, transitive.reduction.start, transitive.reduction.node, vn)
+            else:
+                self.add_family(transitive.reduction.rule.origin, transitive.reduction.rule, transitive.reduction.start, transitive.reduction.node, node)
+        self.paths_loaded = True
+
+    @property
+    def is_ambiguous(self):
+        """Returns True if this node is ambiguous."""
+        return len(self.children) > 1
+
+    @property
+    def children(self):
+        """Returns a list of this node's children sorted from greatest to
+        least priority."""
+        if not self.paths_loaded:
+            self.load_paths()
+        return sorted(self._children, key=attrgetter('sort_key'))
+
+    def __iter__(self):
+        return iter(self._children)
+
+    def __repr__(self):
+        if self.is_intermediate:
+            rule = self.s[0]
+            ptr = self.s[1]
+            before = ( expansion.name for expansion in rule.expansion[:ptr] )
+            after = ( expansion.name for expansion in rule.expansion[ptr:] )
+            symbol = "{} ::= {}* {}".format(rule.origin.name, ' '.join(before), ' '.join(after))
+        else:
+            symbol = self.s.name
+        return "({}, {}, {}, {})".format(symbol, self.start, self.end, self.priority)
+
+class StableSymbolNode(SymbolNode):
+    "A version of SymbolNode that uses OrderedSet for output stability"
+    Set = OrderedSet
+
+class PackedNode(ForestNode):
+    """
+    A Packed Node represents a single derivation in a symbol node.
+
+    Parameters:
+        rule: The rule associated with this node.
+        parent: The parent of this node.
+        left: The left child of this node. ``None`` if one does not exist.
+        right: The right child of this node. ``None`` if one does not exist.
+        priority: The priority of this node.
+    """
+    __slots__ = ('parent', 's', 'rule', 'start', 'left', 'right', 'priority', '_hash')
+    def __init__(self, parent, s, rule, start, left, right):
+        self.parent = parent
+        self.s = s
+        self.start = start
+        self.rule = rule
+        self.left = left
+        self.right = right
+        self.priority = float('-inf')
+        self._hash = hash((self.left, self.right))
+
+    @property
+    def is_empty(self):
+        return self.left is None and self.right is None
+
+    @property
+    def sort_key(self):
+        """
+        Used to sort PackedNode children of SymbolNodes.
+        A SymbolNode has multiple PackedNodes if it matched
+        ambiguously. Hence, we use the sort order to identify
+        the order in which ambiguous children should be considered.
+        """
+        return self.is_empty, -self.priority, self.rule.order
+
+    @property
+    def children(self):
+        """Returns a list of this node's children."""
+        return [x for x in [self.left, self.right] if x is not None]
+
+    def __iter__(self):
+        yield self.left
+        yield self.right
+
+    def __eq__(self, other):
+        if not isinstance(other, PackedNode):
+            return False
+        return self is other or (self.left == other.left and self.right == other.right)
+
+    def __hash__(self):
+        return self._hash
+
+    def __repr__(self):
+        if isinstance(self.s, tuple):
+            rule = self.s[0]
+            ptr = self.s[1]
+            before = ( expansion.name for expansion in rule.expansion[:ptr] )
+            after = ( expansion.name for expansion in rule.expansion[ptr:] )
+            symbol = "{} ::= {}* {}".format(rule.origin.name, ' '.join(before), ' '.join(after))
+        else:
+            symbol = self.s.name
+        return "({}, {}, {}, {})".format(symbol, self.start, self.priority, self.rule.order)
+
+class TokenNode(ForestNode):
+    """
+    A Token Node represents a matched terminal and is always a leaf node.
+
+    Parameters:
+        token: The Token associated with this node.
+        term: The TerminalDef matched by the token.
+        priority: The priority of this node.
+    """
+    __slots__ = ('token', 'term', 'priority', '_hash')
+    def __init__(self, token, term, priority=None):
+        self.token = token
+        self.term = term
+        if priority is not None:
+            self.priority = priority
+        else:
+            self.priority = term.priority if term is not None else 0
+        self._hash = hash(token)
+
+    def __eq__(self, other):
+        if not isinstance(other, TokenNode):
+            return False
+        return self is other or (self.token == other.token)
+
+    def __hash__(self):
+        return self._hash
+
+    def __repr__(self):
+        return repr(self.token)
+
+class ForestVisitor:
+    """
+    An abstract base class for building forest visitors.
+
+    This class performs a controllable depth-first walk of an SPPF.
+    The visitor will not enter cycles and will backtrack if one is encountered.
+    Subclasses are notified of cycles through the ``on_cycle`` method.
+
+    Behavior for visit events is defined by overriding the
+    ``visit*node*`` functions.
+
+    The walk is controlled by the return values of the ``visit*node_in``
+    methods. Returning a node(s) will schedule them to be visited. The visitor
+    will begin to backtrack if no nodes are returned.
+
+    Parameters:
+        single_visit: If ``True``, non-Token nodes will only be visited once.
+    """
+
+    def __init__(self, single_visit=False):
+        self.single_visit = single_visit
+
+    def visit_token_node(self, node):
+        """Called when a ``Token`` is visited. ``Token`` nodes are always leaves."""
+        pass
+
+    def visit_symbol_node_in(self, node):
+        """Called when a symbol node is visited. Nodes that are returned
+        will be scheduled to be visited. If ``visit_intermediate_node_in``
+        is not implemented, this function will be called for intermediate
+        nodes as well."""
+        pass
+
+    def visit_symbol_node_out(self, node):
+        """Called after all nodes returned from a corresponding ``visit_symbol_node_in``
+        call have been visited. If ``visit_intermediate_node_out``
+        is not implemented, this function will be called for intermediate
+        nodes as well."""
+        pass
+
+    def visit_packed_node_in(self, node):
+        """Called when a packed node is visited. Nodes that are returned
+        will be scheduled to be visited. """
+        pass
+
+    def visit_packed_node_out(self, node):
+        """Called after all nodes returned from a corresponding ``visit_packed_node_in``
+        call have been visited."""
+        pass
+
+    def on_cycle(self, node, path):
+        """Called when a cycle is encountered.
+
+        Parameters:
+            node: The node that causes a cycle.
+            path: The list of nodes being visited: nodes that have been
+                entered but not exited. The first element is the root in a forest
+                visit, and the last element is the node visited most recently.
+                ``path`` should be treated as read-only.
+        """
+        pass
+
+    def get_cycle_in_path(self, node, path):
+        """A utility function for use in ``on_cycle`` to obtain a slice of
+        ``path`` that only contains the nodes that make up the cycle."""
+        index = len(path) - 1
+        while id(path[index]) != id(node):
+            index -= 1
+        return path[index:]
+
+    def visit(self, root):
+        # Visiting is a list of IDs of all symbol/intermediate nodes currently in
+        # the stack. It serves two purposes: to detect when we 'recurse' in and out
+        # of a symbol/intermediate so that we can process both up and down. Also,
+        # since the SPPF can have cycles it allows us to detect if we're trying
+        # to recurse into a node that's already on the stack (infinite recursion).
+        visiting = set()
+
+        # set of all nodes that have been visited
+        visited = set()
+
+        # a list of nodes that are currently being visited
+        # used for the `on_cycle` callback
+        path = []
+
+        # We do not use recursion here to walk the Forest due to the limited
+        # stack size in python. Therefore input_stack is essentially our stack.
+        input_stack = deque([root])
+
+        # It is much faster to cache these as locals since they are called
+        # many times in large parses.
+        vpno = getattr(self, 'visit_packed_node_out')
+        vpni = getattr(self, 'visit_packed_node_in')
+        vsno = getattr(self, 'visit_symbol_node_out')
+        vsni = getattr(self, 'visit_symbol_node_in')
+        vino = getattr(self, 'visit_intermediate_node_out', vsno)
+        vini = getattr(self, 'visit_intermediate_node_in', vsni)
+        vtn = getattr(self, 'visit_token_node')
+        oc = getattr(self, 'on_cycle')
+
+        while input_stack:
+            current = next(reversed(input_stack))
+            try:
+                next_node = next(current)
+            except StopIteration:
+                input_stack.pop()
+                continue
+            except TypeError:
+                ### If the current object is not an iterator, pass through to Token/SymbolNode
+                pass
+            else:
+                if next_node is None:
+                    continue
+
+                if id(next_node) in visiting:
+                    oc(next_node, path)
+                    continue
+
+                input_stack.append(next_node)
+                continue
+
+            if isinstance(current, TokenNode):
+                vtn(current.token)
+                input_stack.pop()
+                continue
+
+            current_id = id(current)
+            if current_id in visiting:
+                if isinstance(current, PackedNode):
+                    vpno(current)
+                elif current.is_intermediate:
+                    vino(current)
+                else:
+                    vsno(current)
+                input_stack.pop()
+                path.pop()
+                visiting.remove(current_id)
+                visited.add(current_id)
+            elif self.single_visit and current_id in visited:
+                input_stack.pop()
+            else:
+                visiting.add(current_id)
+                path.append(current)
+                if isinstance(current, PackedNode):
+                    next_node = vpni(current)
+                elif current.is_intermediate:
+                    next_node = vini(current)
+                else:
+                    next_node = vsni(current)
+                if next_node is None:
+                    continue
+
+                if not isinstance(next_node, ForestNode):
+                    next_node = iter(next_node)
+                elif id(next_node) in visiting:
+                    oc(next_node, path)
+                    continue
+
+                input_stack.append(next_node)
+
+class ForestTransformer(ForestVisitor):
+    """The base class for a bottom-up forest transformation. Most users will
+    want to use ``TreeForestTransformer`` instead as it has a friendlier
+    interface and covers most use cases.
+
+    Transformations are applied via inheritance and overriding of the
+    ``transform*node`` methods.
+
+    ``transform_token_node`` receives a ``Token`` as an argument.
+    All other methods receive the node that is being transformed and
+    a list of the results of the transformations of that node's children.
+    The return value of these methods are the resulting transformations.
+
+    If ``Discard`` is raised in a node's transformation, no data from that node
+    will be passed to its parent's transformation.
+    """
+
+    def __init__(self):
+        super(ForestTransformer, self).__init__()
+        # results of transformations
+        self.data = dict()
+        # used to track parent nodes
+        self.node_stack = deque()
+
+    def transform(self, root):
+        """Perform a transformation on an SPPF."""
+        self.node_stack.append('result')
+        self.data['result'] = []
+        self.visit(root)
+        assert len(self.data['result']) <= 1
+        if self.data['result']:
+            return self.data['result'][0]
+
+    def transform_symbol_node(self, node, data):
+        """Transform a symbol node."""
+        return node
+
+    def transform_intermediate_node(self, node, data):
+        """Transform an intermediate node."""
+        return node
+
+    def transform_packed_node(self, node, data):
+        """Transform a packed node."""
+        return node
+
+    def transform_token_node(self, node):
+        """Transform a ``Token``."""
+        return node
+
+    def visit_symbol_node_in(self, node):
+        self.node_stack.append(id(node))
+        self.data[id(node)] = []
+        return node.children
+
+    def visit_packed_node_in(self, node):
+        self.node_stack.append(id(node))
+        self.data[id(node)] = []
+        return node.children
+
+    def visit_token_node(self, node):
+        transformed = self.transform_token_node(node)
+        if transformed is not Discard:
+            self.data[self.node_stack[-1]].append(transformed)
+
+    def _visit_node_out_helper(self, node, method):
+        self.node_stack.pop()
+        transformed = method(node, self.data[id(node)])
+        if transformed is not Discard:
+            self.data[self.node_stack[-1]].append(transformed)
+        del self.data[id(node)]
+
+    def visit_symbol_node_out(self, node):
+        self._visit_node_out_helper(node, self.transform_symbol_node)
+
+    def visit_intermediate_node_out(self, node):
+        self._visit_node_out_helper(node, self.transform_intermediate_node)
+
+    def visit_packed_node_out(self, node):
+        self._visit_node_out_helper(node, self.transform_packed_node)
+
+
+class ForestSumVisitor(ForestVisitor):
+    """
+    A visitor for prioritizing ambiguous parts of the Forest.
+
+    This visitor is used when support for explicit priorities on
+    rules is requested (whether normal, or invert). It walks the
+    forest (or subsets thereof) and cascades properties upwards
+    from the leaves.
+
+    It would be ideal to do this during parsing, however this would
+    require processing each Earley item multiple times. That's
+    a big performance drawback; so running a forest walk is the
+    lesser of two evils: there can be significantly more Earley
+    items created during parsing than there are SPPF nodes in the
+    final tree.
+    """
+    def __init__(self):
+        super(ForestSumVisitor, self).__init__(single_visit=True)
+
+    def visit_packed_node_in(self, node):
+        yield node.left
+        yield node.right
+
+    def visit_symbol_node_in(self, node):
+        return iter(node.children)
+
+    def visit_packed_node_out(self, node):
+        priority = node.rule.options.priority if not node.parent.is_intermediate and node.rule.options.priority else 0
+        priority += getattr(node.right, 'priority', 0)
+        priority += getattr(node.left, 'priority', 0)
+        node.priority = priority
+
+    def visit_symbol_node_out(self, node):
+        node.priority = max(child.priority for child in node.children)
+
+class PackedData():
+    """Used in transformationss of packed nodes to distinguish the data
+    that comes from the left child and the right child.
+    """
+
+    class _NoData():
+        pass
+
+    NO_DATA = _NoData()
+
+    def __init__(self, node, data):
+        self.left = self.NO_DATA
+        self.right = self.NO_DATA
+        if data:
+            if node.left is not None:
+                self.left = data[0]
+                if len(data) > 1:
+                    self.right = data[1]
+            else:
+                self.right = data[0]
+
+class ForestToParseTree(ForestTransformer):
+    """Used by the earley parser when ambiguity equals 'resolve' or
+    'explicit'. Transforms an SPPF into an (ambiguous) parse tree.
+
+    Parameters:
+        tree_class: The tree class to use for construction
+        callbacks: A dictionary of rules to functions that output a tree
+        prioritizer: A ``ForestVisitor`` that manipulates the priorities of ForestNodes
+        resolve_ambiguity: If True, ambiguities will be resolved based on
+                        priorities. Otherwise, `_ambig` nodes will be in the resulting tree.
+        use_cache: If True, the results of packed node transformations will be cached.
+    """
+
+    def __init__(self, tree_class=Tree, callbacks=dict(), prioritizer=ForestSumVisitor(), resolve_ambiguity=True, use_cache=True):
+        super(ForestToParseTree, self).__init__()
+        self.tree_class = tree_class
+        self.callbacks = callbacks
+        self.prioritizer = prioritizer
+        self.resolve_ambiguity = resolve_ambiguity
+        self._use_cache = use_cache
+        self._cache = {}
+        self._on_cycle_retreat = False
+        self._cycle_node = None
+        self._successful_visits = set()
+
+    def visit(self, root):
+        if self.prioritizer:
+            self.prioritizer.visit(root)
+        super(ForestToParseTree, self).visit(root)
+        self._cache = {}
+
+    def on_cycle(self, node, path):
+        logger.debug("Cycle encountered in the SPPF at node: %s. "
+                "As infinite ambiguities cannot be represented in a tree, "
+                "this family of derivations will be discarded.", node)
+        self._cycle_node = node
+        self._on_cycle_retreat = True
+
+    def _check_cycle(self, node):
+        if self._on_cycle_retreat:
+            if id(node) == id(self._cycle_node) or id(node) in self._successful_visits:
+                self._cycle_node = None
+                self._on_cycle_retreat = False
+            else:
+                return Discard
+
+    def _collapse_ambig(self, children):
+        new_children = []
+        for child in children:
+            if hasattr(child, 'data') and child.data == '_ambig':
+                new_children += child.children
+            else:
+                new_children.append(child)
+        return new_children
+
+    def _call_rule_func(self, node, data):
+        # called when transforming children of symbol nodes
+        # data is a list of trees or tokens that correspond to the
+        # symbol's rule expansion
+        return self.callbacks[node.rule](data)
+
+    def _call_ambig_func(self, node, data):
+        # called when transforming a symbol node
+        # data is a list of trees where each tree's data is
+        # equal to the name of the symbol or one of its aliases.
+        if len(data) > 1:
+            return self.tree_class('_ambig', data)
+        elif data:
+            return data[0]
+        return Discard
+
+    def transform_symbol_node(self, node, data):
+        if id(node) not in self._successful_visits:
+            return Discard
+        r = self._check_cycle(node)
+        if r is Discard:
+            return r
+        self._successful_visits.remove(id(node))
+        data = self._collapse_ambig(data)
+        return self._call_ambig_func(node, data)
+
+    def transform_intermediate_node(self, node, data):
+        if id(node) not in self._successful_visits:
+            return Discard
+        r = self._check_cycle(node)
+        if r is Discard:
+            return r
+        self._successful_visits.remove(id(node))
+        if len(data) > 1:
+            children = [self.tree_class('_inter', c) for c in data]
+            return self.tree_class('_iambig', children)
+        return data[0]
+
+    def transform_packed_node(self, node, data):
+        r = self._check_cycle(node)
+        if r is Discard:
+            return r
+        if self.resolve_ambiguity and id(node.parent) in self._successful_visits:
+            return Discard
+        if self._use_cache and id(node) in self._cache:
+            return self._cache[id(node)]
+        children = []
+        assert len(data) <= 2
+        data = PackedData(node, data)
+        if data.left is not PackedData.NO_DATA:
+            if node.left.is_intermediate and isinstance(data.left, list):
+                children += data.left
+            else:
+                children.append(data.left)
+        if data.right is not PackedData.NO_DATA:
+            children.append(data.right)
+        transformed = children if node.parent.is_intermediate else self._call_rule_func(node, children)
+        if self._use_cache:
+            self._cache[id(node)] = transformed
+        return transformed
+
+    def visit_symbol_node_in(self, node):
+        super(ForestToParseTree, self).visit_symbol_node_in(node)
+        if self._on_cycle_retreat:
+            return
+        return node.children
+
+    def visit_packed_node_in(self, node):
+        self._on_cycle_retreat = False
+        to_visit = super(ForestToParseTree, self).visit_packed_node_in(node)
+        if not self.resolve_ambiguity or id(node.parent) not in self._successful_visits:
+            if not self._use_cache or id(node) not in self._cache:
+                return to_visit
+
+    def visit_packed_node_out(self, node):
+        super(ForestToParseTree, self).visit_packed_node_out(node)
+        if not self._on_cycle_retreat:
+            self._successful_visits.add(id(node.parent))
+
+def handles_ambiguity(func):
+    """Decorator for methods of subclasses of ``TreeForestTransformer``.
+    Denotes that the method should receive a list of transformed derivations."""
+    func.handles_ambiguity = True
+    return func
+
+class TreeForestTransformer(ForestToParseTree):
+    """A ``ForestTransformer`` with a tree ``Transformer``-like interface.
+    By default, it will construct a tree.
+
+    Methods provided via inheritance are called based on the rule/symbol
+    names of nodes in the forest.
+
+    Methods that act on rules will receive a list of the results of the
+    transformations of the rule's children. By default, trees and tokens.
+
+    Methods that act on tokens will receive a token.
+
+    Alternatively, methods that act on rules may be annotated with
+    ``handles_ambiguity``. In this case, the function will receive a list
+    of all the transformations of all the derivations of the rule.
+    By default, a list of trees where each tree.data is equal to the
+    rule name or one of its aliases.
+
+    Non-tree transformations are made possible by override of
+    ``__default__``, ``__default_token__``, and ``__default_ambig__``.
+
+    Note:
+        Tree shaping features such as inlined rules and token filtering are
+        not built into the transformation. Positions are also not propagated.
+
+    Parameters:
+        tree_class: The tree class to use for construction
+        prioritizer: A ``ForestVisitor`` that manipulates the priorities of nodes in the SPPF.
+        resolve_ambiguity: If True, ambiguities will be resolved based on priorities.
+        use_cache (bool): If True, caches the results of some transformations,
+                          potentially improving performance when ``resolve_ambiguity==False``.
+                          Only use if you know what you are doing: i.e. All transformation
+                          functions are pure and referentially transparent.
+    """
+
+    def __init__(self, tree_class=Tree, prioritizer=ForestSumVisitor(), resolve_ambiguity=True, use_cache=False):
+        super(TreeForestTransformer, self).__init__(tree_class, dict(), prioritizer, resolve_ambiguity, use_cache)
+
+    def __default__(self, name, data):
+        """Default operation on tree (for override).
+
+        Returns a tree with name with data as children.
+        """
+        return self.tree_class(name, data)
+
+    def __default_ambig__(self, name, data):
+        """Default operation on ambiguous rule (for override).
+
+        Wraps data in an '_ambig_' node if it contains more than
+        one element.
+        """
+        if len(data) > 1:
+            return self.tree_class('_ambig', data)
+        elif data:
+            return data[0]
+        return Discard
+
+    def __default_token__(self, node):
+        """Default operation on ``Token`` (for override).
+
+        Returns ``node``.
+        """
+        return node
+
+    def transform_token_node(self, node):
+        return getattr(self, node.type, self.__default_token__)(node)
+
+    def _call_rule_func(self, node, data):
+        name = node.rule.alias or node.rule.options.template_source or node.rule.origin.name
+        user_func = getattr(self, name, self.__default__)
+        if user_func == self.__default__ or hasattr(user_func, 'handles_ambiguity'):
+            user_func = partial(self.__default__, name)
+        if not self.resolve_ambiguity:
+            wrapper = partial(AmbiguousIntermediateExpander, self.tree_class)
+            user_func = wrapper(user_func)
+        return user_func(data)
+
+    def _call_ambig_func(self, node, data):
+        name = node.s.name
+        user_func = getattr(self, name, self.__default_ambig__)
+        if user_func == self.__default_ambig__ or not hasattr(user_func, 'handles_ambiguity'):
+            user_func = partial(self.__default_ambig__, name)
+        return user_func(data)
+
+class ForestToPyDotVisitor(ForestVisitor):
+    """
+    A Forest visitor which writes the SPPF to a PNG.
+
+    The SPPF can get really large, really quickly because
+    of the amount of meta-data it stores, so this is probably
+    only useful for trivial trees and learning how the SPPF
+    is structured.
+    """
+    def __init__(self, rankdir="TB"):
+        super(ForestToPyDotVisitor, self).__init__(single_visit=True)
+        self.pydot = import_module('pydot')
+        self.graph = self.pydot.Dot(graph_type='digraph', rankdir=rankdir)
+
+    def visit(self, root, filename):
+        super(ForestToPyDotVisitor, self).visit(root)
+        try:
+            self.graph.write_png(filename)
+        except FileNotFoundError as e:
+            logger.error("Could not write png: ", e)
+
+    def visit_token_node(self, node):
+        graph_node_id = str(id(node))
+        graph_node_label = "\"{}\"".format(node.value.replace('"', '\\"'))
+        graph_node_color = 0x808080
+        graph_node_style = "\"filled,rounded\""
+        graph_node_shape = "diamond"
+        graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label)
+        self.graph.add_node(graph_node)
+
+    def visit_packed_node_in(self, node):
+        graph_node_id = str(id(node))
+        graph_node_label = repr(node)
+        graph_node_color = 0x808080
+        graph_node_style = "filled"
+        graph_node_shape = "diamond"
+        graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label)
+        self.graph.add_node(graph_node)
+        yield node.left
+        yield node.right
+
+    def visit_packed_node_out(self, node):
+        graph_node_id = str(id(node))
+        graph_node = self.graph.get_node(graph_node_id)[0]
+        for child in [node.left, node.right]:
+            if child is not None:
+                child_graph_node_id = str(id(child.token if isinstance(child, TokenNode) else child))
+                child_graph_node = self.graph.get_node(child_graph_node_id)[0]
+                self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node))
+            else:
+                #### Try and be above the Python object ID range; probably impl. specific, but maybe this is okay.
+                child_graph_node_id = str(randint(100000000000000000000000000000,123456789012345678901234567890))
+                child_graph_node_style = "invis"
+                child_graph_node = self.pydot.Node(child_graph_node_id, style=child_graph_node_style, label="None")
+                child_edge_style = "invis"
+                self.graph.add_node(child_graph_node)
+                self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node, style=child_edge_style))
+
+    def visit_symbol_node_in(self, node):
+        graph_node_id = str(id(node))
+        graph_node_label = repr(node)
+        graph_node_color = 0x808080
+        graph_node_style = "\"filled\""
+        if node.is_intermediate:
+            graph_node_shape = "ellipse"
+        else:
+            graph_node_shape = "rectangle"
+        graph_node = self.pydot.Node(graph_node_id, style=graph_node_style, fillcolor="#{:06x}".format(graph_node_color), shape=graph_node_shape, label=graph_node_label)
+        self.graph.add_node(graph_node)
+        return iter(node.children)
+
+    def visit_symbol_node_out(self, node):
+        graph_node_id = str(id(node))
+        graph_node = self.graph.get_node(graph_node_id)[0]
+        for child in node.children:
+            child_graph_node_id = str(id(child))
+            child_graph_node = self.graph.get_node(child_graph_node_id)[0]
+            self.graph.add_edge(self.pydot.Edge(graph_node, child_graph_node))
diff --git a/server/libs/lark/parsers/grammar_analysis.py b/server/libs/lark/parsers/grammar_analysis.py
new file mode 100644
index 0000000..28d3cb6
--- /dev/null
+++ b/server/libs/lark/parsers/grammar_analysis.py
@@ -0,0 +1,203 @@
+"Provides for superficial grammar analysis."
+
+from collections import Counter, defaultdict
+from typing import List, Dict, Iterator, FrozenSet, Set
+
+from ..utils import bfs, fzset, classify, OrderedSet
+from ..exceptions import GrammarError
+from ..grammar import Rule, Terminal, NonTerminal, Symbol
+from ..common import ParserConf
+
+
+class RulePtr:
+    __slots__ = ('rule', 'index')
+    rule: Rule
+    index: int
+
+    def __init__(self, rule: Rule, index: int):
+        assert isinstance(rule, Rule)
+        assert index <= len(rule.expansion)
+        self.rule = rule
+        self.index = index
+
+    def __repr__(self):
+        before = [x.name for x in self.rule.expansion[:self.index]]
+        after = [x.name for x in self.rule.expansion[self.index:]]
+        return '<%s : %s * %s>' % (self.rule.origin.name, ' '.join(before), ' '.join(after))
+
+    @property
+    def next(self) -> Symbol:
+        return self.rule.expansion[self.index]
+
+    def advance(self, sym: Symbol) -> 'RulePtr':
+        assert self.next == sym
+        return RulePtr(self.rule, self.index+1)
+
+    @property
+    def is_satisfied(self) -> bool:
+        return self.index == len(self.rule.expansion)
+
+    def __eq__(self, other) -> bool:
+        if not isinstance(other, RulePtr):
+            return NotImplemented
+        return self.rule == other.rule and self.index == other.index
+
+    def __hash__(self) -> int:
+        return hash((self.rule, self.index))
+
+
+State = FrozenSet[RulePtr]
+
+# state generation ensures no duplicate LR0ItemSets
+class LR0ItemSet:
+    __slots__ = ('kernel', 'closure', 'transitions', 'lookaheads')
+
+    kernel: State
+    closure: State
+    transitions: Dict[Symbol, 'LR0ItemSet']
+    lookaheads: Dict[Symbol, Set[Rule]]
+
+    def __init__(self, kernel, closure):
+        self.kernel = fzset(kernel)
+        self.closure = fzset(closure)
+        self.transitions = {}
+        self.lookaheads = defaultdict(set)
+
+    def __repr__(self):
+        return '{%s | %s}' % (', '.join([repr(r) for r in self.kernel]), ', '.join([repr(r) for r in self.closure]))
+
+
+def update_set(set1, set2):
+    if not set2 or set1 > set2:
+        return False
+
+    copy = set(set1)
+    set1 |= set2
+    return set1 != copy
+
+def calculate_sets(rules):
+    """Calculate FOLLOW sets.
+
+    Adapted from: http://lara.epfl.ch/w/cc09:algorithm_for_first_and_follow_sets"""
+    symbols = {sym for rule in rules for sym in rule.expansion} | {rule.origin for rule in rules}
+
+    # foreach grammar rule X ::= Y(1) ... Y(k)
+    # if k=0 or {Y(1),...,Y(k)} subset of NULLABLE then
+    #   NULLABLE = NULLABLE union {X}
+    # for i = 1 to k
+    #   if i=1 or {Y(1),...,Y(i-1)} subset of NULLABLE then
+    #     FIRST(X) = FIRST(X) union FIRST(Y(i))
+    #   for j = i+1 to k
+    #     if i=k or {Y(i+1),...Y(k)} subset of NULLABLE then
+    #       FOLLOW(Y(i)) = FOLLOW(Y(i)) union FOLLOW(X)
+    #     if i+1=j or {Y(i+1),...,Y(j-1)} subset of NULLABLE then
+    #       FOLLOW(Y(i)) = FOLLOW(Y(i)) union FIRST(Y(j))
+    # until none of NULLABLE,FIRST,FOLLOW changed in last iteration
+
+    NULLABLE = set()
+    FIRST = {}
+    FOLLOW = {}
+    for sym in symbols:
+        FIRST[sym]={sym} if sym.is_term else set()
+        FOLLOW[sym]=set()
+
+    # Calculate NULLABLE and FIRST
+    changed = True
+    while changed:
+        changed = False
+
+        for rule in rules:
+            if set(rule.expansion) <= NULLABLE:
+                if update_set(NULLABLE, {rule.origin}):
+                    changed = True
+
+            for i, sym in enumerate(rule.expansion):
+                if set(rule.expansion[:i]) <= NULLABLE:
+                    if update_set(FIRST[rule.origin], FIRST[sym]):
+                        changed = True
+                else:
+                    break
+
+    # Calculate FOLLOW
+    changed = True
+    while changed:
+        changed = False
+
+        for rule in rules:
+            for i, sym in enumerate(rule.expansion):
+                if i==len(rule.expansion)-1 or set(rule.expansion[i+1:]) <= NULLABLE:
+                    if update_set(FOLLOW[sym], FOLLOW[rule.origin]):
+                        changed = True
+
+                for j in range(i+1, len(rule.expansion)):
+                    if set(rule.expansion[i+1:j]) <= NULLABLE:
+                        if update_set(FOLLOW[sym], FIRST[rule.expansion[j]]):
+                            changed = True
+
+    return FIRST, FOLLOW, NULLABLE
+
+
+class GrammarAnalyzer:
+    def __init__(self, parser_conf: ParserConf, debug: bool=False, strict: bool=False):
+        self.debug = debug
+        self.strict = strict
+
+        root_rules = {start: Rule(NonTerminal('$root_' + start), [NonTerminal(start), Terminal('$END')])
+                      for start in parser_conf.start}
+
+        rules = parser_conf.rules + list(root_rules.values())
+        self.rules_by_origin: Dict[NonTerminal, List[Rule]] = classify(rules, lambda r: r.origin)
+
+        if len(rules) != len(set(rules)):
+            duplicates = [item for item, count in Counter(rules).items() if count > 1]
+            raise GrammarError("Rules defined twice: %s" % ', '.join(str(i) for i in duplicates))
+
+        for r in rules:
+            for sym in r.expansion:
+                if not (sym.is_term or sym in self.rules_by_origin):
+                    raise GrammarError("Using an undefined rule: %s" % sym)
+
+        self.start_states = {start: self.expand_rule(root_rule.origin)
+                             for start, root_rule in root_rules.items()}
+
+        self.end_states = {start: fzset({RulePtr(root_rule, len(root_rule.expansion))})
+                           for start, root_rule in root_rules.items()}
+
+        lr0_root_rules = {start: Rule(NonTerminal('$root_' + start), [NonTerminal(start)])
+                for start in parser_conf.start}
+
+        lr0_rules = parser_conf.rules + list(lr0_root_rules.values())
+        assert(len(lr0_rules) == len(set(lr0_rules)))
+
+        self.lr0_rules_by_origin = classify(lr0_rules, lambda r: r.origin)
+
+        # cache RulePtr(r, 0) in r (no duplicate RulePtr objects)
+        self.lr0_start_states = {start: LR0ItemSet([RulePtr(root_rule, 0)], self.expand_rule(root_rule.origin, self.lr0_rules_by_origin))
+                for start, root_rule in lr0_root_rules.items()}
+
+        self.FIRST, self.FOLLOW, self.NULLABLE = calculate_sets(rules)
+
+    def expand_rule(self, source_rule: NonTerminal, rules_by_origin=None) -> OrderedSet[RulePtr]:
+        "Returns all init_ptrs accessible by rule (recursive)"
+
+        if rules_by_origin is None:
+            rules_by_origin = self.rules_by_origin
+
+        init_ptrs = OrderedSet[RulePtr]()
+        def _expand_rule(rule: NonTerminal) -> Iterator[NonTerminal]:
+            assert not rule.is_term, rule
+
+            for r in rules_by_origin[rule]:
+                init_ptr = RulePtr(r, 0)
+                init_ptrs.add(init_ptr)
+
+                if r.expansion: # if not empty rule
+                    new_r = init_ptr.next
+                    if not new_r.is_term:
+                        assert isinstance(new_r, NonTerminal)
+                        yield new_r
+
+        for _ in bfs([source_rule], _expand_rule):
+            pass
+
+        return init_ptrs
diff --git a/server/libs/lark/parsers/lalr_analysis.py b/server/libs/lark/parsers/lalr_analysis.py
new file mode 100644
index 0000000..b7b3fdf
--- /dev/null
+++ b/server/libs/lark/parsers/lalr_analysis.py
@@ -0,0 +1,332 @@
+"""This module builds a LALR(1) transition-table for lalr_parser.py
+
+For now, shift/reduce conflicts are automatically resolved as shifts.
+"""
+
+# Author: Erez Shinan (2017)
+# Email : erezshin@gmail.com
+
+from typing import Dict, Set, Iterator, Tuple, List, TypeVar, Generic
+from collections import defaultdict
+
+from ..utils import classify, classify_bool, bfs, fzset, Enumerator, logger
+from ..exceptions import GrammarError
+
+from .grammar_analysis import GrammarAnalyzer, Terminal, LR0ItemSet, RulePtr, State
+from ..grammar import Rule, Symbol
+from ..common import ParserConf
+
+###{standalone
+
+class Action:
+    def __init__(self, name):
+        self.name = name
+    def __str__(self):
+        return self.name
+    def __repr__(self):
+        return str(self)
+
+Shift = Action('Shift')
+Reduce = Action('Reduce')
+
+StateT = TypeVar("StateT")
+
+class ParseTableBase(Generic[StateT]):
+    states: Dict[StateT, Dict[str, Tuple]]
+    start_states: Dict[str, StateT]
+    end_states: Dict[str, StateT]
+
+    def __init__(self, states, start_states, end_states):
+        self.states = states
+        self.start_states = start_states
+        self.end_states = end_states
+
+    def serialize(self, memo):
+        tokens = Enumerator()
+
+        states = {
+            state: {tokens.get(token): ((1, arg.serialize(memo)) if action is Reduce else (0, arg))
+                    for token, (action, arg) in actions.items()}
+            for state, actions in self.states.items()
+        }
+
+        return {
+            'tokens': tokens.reversed(),
+            'states': states,
+            'start_states': self.start_states,
+            'end_states': self.end_states,
+        }
+
+    @classmethod
+    def deserialize(cls, data, memo):
+        tokens = data['tokens']
+        states = {
+            state: {tokens[token]: ((Reduce, Rule.deserialize(arg, memo)) if action==1 else (Shift, arg))
+                    for token, (action, arg) in actions.items()}
+            for state, actions in data['states'].items()
+        }
+        return cls(states, data['start_states'], data['end_states'])
+
+class ParseTable(ParseTableBase['State']):
+    """Parse-table whose key is State, i.e. set[RulePtr]
+
+    Slower than IntParseTable, but useful for debugging
+    """
+    pass
+
+
+class IntParseTable(ParseTableBase[int]):
+    """Parse-table whose key is int. Best for performance."""
+
+    @classmethod
+    def from_ParseTable(cls, parse_table: ParseTable):
+        enum = list(parse_table.states)
+        state_to_idx: Dict['State', int] = {s:i for i,s in enumerate(enum)}
+        int_states = {}
+
+        for s, la in parse_table.states.items():
+            la = {k:(v[0], state_to_idx[v[1]]) if v[0] is Shift else v
+                  for k,v in la.items()}
+            int_states[ state_to_idx[s] ] = la
+
+
+        start_states = {start:state_to_idx[s] for start, s in parse_table.start_states.items()}
+        end_states = {start:state_to_idx[s] for start, s in parse_table.end_states.items()}
+        return cls(int_states, start_states, end_states)
+
+###}
+
+
+# digraph and traverse, see The Theory and Practice of Compiler Writing
+
+# computes F(x) = G(x) union (union { G(y) | x R y })
+# X: nodes
+# R: relation (function mapping node -> list of nodes that satisfy the relation)
+# G: set valued function
+def digraph(X, R, G):
+    F = {}
+    S = []
+    N = dict.fromkeys(X, 0)
+    for x in X:
+        # this is always true for the first iteration, but N[x] may be updated in traverse below
+        if N[x] == 0:
+            traverse(x, S, N, X, R, G, F)
+    return F
+
+# x: single node
+# S: stack
+# N: weights
+# X: nodes
+# R: relation (see above)
+# G: set valued function
+# F: set valued function we are computing (map of input -> output)
+def traverse(x, S, N, X, R, G, F):
+    S.append(x)
+    d = len(S)
+    N[x] = d
+    F[x] = G[x]
+    for y in R[x]:
+        if N[y] == 0:
+            traverse(y, S, N, X, R, G, F)
+        n_x = N[x]
+        assert(n_x > 0)
+        n_y = N[y]
+        assert(n_y != 0)
+        if (n_y > 0) and (n_y < n_x):
+            N[x] = n_y
+        F[x].update(F[y])
+    if N[x] == d:
+        f_x = F[x]
+        while True:
+            z = S.pop()
+            N[z] = -1
+            F[z] = f_x
+            if z == x:
+                break
+
+
+class LALR_Analyzer(GrammarAnalyzer):
+    lr0_itemsets: Set[LR0ItemSet]
+    nonterminal_transitions: List[Tuple[LR0ItemSet, Symbol]]
+    lookback: Dict[Tuple[LR0ItemSet, Symbol], Set[Tuple[LR0ItemSet, Rule]]]
+    includes: Dict[Tuple[LR0ItemSet, Symbol], Set[Tuple[LR0ItemSet, Symbol]]]
+    reads: Dict[Tuple[LR0ItemSet, Symbol], Set[Tuple[LR0ItemSet, Symbol]]]
+    directly_reads: Dict[Tuple[LR0ItemSet, Symbol], Set[Symbol]]
+
+
+    def __init__(self, parser_conf: ParserConf, debug: bool=False, strict: bool=False):
+        GrammarAnalyzer.__init__(self, parser_conf, debug, strict)
+        self.nonterminal_transitions = []
+        self.directly_reads = defaultdict(set)
+        self.reads = defaultdict(set)
+        self.includes = defaultdict(set)
+        self.lookback = defaultdict(set)
+
+
+    def compute_lr0_states(self) -> None:
+        self.lr0_itemsets = set()
+        # map of kernels to LR0ItemSets
+        cache: Dict['State', LR0ItemSet] = {}
+
+        def step(state: LR0ItemSet) -> Iterator[LR0ItemSet]:
+            _, unsat = classify_bool(state.closure, lambda rp: rp.is_satisfied)
+
+            d = classify(unsat, lambda rp: rp.next)
+            for sym, rps in d.items():
+                kernel = fzset({rp.advance(sym) for rp in rps})
+                new_state = cache.get(kernel, None)
+                if new_state is None:
+                    closure = set(kernel)
+                    for rp in kernel:
+                        if not rp.is_satisfied and not rp.next.is_term:
+                            closure |= self.expand_rule(rp.next, self.lr0_rules_by_origin)
+                    new_state = LR0ItemSet(kernel, closure)
+                    cache[kernel] = new_state
+
+                state.transitions[sym] = new_state
+                yield new_state
+
+            self.lr0_itemsets.add(state)
+
+        for _ in bfs(self.lr0_start_states.values(), step):
+            pass
+
+    def compute_reads_relations(self):
+        # handle start state
+        for root in self.lr0_start_states.values():
+            assert(len(root.kernel) == 1)
+            for rp in root.kernel:
+                assert(rp.index == 0)
+                self.directly_reads[(root, rp.next)] = set([ Terminal('$END') ])
+
+        for state in self.lr0_itemsets:
+            seen = set()
+            for rp in state.closure:
+                if rp.is_satisfied:
+                    continue
+                s = rp.next
+                # if s is a not a nonterminal
+                if s not in self.lr0_rules_by_origin:
+                    continue
+                if s in seen:
+                    continue
+                seen.add(s)
+                nt = (state, s)
+                self.nonterminal_transitions.append(nt)
+                dr = self.directly_reads[nt]
+                r = self.reads[nt]
+                next_state = state.transitions[s]
+                for rp2 in next_state.closure:
+                    if rp2.is_satisfied:
+                        continue
+                    s2 = rp2.next
+                    # if s2 is a terminal
+                    if s2 not in self.lr0_rules_by_origin:
+                        dr.add(s2)
+                    if s2 in self.NULLABLE:
+                        r.add((next_state, s2))
+
+    def compute_includes_lookback(self):
+        for nt in self.nonterminal_transitions:
+            state, nonterminal = nt
+            includes = []
+            lookback = self.lookback[nt]
+            for rp in state.closure:
+                if rp.rule.origin != nonterminal:
+                    continue
+                # traverse the states for rp(.rule)
+                state2 = state
+                for i in range(rp.index, len(rp.rule.expansion)):
+                    s = rp.rule.expansion[i]
+                    nt2 = (state2, s)
+                    state2 = state2.transitions[s]
+                    if nt2 not in self.reads:
+                        continue
+                    for j in range(i + 1, len(rp.rule.expansion)):
+                        if rp.rule.expansion[j] not in self.NULLABLE:
+                            break
+                    else:
+                        includes.append(nt2)
+                # state2 is at the final state for rp.rule
+                if rp.index == 0:
+                    for rp2 in state2.closure:
+                        if (rp2.rule == rp.rule) and rp2.is_satisfied:
+                            lookback.add((state2, rp2.rule))
+            for nt2 in includes:
+                self.includes[nt2].add(nt)
+
+    def compute_lookaheads(self):
+        read_sets = digraph(self.nonterminal_transitions, self.reads, self.directly_reads)
+        follow_sets = digraph(self.nonterminal_transitions, self.includes, read_sets)
+
+        for nt, lookbacks in self.lookback.items():
+            for state, rule in lookbacks:
+                for s in follow_sets[nt]:
+                    state.lookaheads[s].add(rule)
+
+    def compute_lalr1_states(self) -> None:
+        m: Dict[LR0ItemSet, Dict[str, Tuple]] = {}
+        reduce_reduce = []
+        for itemset in self.lr0_itemsets:
+            actions: Dict[Symbol, Tuple] = {la: (Shift, next_state.closure)
+                                                      for la, next_state in itemset.transitions.items()}
+            for la, rules in itemset.lookaheads.items():
+                if len(rules) > 1:
+                    # Try to resolve conflict based on priority
+                    p = [(r.options.priority or 0, r) for r in rules]
+                    p.sort(key=lambda r: r[0], reverse=True)
+                    best, second_best = p[:2]
+                    if best[0] > second_best[0]:
+                        rules = {best[1]}
+                    else:
+                        reduce_reduce.append((itemset, la, rules))
+                        continue
+
+                rule ,= rules
+                if la in actions:
+                    if self.strict:
+                        raise GrammarError(f"Shift/Reduce conflict for terminal {la.name}. [strict-mode]\n ")
+                    elif self.debug:
+                        logger.warning('Shift/Reduce conflict for terminal %s: (resolving as shift)', la.name)
+                        logger.warning(' * %s', rule)
+                    else:
+                        logger.debug('Shift/Reduce conflict for terminal %s: (resolving as shift)', la.name)
+                        logger.debug(' * %s', rule)
+                else:
+                    actions[la] = (Reduce, rule)
+            m[itemset] = { k.name: v for k, v in actions.items() }
+
+        if reduce_reduce:
+            msgs = []
+            for itemset, la, rules in reduce_reduce:
+                msg = 'Reduce/Reduce collision in %s between the following rules: %s' % (la, ''.join([ '\n\t- ' + str(r) for r in rules ]))
+                if self.debug:
+                    msg += '\n    collision occurred in state: {%s\n    }' % ''.join(['\n\t' + str(x) for x in itemset.closure])
+                msgs.append(msg)
+            raise GrammarError('\n\n'.join(msgs))
+
+        states = { k.closure: v for k, v in m.items() }
+
+        # compute end states
+        end_states: Dict[str, 'State'] = {}
+        for state in states:
+            for rp in state:
+                for start in self.lr0_start_states:
+                    if rp.rule.origin.name == ('$root_' + start) and rp.is_satisfied:
+                        assert start not in end_states
+                        end_states[start] = state
+
+        start_states = { start: state.closure for start, state in self.lr0_start_states.items() }
+        _parse_table = ParseTable(states, start_states, end_states)
+
+        if self.debug:
+            self.parse_table = _parse_table
+        else:
+            self.parse_table = IntParseTable.from_ParseTable(_parse_table)
+
+    def compute_lalr(self):
+        self.compute_lr0_states()
+        self.compute_reads_relations()
+        self.compute_includes_lookback()
+        self.compute_lookaheads()
+        self.compute_lalr1_states()
diff --git a/server/libs/lark/parsers/lalr_interactive_parser.py b/server/libs/lark/parsers/lalr_interactive_parser.py
new file mode 100644
index 0000000..fbf0d1f
--- /dev/null
+++ b/server/libs/lark/parsers/lalr_interactive_parser.py
@@ -0,0 +1,158 @@
+# This module provides a LALR interactive parser, which is used for debugging and error handling
+
+from typing import Iterator, List
+from copy import copy
+import warnings
+
+from lark.exceptions import UnexpectedToken
+from lark.lexer import Token, LexerThread
+from .lalr_parser_state import ParserState
+
+###{standalone
+
+class InteractiveParser:
+    """InteractiveParser gives you advanced control over parsing and error handling when parsing with LALR.
+
+    For a simpler interface, see the ``on_error`` argument to ``Lark.parse()``.
+    """
+    def __init__(self, parser, parser_state: ParserState, lexer_thread: LexerThread):
+        self.parser = parser
+        self.parser_state = parser_state
+        self.lexer_thread = lexer_thread
+        self.result = None
+
+    @property
+    def lexer_state(self) -> LexerThread:
+        warnings.warn("lexer_state will be removed in subsequent releases. Use lexer_thread instead.", DeprecationWarning)
+        return self.lexer_thread
+
+    def feed_token(self, token: Token):
+        """Feed the parser with a token, and advance it to the next state, as if it received it from the lexer.
+
+        Note that ``token`` has to be an instance of ``Token``.
+        """
+        return self.parser_state.feed_token(token, token.type == '$END')
+
+    def iter_parse(self) -> Iterator[Token]:
+        """Step through the different stages of the parse, by reading tokens from the lexer
+        and feeding them to the parser, one per iteration.
+
+        Returns an iterator of the tokens it encounters.
+
+        When the parse is over, the resulting tree can be found in ``InteractiveParser.result``.
+        """
+        for token in self.lexer_thread.lex(self.parser_state):
+            yield token
+            self.result = self.feed_token(token)
+
+    def exhaust_lexer(self) -> List[Token]:
+        """Try to feed the rest of the lexer state into the interactive parser.
+
+        Note that this modifies the instance in place and does not feed an '$END' Token
+        """
+        return list(self.iter_parse())
+
+
+    def feed_eof(self, last_token=None):
+        """Feed a '$END' Token. Borrows from 'last_token' if given."""
+        eof = Token.new_borrow_pos('$END', '', last_token) if last_token is not None else self.lexer_thread._Token('$END', '', 0, 1, 1)
+        return self.feed_token(eof)
+
+
+    def __copy__(self):
+        """Create a new interactive parser with a separate state.
+
+        Calls to feed_token() won't affect the old instance, and vice-versa.
+        """
+        return self.copy()
+
+    def copy(self, deepcopy_values=True):
+        return type(self)(
+            self.parser,
+            self.parser_state.copy(deepcopy_values=deepcopy_values),
+            copy(self.lexer_thread),
+        )
+
+    def __eq__(self, other):
+        if not isinstance(other, InteractiveParser):
+            return False
+
+        return self.parser_state == other.parser_state and self.lexer_thread == other.lexer_thread
+
+    def as_immutable(self):
+        """Convert to an ``ImmutableInteractiveParser``."""
+        p = copy(self)
+        return ImmutableInteractiveParser(p.parser, p.parser_state, p.lexer_thread)
+
+    def pretty(self):
+        """Print the output of ``choices()`` in a way that's easier to read."""
+        out = ["Parser choices:"]
+        for k, v in self.choices().items():
+            out.append('\t- %s -> %r' % (k, v))
+        out.append('stack size: %s' % len(self.parser_state.state_stack))
+        return '\n'.join(out)
+
+    def choices(self):
+        """Returns a dictionary of token types, matched to their action in the parser.
+
+        Only returns token types that are accepted by the current state.
+
+        Updated by ``feed_token()``.
+        """
+        return self.parser_state.parse_conf.parse_table.states[self.parser_state.position]
+
+    def accepts(self):
+        """Returns the set of possible tokens that will advance the parser into a new valid state."""
+        accepts = set()
+        conf_no_callbacks = copy(self.parser_state.parse_conf)
+        # We don't want to call callbacks here since those might have arbitrary side effects
+        # and are unnecessarily slow.
+        conf_no_callbacks.callbacks = {}
+        for t in self.choices():
+            if t.isupper(): # is terminal?
+                new_cursor = self.copy(deepcopy_values=False)
+                new_cursor.parser_state.parse_conf = conf_no_callbacks
+                try:
+                    new_cursor.feed_token(self.lexer_thread._Token(t, ''))
+                except UnexpectedToken:
+                    pass
+                else:
+                    accepts.add(t)
+        return accepts
+
+    def resume_parse(self):
+        """Resume automated parsing from the current state.
+        """
+        return self.parser.parse_from_state(self.parser_state, last_token=self.lexer_thread.state.last_token)
+
+
+
+class ImmutableInteractiveParser(InteractiveParser):
+    """Same as ``InteractiveParser``, but operations create a new instance instead
+    of changing it in-place.
+    """
+
+    result = None
+
+    def __hash__(self):
+        return hash((self.parser_state, self.lexer_thread))
+
+    def feed_token(self, token):
+        c = copy(self)
+        c.result = InteractiveParser.feed_token(c, token)
+        return c
+
+    def exhaust_lexer(self):
+        """Try to feed the rest of the lexer state into the parser.
+
+        Note that this returns a new ImmutableInteractiveParser and does not feed an '$END' Token"""
+        cursor = self.as_mutable()
+        cursor.exhaust_lexer()
+        return cursor.as_immutable()
+
+    def as_mutable(self):
+        """Convert to an ``InteractiveParser``."""
+        p = copy(self)
+        return InteractiveParser(p.parser, p.parser_state, p.lexer_thread)
+
+###}
diff --git a/server/libs/lark/parsers/lalr_parser.py b/server/libs/lark/parsers/lalr_parser.py
new file mode 100644
index 0000000..6ae2a04
--- /dev/null
+++ b/server/libs/lark/parsers/lalr_parser.py
@@ -0,0 +1,122 @@
+"""This module implements a LALR(1) Parser
+"""
+# Author: Erez Shinan (2017)
+# Email : erezshin@gmail.com
+from typing import Dict, Any, Optional
+from ..lexer import Token, LexerThread
+from ..utils import Serialize
+from ..common import ParserConf, ParserCallbacks
+
+from .lalr_analysis import LALR_Analyzer, IntParseTable, ParseTableBase
+from .lalr_interactive_parser import InteractiveParser
+from lark.exceptions import UnexpectedCharacters, UnexpectedInput, UnexpectedToken
+from .lalr_parser_state import ParserState, ParseConf
+
+###{standalone
+
+class LALR_Parser(Serialize):
+    def __init__(self, parser_conf: ParserConf, debug: bool=False, strict: bool=False):
+        analysis = LALR_Analyzer(parser_conf, debug=debug, strict=strict)
+        analysis.compute_lalr()
+        callbacks = parser_conf.callbacks
+
+        self._parse_table = analysis.parse_table
+        self.parser_conf = parser_conf
+        self.parser = _Parser(analysis.parse_table, callbacks, debug)
+
+    @classmethod
+    def deserialize(cls, data, memo, callbacks, debug=False):
+        inst = cls.__new__(cls)
+        inst._parse_table = IntParseTable.deserialize(data, memo)
+        inst.parser = _Parser(inst._parse_table, callbacks, debug)
+        return inst
+
+    def serialize(self, memo: Any = None) -> Dict[str, Any]:
+        return self._parse_table.serialize(memo)
+
+    def parse_interactive(self, lexer: LexerThread, start: str):
+        return self.parser.parse(lexer, start, start_interactive=True)
+
+    def parse(self, lexer, start, on_error=None):
+        try:
+            return self.parser.parse(lexer, start)
+        except UnexpectedInput as e:
+            if on_error is None:
+                raise
+
+            while True:
+                if isinstance(e, UnexpectedCharacters):
+                    s = e.interactive_parser.lexer_thread.state
+                    p = s.line_ctr.char_pos
+
+                if not on_error(e):
+                    raise e
+
+                if isinstance(e, UnexpectedCharacters):
+                    # If user didn't change the character position, then we should
+                    if p == s.line_ctr.char_pos:
+                        s.line_ctr.feed(s.text[p:p+1])
+
+                try:
+                    return e.interactive_parser.resume_parse()
+                except UnexpectedToken as e2:
+                    if (isinstance(e, UnexpectedToken)
+                        and e.token.type == e2.token.type == '$END'
+                        and e.interactive_parser == e2.interactive_parser):
+                        # Prevent infinite loop
+                        raise e2
+                    e = e2
+                except UnexpectedCharacters as e2:
+                    e = e2
+
+
+class _Parser:
+    parse_table: ParseTableBase
+    callbacks: ParserCallbacks
+    debug: bool
+
+    def __init__(self, parse_table: ParseTableBase, callbacks: ParserCallbacks, debug: bool=False):
+        self.parse_table = parse_table
+        self.callbacks = callbacks
+        self.debug = debug
+
+    def parse(self, lexer: LexerThread, start: str, value_stack=None, state_stack=None, start_interactive=False):
+        parse_conf = ParseConf(self.parse_table, self.callbacks, start)
+        parser_state = ParserState(parse_conf, lexer, state_stack, value_stack)
+        if start_interactive:
+            return InteractiveParser(self, parser_state, parser_state.lexer)
+        return self.parse_from_state(parser_state)
+
+
+    def parse_from_state(self, state: ParserState, last_token: Optional[Token]=None):
+        """Run the main LALR parser loop
+
+        Parameters:
+            state - the initial state. Changed in-place.
+            last_token - Used only for line information in case of an empty lexer.
+        """
+        try:
+            token = last_token
+            for token in state.lexer.lex(state):
+                assert token is not None
+                state.feed_token(token)
+
+            end_token = Token.new_borrow_pos('$END', '', token) if token else Token('$END', '', 0, 1, 1)
+            return state.feed_token(end_token, True)
+        except UnexpectedInput as e:
+            try:
+                e.interactive_parser = InteractiveParser(self, state, state.lexer)
+            except NameError:
+                pass
+            raise e
+        except Exception as e:
+            if self.debug:
+                print("")
+                print("STATE STACK DUMP")
+                print("----------------")
+                for i, s in enumerate(state.state_stack):
+                    print('%d)' % i , s)
+                print("")
+
+            raise
+###}
diff --git a/server/libs/lark/parsers/lalr_parser_state.py b/server/libs/lark/parsers/lalr_parser_state.py
new file mode 100644
index 0000000..273bc00
--- /dev/null
+++ b/server/libs/lark/parsers/lalr_parser_state.py
@@ -0,0 +1,110 @@
+from copy import deepcopy, copy
+from typing import Dict, Any, Generic, List
+from ..lexer import Token, LexerThread
+from ..common import ParserCallbacks
+
+from .lalr_analysis import Shift, ParseTableBase, StateT
+from lark.exceptions import UnexpectedToken
+
+###{standalone
+
+class ParseConf(Generic[StateT]):
+    __slots__ = 'parse_table', 'callbacks', 'start', 'start_state', 'end_state', 'states'
+
+    parse_table: ParseTableBase[StateT]
+    callbacks: ParserCallbacks
+    start: str
+
+    start_state: StateT
+    end_state: StateT
+    states: Dict[StateT, Dict[str, tuple]]
+
+    def __init__(self, parse_table: ParseTableBase[StateT], callbacks: ParserCallbacks, start: str):
+        self.parse_table = parse_table
+
+        self.start_state = self.parse_table.start_states[start]
+        self.end_state = self.parse_table.end_states[start]
+        self.states = self.parse_table.states
+
+        self.callbacks = callbacks
+        self.start = start
+
+class ParserState(Generic[StateT]):
+    __slots__ = 'parse_conf', 'lexer', 'state_stack', 'value_stack'
+
+    parse_conf: ParseConf[StateT]
+    lexer: LexerThread
+    state_stack: List[StateT]
+    value_stack: list
+
+    def __init__(self, parse_conf: ParseConf[StateT], lexer: LexerThread, state_stack=None, value_stack=None):
+        self.parse_conf = parse_conf
+        self.lexer = lexer
+        self.state_stack = state_stack or [self.parse_conf.start_state]
+        self.value_stack = value_stack or []
+
+    @property
+    def position(self) -> StateT:
+        return self.state_stack[-1]
+
+    # Necessary for match_examples() to work
+    def __eq__(self, other) -> bool:
+        if not isinstance(other, ParserState):
+            return NotImplemented
+        return len(self.state_stack) == len(other.state_stack) and self.position == other.position
+
+    def __copy__(self):
+        return self.copy()
+
+    def copy(self, deepcopy_values=True) -> 'ParserState[StateT]':
+        return type(self)(
+            self.parse_conf,
+            self.lexer, # XXX copy
+            copy(self.state_stack),
+            deepcopy(self.value_stack) if deepcopy_values else copy(self.value_stack),
+        )
+
+    def feed_token(self, token: Token, is_end=False) -> Any:
+        state_stack = self.state_stack
+        value_stack = self.value_stack
+        states = self.parse_conf.states
+        end_state = self.parse_conf.end_state
+        callbacks = self.parse_conf.callbacks
+
+        while True:
+            state = state_stack[-1]
+            try:
+                action, arg = states[state][token.type]
+            except KeyError:
+                expected = {s for s in states[state].keys() if s.isupper()}
+                raise UnexpectedToken(token, expected, state=self, interactive_parser=None)
+
+            assert arg != end_state
+
+            if action is Shift:
+                # shift once and return
+                assert not is_end
+                state_stack.append(arg)
+                value_stack.append(token if token.type not in callbacks else callbacks[token.type](token))
+                return
+            else:
+                # reduce+shift as many times as necessary
+                rule = arg
+                size = len(rule.expansion)
+                if size:
+                    s = value_stack[-size:]
+                    del state_stack[-size:]
+                    del value_stack[-size:]
+                else:
+                    s = []
+
+                value = callbacks[rule](s) if callbacks else s
+
+                _action, new_state = states[state_stack[-1]][rule.origin.name]
+                assert _action is Shift
+                state_stack.append(new_state)
+                value_stack.append(value)
+
+                if is_end and state_stack[-1] == end_state:
+                    return value_stack[-1]
+###}
diff --git a/server/libs/lark/parsers/xearley.py b/server/libs/lark/parsers/xearley.py
new file mode 100644
index 0000000..a0f43ac
--- /dev/null
+++ b/server/libs/lark/parsers/xearley.py
@@ -0,0 +1,165 @@
+"""This module implements an Earley parser with a dynamic lexer
+
+The core Earley algorithm used here is based on Elizabeth Scott's implementation, here:
+    https://www.sciencedirect.com/science/article/pii/S1571066108001497
+
+That is probably the best reference for understanding the algorithm here.
+
+The Earley parser outputs an SPPF-tree as per that document. The SPPF tree format
+is better documented here:
+    http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/
+
+Instead of running a lexer beforehand, or using a costy char-by-char method, this parser
+uses regular expressions by necessity, achieving high-performance while maintaining all of
+Earley's power in parsing any CFG.
+"""
+
+from typing import TYPE_CHECKING, Callable, Optional, List, Any
+from collections import defaultdict
+
+from ..tree import Tree
+from ..exceptions import UnexpectedCharacters
+from ..lexer import Token
+from ..grammar import Terminal
+from .earley import Parser as BaseParser
+from .earley_forest import TokenNode
+
+if TYPE_CHECKING:
+    from ..common import LexerConf, ParserConf
+
+class Parser(BaseParser):
+    def __init__(self, lexer_conf: 'LexerConf', parser_conf: 'ParserConf', term_matcher: Callable,
+                 resolve_ambiguity: bool=True, complete_lex: bool=False, debug: bool=False,
+                 tree_class: Optional[Callable[[str, List], Any]]=Tree, ordered_sets: bool=True):
+        BaseParser.__init__(self, lexer_conf, parser_conf, term_matcher, resolve_ambiguity,
+                            debug, tree_class, ordered_sets)
+        self.ignore = [Terminal(t) for t in lexer_conf.ignore]
+        self.complete_lex = complete_lex
+
+    def _parse(self, stream, columns, to_scan, start_symbol=None):
+
+        def scan(i, to_scan):
+            """The core Earley Scanner.
+
+            This is a custom implementation of the scanner that uses the
+            Lark lexer to match tokens. The scan list is built by the
+            Earley predictor, based on the previously completed tokens.
+            This ensures that at each phase of the parse we have a custom
+            lexer context, allowing for more complex ambiguities."""
+
+            node_cache = {}
+
+            # 1) Loop the expectations and ask the lexer to match.
+            # Since regexp is forward looking on the input stream, and we only
+            # want to process tokens when we hit the point in the stream at which
+            # they complete, we push all tokens into a buffer (delayed_matches), to
+            # be held possibly for a later parse step when we reach the point in the
+            # input stream at which they complete.
+            for item in self.Set(to_scan):
+                m = match(item.expect, stream, i)
+                if m:
+                    t = Token(item.expect.name, m.group(0), i, text_line, text_column)
+                    delayed_matches[m.end()].append( (item, i, t) )
+
+                    if self.complete_lex:
+                        s = m.group(0)
+                        for j in range(1, len(s)):
+                            m = match(item.expect, s[:-j])
+                            if m:
+                                t = Token(item.expect.name, m.group(0), i, text_line, text_column)
+                                delayed_matches[i+m.end()].append( (item, i, t) )
+
+                    # XXX The following 3 lines were commented out for causing a bug. See issue #768
+                    # # Remove any items that successfully matched in this pass from the to_scan buffer.
+                    # # This ensures we don't carry over tokens that already matched, if we're ignoring below.
+                    # to_scan.remove(item)
+
+            # 3) Process any ignores. This is typically used for e.g. whitespace.
+            # We carry over any unmatched items from the to_scan buffer to be matched again after
+            # the ignore. This should allow us to use ignored symbols in non-terminals to implement
+            # e.g. mandatory spacing.
+            for x in self.ignore:
+                m = match(x, stream, i)
+                if m:
+                    # Carry over any items still in the scan buffer, to past the end of the ignored items.
+                    delayed_matches[m.end()].extend([(item, i, None) for item in to_scan ])
+
+                    # If we're ignoring up to the end of the file, # carry over the start symbol if it already completed.
+                    delayed_matches[m.end()].extend([(item, i, None) for item in columns[i] if item.is_complete and item.s == start_symbol])
+
+            next_to_scan = self.Set()
+            next_set = self.Set()
+            columns.append(next_set)
+            transitives.append({})
+
+            ## 4) Process Tokens from delayed_matches.
+            # This is the core of the Earley scanner. Create an SPPF node for each Token,
+            # and create the symbol node in the SPPF tree. Advance the item that completed,
+            # and add the resulting new item to either the Earley set (for processing by the
+            # completer/predictor) or the to_scan buffer for the next parse step.
+            for item, start, token in delayed_matches[i+1]:
+                if token is not None:
+                    token.end_line = text_line
+                    token.end_column = text_column + 1
+                    token.end_pos = i + 1
+
+                    new_item = item.advance()
+                    label = (new_item.s, new_item.start, i + 1)
+                    token_node = TokenNode(token, terminals[token.type])
+                    new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
+                    new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token_node)
+                else:
+                    new_item = item
+
+                if new_item.expect in self.TERMINALS:
+                    # add (B ::= Aai+1.B, h, y) to Q'
+                    next_to_scan.add(new_item)
+                else:
+                    # add (B ::= Aa+1.B, h, y) to Ei+1
+                    next_set.add(new_item)
+
+            del delayed_matches[i+1]    # No longer needed, so unburden memory
+
+            if not next_set and not delayed_matches and not next_to_scan:
+                considered_rules = list(sorted(to_scan, key=lambda key: key.rule.origin.name))
+                raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan},
+                                           set(to_scan), state=frozenset(i.s for i in to_scan),
+                                           considered_rules=considered_rules
+                                           )
+
+            return next_to_scan
+
+
+        delayed_matches = defaultdict(list)
+        match = self.term_matcher
+        terminals = self.lexer_conf.terminals_by_name
+
+        # Cache for nodes & tokens created in a particular parse step.
+        transitives = [{}]
+
+        text_line = 1
+        text_column = 1
+
+        ## The main Earley loop.
+        # Run the Prediction/Completion cycle for any Items in the current Earley set.
+        # Completions will be added to the SPPF tree, and predictions will be recursively
+        # processed down to terminals/empty nodes to be added to the scanner for the next
+        # step.
+        i = 0
+        for token in stream:
+            self.predict_and_complete(i, to_scan, columns, transitives)
+
+            to_scan = scan(i, to_scan)
+
+            if token == '\n':
+                text_line += 1
+                text_column = 1
+            else:
+                text_column += 1
+            i += 1
+
+        self.predict_and_complete(i, to_scan, columns, transitives)
+
+        ## Column is now the final column in the parse.
+        assert i == len(columns)-1
+        return to_scan
diff --git a/server/libs/lark/py.typed b/server/libs/lark/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/server/libs/lark/reconstruct.py b/server/libs/lark/reconstruct.py
new file mode 100644
index 0000000..2d8423a
--- /dev/null
+++ b/server/libs/lark/reconstruct.py
@@ -0,0 +1,107 @@
+"""This is an experimental tool for reconstructing text from a shaped tree, based on a Lark grammar.
+"""
+
+from typing import Dict, Callable, Iterable, Optional
+
+from .lark import Lark
+from .tree import Tree, ParseTree
+from .visitors import Transformer_InPlace
+from .lexer import Token, PatternStr, TerminalDef
+from .grammar import Terminal, NonTerminal, Symbol
+
+from .tree_matcher import TreeMatcher, is_discarded_terminal
+from .utils import is_id_continue
+
+def is_iter_empty(i):
+    try:
+        _ = next(i)
+        return False
+    except StopIteration:
+        return True
+
+
+class WriteTokensTransformer(Transformer_InPlace):
+    "Inserts discarded tokens into their correct place, according to the rules of grammar"
+
+    tokens: Dict[str, TerminalDef]
+    term_subs: Dict[str, Callable[[Symbol], str]]
+
+    def __init__(self, tokens: Dict[str, TerminalDef], term_subs: Dict[str, Callable[[Symbol], str]]) -> None:
+        self.tokens = tokens
+        self.term_subs = term_subs
+
+    def __default__(self, data, children, meta):
+        if not getattr(meta, 'match_tree', False):
+            return Tree(data, children)
+
+        iter_args = iter(children)
+        to_write = []
+        for sym in meta.orig_expansion:
+            if is_discarded_terminal(sym):
+                try:
+                    v = self.term_subs[sym.name](sym)
+                except KeyError:
+                    t = self.tokens[sym.name]
+                    if not isinstance(t.pattern, PatternStr):
+                        raise NotImplementedError("Reconstructing regexps not supported yet: %s" % t)
+
+                    v = t.pattern.value
+                to_write.append(v)
+            else:
+                x = next(iter_args)
+                if isinstance(x, list):
+                    to_write += x
+                else:
+                    if isinstance(x, Token):
+                        assert Terminal(x.type) == sym, x
+                    else:
+                        assert NonTerminal(x.data) == sym, (sym, x)
+                    to_write.append(x)
+
+        assert is_iter_empty(iter_args)
+        return to_write
+
+
+class Reconstructor(TreeMatcher):
+    """
+    A Reconstructor that will, given a full parse Tree, generate source code.
+
+    Note:
+        The reconstructor cannot generate values from regexps. If you need to produce discarded
+        regexes, such as newlines, use `term_subs` and provide default values for them.
+
+    Parameters:
+        parser: a Lark instance
+        term_subs: a dictionary of [Terminal name as str] to [output text as str]
+    """
+
+    write_tokens: WriteTokensTransformer
+
+    def __init__(self, parser: Lark, term_subs: Optional[Dict[str, Callable[[Symbol], str]]]=None) -> None:
+        TreeMatcher.__init__(self, parser)
+
+        self.write_tokens = WriteTokensTransformer({t.name:t for t in self.tokens}, term_subs or {})
+
+    def _reconstruct(self, tree):
+        unreduced_tree = self.match_tree(tree, tree.data)
+
+        res = self.write_tokens.transform(unreduced_tree)
+        for item in res:
+            if isinstance(item, Tree):
+                # TODO use orig_expansion.rulename to support templates
+                yield from self._reconstruct(item)
+            else:
+                yield item
+
+    def reconstruct(self, tree: ParseTree, postproc: Optional[Callable[[Iterable[str]], Iterable[str]]]=None, insert_spaces: bool=True) -> str:
+        x = self._reconstruct(tree)
+        if postproc:
+            x = postproc(x)
+        y = []
+        prev_item = ''
+        for item in x:
+            if insert_spaces and prev_item and item and is_id_continue(prev_item[-1]) and is_id_continue(item[0]):
+                y.append(' ')
+            y.append(item)
+            prev_item = item
+        return ''.join(y)
diff --git a/server/libs/lark/tools/__init__.py b/server/libs/lark/tools/__init__.py
new file mode 100644
index 0000000..eeb40e1
--- /dev/null
+++ b/server/libs/lark/tools/__init__.py
@@ -0,0 +1,70 @@
+import sys
+from argparse import ArgumentParser, FileType
+from textwrap import indent
+from logging import DEBUG, INFO, WARN, ERROR
+from typing import Optional
+import warnings
+
+from lark import Lark, logger
+try:
+    from interegular import logger as interegular_logger
+    has_interegular = True
+except ImportError:
+    has_interegular = False
+
+lalr_argparser = ArgumentParser(add_help=False, epilog='Look at the Lark documentation for more info on the options')
+
+flags = [
+    ('d', 'debug'),
+    'keep_all_tokens',
+    'regex',
+    'propagate_positions',
+    'maybe_placeholders',
+    'use_bytes'
+]
+
+options = ['start', 'lexer']
+
+lalr_argparser.add_argument('-v', '--verbose', action='count', default=0, help="Increase Logger output level, up to three times")
+lalr_argparser.add_argument('-s', '--start', action='append', default=[])
+lalr_argparser.add_argument('-l', '--lexer', default='contextual', choices=('basic', 'contextual'))
+lalr_argparser.add_argument('-o', '--out', type=FileType('w', encoding='utf-8'), default=sys.stdout, help='the output file (default=stdout)')
+lalr_argparser.add_argument('grammar_file', type=FileType('r', encoding='utf-8'), help='A valid .lark file')
+
+for flag in flags:
+    if isinstance(flag, tuple):
+        options.append(flag[1])
+        lalr_argparser.add_argument('-' + flag[0], '--' + flag[1], action='store_true')
+    elif isinstance(flag, str):
+        options.append(flag)
+        lalr_argparser.add_argument('--' + flag, action='store_true')
+    else:
+        raise NotImplementedError("flags must only contain strings or tuples of strings")
+
+
+def build_lalr(namespace):
+    logger.setLevel((ERROR, WARN, INFO, DEBUG)[min(namespace.verbose, 3)])
+    if has_interegular:
+        interegular_logger.setLevel(logger.getEffectiveLevel())
+    if len(namespace.start) == 0:
+        namespace.start.append('start')
+    kwargs = {n: getattr(namespace, n) for n in options}
+    return Lark(namespace.grammar_file, parser='lalr', **kwargs), namespace.out
+
+
+def showwarning_as_comment(message, category, filename, lineno, file=None, line=None):
+    # Based on warnings._showwarnmsg_impl
+    text = warnings.formatwarning(message, category, filename, lineno, line)
+    text = indent(text, '# ')
+    if file is None:
+        file = sys.stderr
+        if file is None:
+            return
+    try:
+        file.write(text)
+    except OSError:
+        pass
+
+
+def make_warnings_comments():
+    warnings.showwarning = showwarning_as_comment
diff --git a/server/libs/lark/tools/nearley.py b/server/libs/lark/tools/nearley.py
new file mode 100644
index 0000000..1fc27d5
--- /dev/null
+++ b/server/libs/lark/tools/nearley.py
@@ -0,0 +1,202 @@
+"Converts Nearley grammars to Lark"
+
+import os.path
+import sys
+import codecs
+import argparse
+
+
+from lark import Lark, Transformer, v_args
+
+nearley_grammar = r"""
+    start: (ruledef|directive)+
+
+    directive: "@" NAME (STRING|NAME)
+             | "@" JS  -> js_code
+    ruledef: NAME "->" expansions
+           | NAME REGEXP "->" expansions -> macro
+    expansions: expansion ("|" expansion)*
+
+    expansion: expr+ js
+
+    ?expr: item (":" /[+*?]/)?
+
+    ?item: rule|string|regexp|null
+         | "(" expansions ")"
+
+    rule: NAME
+    string: STRING
+    regexp: REGEXP
+    null: "null"
+    JS: /{%.*?%}/s
+    js: JS?
+
+    NAME: /[a-zA-Z_$]\w*/
+    COMMENT: /#[^\n]*/
+    REGEXP: /\[.*?\]/
+
+    STRING: _STRING "i"?
+
+    %import common.ESCAPED_STRING -> _STRING
+    %import common.WS
+    %ignore WS
+    %ignore COMMENT
+
+    """
+
+nearley_grammar_parser = Lark(nearley_grammar, parser='earley', lexer='basic')
+
+def _get_rulename(name):
+    name = {'_': '_ws_maybe', '__': '_ws'}.get(name, name)
+    return 'n_' + name.replace('$', '__DOLLAR__').lower()
+
+@v_args(inline=True)
+class NearleyToLark(Transformer):
+    def __init__(self):
+        self._count = 0
+        self.extra_rules = {}
+        self.extra_rules_rev = {}
+        self.alias_js_code = {}
+
+    def _new_function(self, code):
+        name = 'alias_%d' % self._count
+        self._count += 1
+
+        self.alias_js_code[name] = code
+        return name
+
+    def _extra_rule(self, rule):
+        if rule in self.extra_rules_rev:
+            return self.extra_rules_rev[rule]
+
+        name = 'xrule_%d' % len(self.extra_rules)
+        assert name not in self.extra_rules
+        self.extra_rules[name] = rule
+        self.extra_rules_rev[rule] = name
+        return name
+
+    def rule(self, name):
+        return _get_rulename(name)
+
+    def ruledef(self, name, exps):
+        return '!%s: %s' % (_get_rulename(name), exps)
+
+    def expr(self, item, op):
+        rule = '(%s)%s' % (item, op)
+        return self._extra_rule(rule)
+
+    def regexp(self, r):
+        return '/%s/' % r
+
+    def null(self):
+        return ''
+
+    def string(self, s):
+        return self._extra_rule(s)
+
+    def expansion(self, *x):
+        x, js = x[:-1], x[-1]
+        if js.children:
+            js_code ,= js.children
+            js_code = js_code[2:-2]
+            alias = '-> ' + self._new_function(js_code)
+        else:
+            alias = ''
+        return ' '.join(x) + alias
+
+    def expansions(self, *x):
+        return '%s' % ('\n    |'.join(x))
+
+    def start(self, *rules):
+        return '\n'.join(filter(None, rules))
+
+def _nearley_to_lark(g, builtin_path, n2l, js_code, folder_path, includes):
+    rule_defs = []
+
+    tree = nearley_grammar_parser.parse(g)
+    for statement in tree.children:
+        if statement.data == 'directive':
+            directive, arg = statement.children
+            if directive in ('builtin', 'include'):
+                folder = builtin_path if directive == 'builtin' else folder_path
+                path = os.path.join(folder, arg[1:-1])
+                if path not in includes:
+                    includes.add(path)
+                    with codecs.open(path, encoding='utf8') as f:
+                        text = f.read()
+                    rule_defs += _nearley_to_lark(text, builtin_path, n2l, js_code, os.path.abspath(os.path.dirname(path)), includes)
+            else:
+                assert False, directive
+        elif statement.data == 'js_code':
+            code ,= statement.children
+            code = code[2:-2]
+            js_code.append(code)
+        elif statement.data == 'macro':
+            pass    # TODO Add support for macros!
+        elif statement.data == 'ruledef':
+            rule_defs.append(n2l.transform(statement))
+        else:
+            raise Exception("Unknown statement: %s" % statement)
+
+    return rule_defs
+
+
+def create_code_for_nearley_grammar(g, start, builtin_path, folder_path, es6=False):
+    import js2py
+
+    emit_code = []
+    def emit(x=None):
+        if x:
+            emit_code.append(x)
+        emit_code.append('\n')
+
+    js_code = ['function id(x) {return x[0];}']
+    n2l = NearleyToLark()
+    rule_defs = _nearley_to_lark(g, builtin_path, n2l, js_code, folder_path, set())
+    lark_g = '\n'.join(rule_defs)
+    lark_g += '\n'+'\n'.join('!%s: %s' % item for item in n2l.extra_rules.items())
+
+    emit('from lark import Lark, Transformer')
+    emit()
+    emit('grammar = ' + repr(lark_g))
+    emit()
+
+    for alias, code in n2l.alias_js_code.items():
+        js_code.append('%s = (%s);' % (alias, code))
+
+    if es6:
+        emit(js2py.translate_js6('\n'.join(js_code)))
+    else:
+        emit(js2py.translate_js('\n'.join(js_code)))
+    emit('class TransformNearley(Transformer):')
+    for alias in n2l.alias_js_code:
+        emit("    %s = var.get('%s').to_python()" % (alias, alias))
+    emit("    __default__ = lambda self, n, c, m: c if c else None")
+
+    emit()
+    emit('parser = Lark(grammar, start="n_%s", maybe_placeholders=False)' % start)
+    emit('def parse(text):')
+    emit('    return TransformNearley().transform(parser.parse(text))')
+
+    return ''.join(emit_code)
+
+def main(fn, start, nearley_lib, es6=False):
+    with codecs.open(fn, encoding='utf8') as f:
+        grammar = f.read()
+    return create_code_for_nearley_grammar(grammar, start, os.path.join(nearley_lib, 'builtin'), os.path.abspath(os.path.dirname(fn)), es6=es6)
+
+def get_arg_parser():
+    parser = argparse.ArgumentParser(description='Reads a Nearley grammar (with js functions), and outputs an equivalent lark parser.')
+    parser.add_argument('nearley_grammar', help='Path to the file containing the nearley grammar')
+    parser.add_argument('start_rule', help='Rule within the nearley grammar to make the base rule')
+    parser.add_argument('nearley_lib', help='Path to root directory of nearley codebase (used for including builtins)')
+    parser.add_argument('--es6', help='Enable experimental ES6 support', action='store_true')
+    return parser
+
+if __name__ == '__main__':
+    parser = get_arg_parser()
+    if len(sys.argv) == 1:
+        parser.print_help(sys.stderr)
+        sys.exit(1)
+    args = parser.parse_args()
+    print(main(fn=args.nearley_grammar, start=args.start_rule, nearley_lib=args.nearley_lib, es6=args.es6))
diff --git a/server/libs/lark/tools/serialize.py b/server/libs/lark/tools/serialize.py
new file mode 100644
index 0000000..eb28824
--- /dev/null
+++ b/server/libs/lark/tools/serialize.py
@@ -0,0 +1,32 @@
+import sys
+import json
+
+from lark.grammar import Rule
+from lark.lexer import TerminalDef
+from lark.tools import lalr_argparser, build_lalr
+
+import argparse
+
+argparser = argparse.ArgumentParser(prog='python -m lark.tools.serialize', parents=[lalr_argparser],
+                                    description="Lark Serialization Tool - Stores Lark's internal state & LALR analysis as a JSON file",
+                                    epilog='Look at the Lark documentation for more info on the options')
+
+
+def serialize(lark_inst, outfile):
+    data, memo = lark_inst.memo_serialize([TerminalDef, Rule])
+    outfile.write('{\n')
+    outfile.write('  "data": %s,\n' % json.dumps(data))
+    outfile.write('  "memo": %s\n' % json.dumps(memo))
+    outfile.write('}\n')
+
+
+def main():
+    if len(sys.argv)==1:
+        argparser.print_help(sys.stderr)
+        sys.exit(1)
+    ns = argparser.parse_args()
+    serialize(*build_lalr(ns))
+
+
+if __name__ == '__main__':
+    main()
diff --git a/server/libs/lark/tools/standalone.py b/server/libs/lark/tools/standalone.py
new file mode 100644
index 0000000..9940ccb
--- /dev/null
+++ b/server/libs/lark/tools/standalone.py
@@ -0,0 +1,196 @@
+###{standalone
+#
+#
+#   Lark Stand-alone Generator Tool
+# ----------------------------------
+# Generates a stand-alone LALR(1) parser
+#
+# Git:    https://github.com/erezsh/lark
+# Author: Erez Shinan (erezshin@gmail.com)
+#
+#
+#    >>> LICENSE
+#
+#    This tool and its generated code use a separate license from Lark,
+#    and are subject to the terms of the Mozilla Public License, v. 2.0.
+#    If a copy of the MPL was not distributed with this
+#    file, You can obtain one at https://mozilla.org/MPL/2.0/.
+#
+#    If you wish to purchase a commercial license for this tool and its
+#    generated code, you may contact me via email or otherwise.
+#
+#    If MPL2 is incompatible with your free or open-source project,
+#    contact me and we'll work it out.
+#
+#
+
+from copy import deepcopy
+from abc import ABC, abstractmethod
+from types import ModuleType
+from typing import (
+    TypeVar, Generic, Type, Tuple, List, Dict, Iterator, Collection, Callable, Optional, FrozenSet, Any,
+    Union, Iterable, IO, TYPE_CHECKING, overload, Sequence,
+    Pattern as REPattern, ClassVar, Set, Mapping
+)
+###}
+
+import sys
+import token, tokenize
+import os
+from os import path
+from collections import defaultdict
+from functools import partial
+from argparse import ArgumentParser
+
+import lark
+from lark.tools import lalr_argparser, build_lalr, make_warnings_comments
+
+
+from lark.grammar import Rule
+from lark.lexer import TerminalDef
+
+_dir = path.dirname(__file__)
+_larkdir = path.join(_dir, path.pardir)
+
+
+EXTRACT_STANDALONE_FILES = [
+    'tools/standalone.py',
+    'exceptions.py',
+    'utils.py',
+    'tree.py',
+    'visitors.py',
+    'grammar.py',
+    'lexer.py',
+    'common.py',
+    'parse_tree_builder.py',
+    'parsers/lalr_analysis.py',
+    'parsers/lalr_parser_state.py',
+    'parsers/lalr_parser.py',
+    'parsers/lalr_interactive_parser.py',
+    'parser_frontends.py',
+    'lark.py',
+    'indenter.py',
+]
+
+def extract_sections(lines):
+    section = None
+    text = []
+    sections = defaultdict(list)
+    for line in lines:
+        if line.startswith('###'):
+            if line[3] == '{':
+                section = line[4:].strip()
+            elif line[3] == '}':
+                sections[section] += text
+                section = None
+                text = []
+            else:
+                raise ValueError(line)
+        elif section:
+            text.append(line)
+
+    return {name: ''.join(text) for name, text in sections.items()}
+
+
+def strip_docstrings(line_gen):
+    """ Strip comments and docstrings from a file.
+    Based on code from: https://stackoverflow.com/questions/1769332/script-to-remove-python-comments-docstrings
+    """
+    res = []
+
+    prev_toktype = token.INDENT
+    last_lineno = -1
+    last_col = 0
+
+    tokgen = tokenize.generate_tokens(line_gen)
+    for toktype, ttext, (slineno, scol), (elineno, ecol), ltext in tokgen:
+        if slineno > last_lineno:
+            last_col = 0
+        if scol > last_col:
+            res.append(" " * (scol - last_col))
+        if toktype == token.STRING and prev_toktype == token.INDENT:
+            # Docstring
+            res.append("#--")
+        elif toktype == tokenize.COMMENT:
+            # Comment
+            res.append("##\n")
+        else:
+            res.append(ttext)
+        prev_toktype = toktype
+        last_col = ecol
+        last_lineno = elineno
+
+    return ''.join(res)
+
+
+def gen_standalone(lark_inst, output=None, out=sys.stdout, compress=False):
+    if output is None:
+        output = partial(print, file=out)
+
+    import pickle, zlib, base64
+    def compressed_output(obj):
+        s = pickle.dumps(obj, pickle.HIGHEST_PROTOCOL)
+        c = zlib.compress(s)
+        output(repr(base64.b64encode(c)))
+
+    def output_decompress(name):
+        output('%(name)s = pickle.loads(zlib.decompress(base64.b64decode(%(name)s)))' % locals())
+
+    output('# The file was automatically generated by Lark v%s' % lark.__version__)
+    output('__version__ = "%s"' % lark.__version__)
+    output()
+
+    for i, pyfile in enumerate(EXTRACT_STANDALONE_FILES):
+        with open(os.path.join(_larkdir, pyfile)) as f:
+            code = extract_sections(f)['standalone']
+            if i:   # if not this file
+                code = strip_docstrings(partial(next, iter(code.splitlines(True))))
+            output(code)
+
+    data, m = lark_inst.memo_serialize([TerminalDef, Rule])
+    output('import pickle, zlib, base64')
+    if compress:
+        output('DATA = (')
+        compressed_output(data)
+        output(')')
+        output_decompress('DATA')
+        output('MEMO = (')
+        compressed_output(m)
+        output(')')
+        output_decompress('MEMO')
+    else:
+        output('DATA = (')
+        output(data)
+        output(')')
+        output('MEMO = (')
+        output(m)
+        output(')')
+
+
+    output('Shift = 0')
+    output('Reduce = 1')
+    output("def Lark_StandAlone(**kwargs):")
+    output("  return Lark._load_from_dict(DATA, MEMO, **kwargs)")
+
+
+
+
+def main():
+    make_warnings_comments()
+    parser = ArgumentParser(prog="prog='python -m lark.tools.standalone'", description="Lark Stand-alone Generator Tool",
+                            parents=[lalr_argparser], epilog='Look at the Lark documentation for more info on the options')
+    parser.add_argument('-c', '--compress', action='store_true', default=0, help="Enable compression")
+    if len(sys.argv) == 1:
+        parser.print_help(sys.stderr)
+        sys.exit(1)
+    ns = parser.parse_args()
+
+    lark_inst, out = build_lalr(ns)
+    gen_standalone(lark_inst, out=out, compress=ns.compress)
+
+    ns.out.close()
+    ns.grammar_file.close()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/server/libs/lark/tree.py b/server/libs/lark/tree.py
new file mode 100644
index 0000000..76f8738
--- /dev/null
+++ b/server/libs/lark/tree.py
@@ -0,0 +1,267 @@
+import sys
+from copy import deepcopy
+
+from typing import List, Callable, Iterator, Union, Optional, Generic, TypeVar, TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from .lexer import TerminalDef, Token
+    try:
+        import rich
+    except ImportError:
+        pass
+    from typing import Literal
+
+###{standalone
+
+class Meta:
+
+    empty: bool
+    line: int
+    column: int
+    start_pos: int
+    end_line: int
+    end_column: int
+    end_pos: int
+    orig_expansion: 'List[TerminalDef]'
+    match_tree: bool
+
+    def __init__(self):
+        self.empty = True
+
+
+_Leaf_T = TypeVar("_Leaf_T")
+Branch = Union[_Leaf_T, 'Tree[_Leaf_T]']
+
+
+class Tree(Generic[_Leaf_T]):
+    """The main tree class.
+
+    Creates a new tree, and stores "data" and "children" in attributes of the same name.
+    Trees can be hashed and compared.
+
+    Parameters:
+        data: The name of the rule or alias
+        children: List of matched sub-rules and terminals
+        meta: Line & Column numbers (if ``propagate_positions`` is enabled).
+            meta attributes: (line, column, end_line, end_column, start_pos, end_pos,
+                              container_line, container_column, container_end_line, container_end_column)
+            container_* attributes consider all symbols, including those that have been inlined in the tree.
+            For example, in the rule 'a: _A B _C', the regular attributes will mark the start and end of B,
+            but the container_* attributes will also include _A and _C in the range. However, rules that
+            contain 'a' will consider it in full, including _A and _C for all attributes.
+    """
+
+    data: str
+    children: 'List[Branch[_Leaf_T]]'
+
+    def __init__(self, data: str, children: 'List[Branch[_Leaf_T]]', meta: Optional[Meta]=None) -> None:
+        self.data = data
+        self.children = children
+        self._meta = meta
+
+    @property
+    def meta(self) -> Meta:
+        if self._meta is None:
+            self._meta = Meta()
+        return self._meta
+
+    def __repr__(self):
+        return 'Tree(%r, %r)' % (self.data, self.children)
+
+    def _pretty_label(self):
+        return self.data
+
+    def _pretty(self, level, indent_str):
+        yield f'{indent_str*level}{self._pretty_label()}'
+        if len(self.children) == 1 and not isinstance(self.children[0], Tree):
+            yield f'\t{self.children[0]}\n'
+        else:
+            yield '\n'
+            for n in self.children:
+                if isinstance(n, Tree):
+                    yield from n._pretty(level+1, indent_str)
+                else:
+                    yield f'{indent_str*(level+1)}{n}\n'
+
+    def pretty(self, indent_str: str='  ') -> str:
+        """Returns an indented string representation of the tree.
+
+        Great for debugging.
+        """
+        return ''.join(self._pretty(0, indent_str))
+
+    def __rich__(self, parent:Optional['rich.tree.Tree']=None) -> 'rich.tree.Tree':
+        """Returns a tree widget for the 'rich' library.
+
+        Example:
+            ::
+                from rich import print
+                from lark import Tree
+
+                tree = Tree('root', ['node1', 'node2'])
+                print(tree)
+        """
+        return self._rich(parent)
+
+    def _rich(self, parent):
+        if parent:
+            tree = parent.add(f'[bold]{self.data}[/bold]')
+        else:
+            import rich.tree
+            tree = rich.tree.Tree(self.data)
+
+        for c in self.children:
+            if isinstance(c, Tree):
+                c._rich(tree)
+            else:
+                tree.add(f'[green]{c}[/green]')
+
+        return tree
+
+    def __eq__(self, other):
+        try:
+            return self.data == other.data and self.children == other.children
+        except AttributeError:
+            return False
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __hash__(self) -> int:
+        return hash((self.data, tuple(self.children)))
+
+    def iter_subtrees(self) -> 'Iterator[Tree[_Leaf_T]]':
+        """Depth-first iteration.
+
+        Iterates over all the subtrees, never returning to the same node twice (Lark's parse-tree is actually a DAG).
+        """
+        queue = [self]
+        subtrees = dict()
+        for subtree in queue:
+            subtrees[id(subtree)] = subtree
+            queue += [c for c in reversed(subtree.children)
+                      if isinstance(c, Tree) and id(c) not in subtrees]
+
+        del queue
+        return reversed(list(subtrees.values()))
+
+    def iter_subtrees_topdown(self):
+        """Breadth-first iteration.
+
+        Iterates over all the subtrees, return nodes in order like pretty() does.
+        """
+        stack = [self]
+        stack_append = stack.append
+        stack_pop = stack.pop
+        while stack:
+            node = stack_pop()
+            if not isinstance(node, Tree):
+                continue
+            yield node
+            for child in reversed(node.children):
+                stack_append(child)
+
+    def find_pred(self, pred: 'Callable[[Tree[_Leaf_T]], bool]') -> 'Iterator[Tree[_Leaf_T]]':
+        """Returns all nodes of the tree that evaluate pred(node) as true."""
+        return filter(pred, self.iter_subtrees())
+
+    def find_data(self, data: str) -> 'Iterator[Tree[_Leaf_T]]':
+        """Returns all nodes of the tree whose data equals the given data."""
+        return self.find_pred(lambda t: t.data == data)
+
+###}
+
+    def expand_kids_by_data(self, *data_values):
+        """Expand (inline) children with any of the given data values. Returns True if anything changed"""
+        changed = False
+        for i in range(len(self.children)-1, -1, -1):
+            child = self.children[i]
+            if isinstance(child, Tree) and child.data in data_values:
+                self.children[i:i+1] = child.children
+                changed = True
+        return changed
+
+
+    def scan_values(self, pred: 'Callable[[Branch[_Leaf_T]], bool]') -> Iterator[_Leaf_T]:
+        """Return all values in the tree that evaluate pred(value) as true.
+
+        This can be used to find all the tokens in the tree.
+
+        Example:
+            >>> all_tokens = tree.scan_values(lambda v: isinstance(v, Token))
+        """
+        for c in self.children:
+            if isinstance(c, Tree):
+                for t in c.scan_values(pred):
+                    yield t
+            else:
+                if pred(c):
+                    yield c
+
+    def __deepcopy__(self, memo):
+        return type(self)(self.data, deepcopy(self.children, memo), meta=self._meta)
+
+    def copy(self) -> 'Tree[_Leaf_T]':
+        return type(self)(self.data, self.children)
+
+    def set(self, data: str, children: 'List[Branch[_Leaf_T]]') -> None:
+        self.data = data
+        self.children = children
+
+
+ParseTree = Tree['Token']
+
+
+class SlottedTree(Tree):
+    __slots__ = 'data', 'children', 'rule', '_meta'
+
+
+def pydot__tree_to_png(tree: Tree, filename: str, rankdir: 'Literal["TB", "LR", "BT", "RL"]'="LR", **kwargs) -> None:
+    graph = pydot__tree_to_graph(tree, rankdir, **kwargs)
+    graph.write_png(filename)
+
+
+def pydot__tree_to_dot(tree: Tree, filename, rankdir="LR", **kwargs):
+    graph = pydot__tree_to_graph(tree, rankdir, **kwargs)
+    graph.write(filename)
+
+
+def pydot__tree_to_graph(tree: Tree, rankdir="LR", **kwargs):
+    """Creates a colorful image that represents the tree (data+children, without meta)
+
+    Possible values for `rankdir` are "TB", "LR", "BT", "RL", corresponding to
+    directed graphs drawn from top to bottom, from left to right, from bottom to
+    top, and from right to left, respectively.
+
+    `kwargs` can be any graph attribute (e. g. `dpi=200`). For a list of
+    possible attributes, see https://www.graphviz.org/doc/info/attrs.html.
+    """
+
+    import pydot  # type: ignore[import-not-found]
+    graph = pydot.Dot(graph_type='digraph', rankdir=rankdir, **kwargs)
+
+    i = [0]
+
+    def new_leaf(leaf):
+        node = pydot.Node(i[0], label=repr(leaf))
+        i[0] += 1
+        graph.add_node(node)
+        return node
+
+    def _to_pydot(subtree):
+        color = hash(subtree.data) & 0xffffff
+        color |= 0x808080
+
+        subnodes = [_to_pydot(child) if isinstance(child, Tree) else new_leaf(child)
+                    for child in subtree.children]
+        node = pydot.Node(i[0], style="filled", fillcolor="#%x" % color, label=subtree.data)
+        i[0] += 1
+        graph.add_node(node)
+
+        for subnode in subnodes:
+            graph.add_edge(pydot.Edge(node, subnode))
+
+        return node
+
+    _to_pydot(tree)
+    return graph
diff --git a/server/libs/lark/tree_matcher.py b/server/libs/lark/tree_matcher.py
new file mode 100644
index 0000000..0f42652
--- /dev/null
+++ b/server/libs/lark/tree_matcher.py
@@ -0,0 +1,186 @@
+"""Tree matcher based on Lark grammar"""
+
+import re
+from collections import defaultdict
+
+from . import Tree, Token
+from .common import ParserConf
+from .parsers import earley
+from .grammar import Rule, Terminal, NonTerminal
+
+
+def is_discarded_terminal(t):
+    return t.is_term and t.filter_out
+
+
+class _MakeTreeMatch:
+    def __init__(self, name, expansion):
+        self.name = name
+        self.expansion = expansion
+
+    def __call__(self, args):
+        t = Tree(self.name, args)
+        t.meta.match_tree = True
+        t.meta.orig_expansion = self.expansion
+        return t
+
+
+def _best_from_group(seq, group_key, cmp_key):
+    d = {}
+    for item in seq:
+        key = group_key(item)
+        if key in d:
+            v1 = cmp_key(item)
+            v2 = cmp_key(d[key])
+            if v2 > v1:
+                d[key] = item
+        else:
+            d[key] = item
+    return list(d.values())
+
+
+def _best_rules_from_group(rules):
+    rules = _best_from_group(rules, lambda r: r, lambda r: -len(r.expansion))
+    rules.sort(key=lambda r: len(r.expansion))
+    return rules
+
+
+def _match(term, token):
+    if isinstance(token, Tree):
+        name, _args = parse_rulename(term.name)
+        return token.data == name
+    elif isinstance(token, Token):
+        return term == Terminal(token.type)
+    assert False, (term, token)
+
+
+def make_recons_rule(origin, expansion, old_expansion):
+    return Rule(origin, expansion, alias=_MakeTreeMatch(origin.name, old_expansion))
+
+
+def make_recons_rule_to_term(origin, term):
+    return make_recons_rule(origin, [Terminal(term.name)], [term])
+
+
+def parse_rulename(s):
+    "Parse rule names that may contain a template syntax (like rule{a, b, ...})"
+    name, args_str = re.match(r'(\w+)(?:{(.+)})?', s).groups()
+    args = args_str and [a.strip() for a in args_str.split(',')]
+    return name, args
+
+
+
+class ChildrenLexer:
+    def __init__(self, children):
+        self.children = children
+
+    def lex(self, parser_state):
+        return self.children
+
+class TreeMatcher:
+    """Match the elements of a tree node, based on an ontology
+    provided by a Lark grammar.
+
+    Supports templates and inlined rules (`rule{a, b,..}` and `_rule`)
+
+    Initialize with an instance of Lark.
+    """
+
+    def __init__(self, parser):
+        # XXX TODO calling compile twice returns different results!
+        assert not parser.options.maybe_placeholders
+        # XXX TODO: we just ignore the potential existence of a postlexer
+        self.tokens, rules, _extra = parser.grammar.compile(parser.options.start, set())
+
+        self.rules_for_root = defaultdict(list)
+
+        self.rules = list(self._build_recons_rules(rules))
+        self.rules.reverse()
+
+        # Choose the best rule from each group of {rule => [rule.alias]}, since we only really need one derivation.
+        self.rules = _best_rules_from_group(self.rules)
+
+        self.parser = parser
+        self._parser_cache = {}
+
+    def _build_recons_rules(self, rules):
+        "Convert tree-parsing/construction rules to tree-matching rules"
+        expand1s = {r.origin for r in rules if r.options.expand1}
+
+        aliases = defaultdict(list)
+        for r in rules:
+            if r.alias:
+                aliases[r.origin].append(r.alias)
+
+        rule_names = {r.origin for r in rules}
+        nonterminals = {sym for sym in rule_names
+                        if sym.name.startswith('_') or sym in expand1s or sym in aliases}
+
+        seen = set()
+        for r in rules:
+            recons_exp = [sym if sym in nonterminals else Terminal(sym.name)
+                          for sym in r.expansion if not is_discarded_terminal(sym)]
+
+            # Skip self-recursive constructs
+            if recons_exp == [r.origin] and r.alias is None:
+                continue
+
+            sym = NonTerminal(r.alias) if r.alias else r.origin
+            rule = make_recons_rule(sym, recons_exp, r.expansion)
+
+            if sym in expand1s and len(recons_exp) != 1:
+                self.rules_for_root[sym.name].append(rule)
+
+                if sym.name not in seen:
+                    yield make_recons_rule_to_term(sym, sym)
+                    seen.add(sym.name)
+            else:
+                if sym.name.startswith('_') or sym in expand1s:
+                    yield rule
+                else:
+                    self.rules_for_root[sym.name].append(rule)
+
+        for origin, rule_aliases in aliases.items():
+            for alias in rule_aliases:
+                yield make_recons_rule_to_term(origin, NonTerminal(alias))
+            yield make_recons_rule_to_term(origin, origin)
+
+    def match_tree(self, tree, rulename):
+        """Match the elements of `tree` to the symbols of rule `rulename`.
+
+        Parameters:
+            tree (Tree): the tree node to match
+            rulename (str): The expected full rule name (including template args)
+
+        Returns:
+            Tree: an unreduced tree that matches `rulename`
+
+        Raises:
+            UnexpectedToken: If no match was found.
+
+        Note:
+            It's the callers' responsibility match the tree recursively.
+        """
+        if rulename:
+            # validate
+            name, _args = parse_rulename(rulename)
+            assert tree.data == name
+        else:
+            rulename = tree.data
+
+        # TODO: ambiguity?
+        try:
+            parser = self._parser_cache[rulename]
+        except KeyError:
+            rules = self.rules + _best_rules_from_group(self.rules_for_root[rulename])
+
+            # TODO pass callbacks through dict, instead of alias?
+            callbacks = {rule: rule.alias for rule in rules}
+            conf = ParserConf(rules, callbacks, [rulename])
+            parser = earley.Parser(self.parser.lexer_conf, conf, _match, resolve_ambiguity=True)
+            self._parser_cache[rulename] = parser
+
+        # find a full derivation
+        unreduced_tree = parser.parse(ChildrenLexer(tree.children), rulename)
+        assert unreduced_tree.data == rulename
+        return unreduced_tree
diff --git a/server/libs/lark/tree_templates.py b/server/libs/lark/tree_templates.py
new file mode 100644
index 0000000..6ec7323
--- /dev/null
+++ b/server/libs/lark/tree_templates.py
@@ -0,0 +1,180 @@
+"""This module defines utilities for matching and translation tree templates.
+
+A tree templates is a tree that contains nodes that are template variables.
+
+"""
+
+from typing import Union, Optional, Mapping, Dict, Tuple, Iterator
+
+from lark import Tree, Transformer
+from lark.exceptions import MissingVariableError
+
+Branch = Union[Tree[str], str]
+TreeOrCode = Union[Tree[str], str]
+MatchResult = Dict[str, Tree]
+_TEMPLATE_MARKER = '$'
+
+
+class TemplateConf:
+    """Template Configuration
+
+    Allows customization for different uses of Template
+
+    parse() must return a Tree instance.
+    """
+
+    def __init__(self, parse=None):
+        self._parse = parse
+
+    def test_var(self, var: Union[Tree[str], str]) -> Optional[str]:
+        """Given a tree node, if it is a template variable return its name. Otherwise, return None.
+
+        This method may be overridden for customization
+
+        Parameters:
+            var: Tree | str - The tree node to test
+
+        """
+        if isinstance(var, str):
+            return _get_template_name(var)
+
+        if (
+            isinstance(var, Tree)
+            and var.data == "var"
+            and len(var.children) > 0
+            and isinstance(var.children[0], str)
+        ):
+            return _get_template_name(var.children[0])
+
+        return None
+
+    def _get_tree(self, template: TreeOrCode) -> Tree[str]:
+        if isinstance(template, str):
+            assert self._parse
+            template = self._parse(template)
+
+        if not isinstance(template, Tree):
+            raise TypeError("template parser must return a Tree instance")
+
+        return template
+
+    def __call__(self, template: Tree[str]) -> 'Template':
+        return Template(template, conf=self)
+
+    def _match_tree_template(self, template: TreeOrCode, tree: Branch) -> Optional[MatchResult]:
+        """Returns dict of {var: match} if found a match, else None
+        """
+        template_var = self.test_var(template)
+        if template_var:
+            if not isinstance(tree, Tree):
+                raise TypeError(f"Template variables can only match Tree instances. Not {tree!r}")
+            return {template_var: tree}
+
+        if isinstance(template, str):
+            if template == tree:
+                return {}
+            return None
+
+        assert isinstance(template, Tree) and isinstance(tree, Tree), f"template={template} tree={tree}"
+
+        if template.data == tree.data and len(template.children) == len(tree.children):
+            res = {}
+            for t1, t2 in zip(template.children, tree.children):
+                matches = self._match_tree_template(t1, t2)
+                if matches is None:
+                    return None
+
+                res.update(matches)
+
+            return res
+
+        return None
+
+
+class _ReplaceVars(Transformer[str, Tree[str]]):
+    def __init__(self, conf: TemplateConf, vars: Mapping[str, Tree[str]]) -> None:
+        super().__init__()
+        self._conf = conf
+        self._vars = vars
+
+    def __default__(self, data, children, meta) -> Tree[str]:
+        tree = super().__default__(data, children, meta)
+
+        var = self._conf.test_var(tree)
+        if var:
+            try:
+                return self._vars[var]
+            except KeyError:
+                raise MissingVariableError(f"No mapping for template variable ({var})")
+        return tree
+
+
+class Template:
+    """Represents a tree template, tied to a specific configuration
+
+    A tree template is a tree that contains nodes that are template variables.
+    Those variables will match any tree.
+    (future versions may support annotations on the variables, to allow more complex templates)
+    """
+
+    def __init__(self, tree: Tree[str], conf: TemplateConf = TemplateConf()):
+        self.conf = conf
+        self.tree = conf._get_tree(tree)
+
+    def match(self, tree: TreeOrCode) -> Optional[MatchResult]:
+        """Match a tree template to a tree.
+
+        A tree template without variables will only match ``tree`` if it is equal to the template.
+
+        Parameters:
+            tree (Tree): The tree to match to the template
+
+        Returns:
+            Optional[Dict[str, Tree]]: If match is found, returns a dictionary mapping
+                template variable names to their matching tree nodes.
+                If no match was found, returns None.
+        """
+        tree = self.conf._get_tree(tree)
+        return self.conf._match_tree_template(self.tree, tree)
+
+    def search(self, tree: TreeOrCode) -> Iterator[Tuple[Tree[str], MatchResult]]:
+        """Search for all occurrences of the tree template inside ``tree``.
+        """
+        tree = self.conf._get_tree(tree)
+        for subtree in tree.iter_subtrees():
+            res = self.match(subtree)
+            if res:
+                yield subtree, res
+
+    def apply_vars(self, vars: Mapping[str, Tree[str]]) -> Tree[str]:
+        """Apply vars to the template tree
+        """
+        return _ReplaceVars(self.conf, vars).transform(self.tree)
+
+
+def translate(t1: Template, t2: Template, tree: TreeOrCode):
+    """Search tree and translate each occurrence of t1 into t2.
+    """
+    tree = t1.conf._get_tree(tree)      # ensure it's a tree, parse if necessary and possible
+    for subtree, vars in t1.search(tree):
+        res = t2.apply_vars(vars)
+        subtree.set(res.data, res.children)
+    return tree
+
+
+class TemplateTranslator:
+    """Utility class for translating a collection of patterns
+    """
+
+    def __init__(self, translations: Mapping[Template, Template]):
+        assert all(isinstance(k, Template) and isinstance(v, Template) for k, v in translations.items())
+        self.translations = translations
+
+    def translate(self, tree: Tree[str]):
+        for k, v in self.translations.items():
+            tree = translate(k, v, tree)
+        return tree
+
+
+def _get_template_name(value: str) -> Optional[str]:
+    return value.lstrip(_TEMPLATE_MARKER) if value.startswith(_TEMPLATE_MARKER) else None
diff --git a/server/libs/lark/utils.py b/server/libs/lark/utils.py
new file mode 100644
index 0000000..3767a66
--- /dev/null
+++ b/server/libs/lark/utils.py
@@ -0,0 +1,346 @@
+import unicodedata
+import os
+from itertools import product
+from collections import deque
+from typing import Callable, Iterator, List, Optional, Tuple, Type, TypeVar, Union, Dict, Any, Sequence, Iterable, AbstractSet
+
+###{standalone
+import sys, re
+import logging
+
+logger: logging.Logger = logging.getLogger("lark")
+logger.addHandler(logging.StreamHandler())
+# Set to highest level, since we have some warnings amongst the code
+# By default, we should not output any log messages
+logger.setLevel(logging.CRITICAL)
+
+
+NO_VALUE = object()
+
+T = TypeVar("T")
+
+
+def classify(seq: Iterable, key: Optional[Callable] = None, value: Optional[Callable] = None) -> Dict:
+    d: Dict[Any, Any] = {}
+    for item in seq:
+        k = key(item) if (key is not None) else item
+        v = value(item) if (value is not None) else item
+        try:
+            d[k].append(v)
+        except KeyError:
+            d[k] = [v]
+    return d
+
+
+def _deserialize(data: Any, namespace: Dict[str, Any], memo: Dict) -> Any:
+    if isinstance(data, dict):
+        if '__type__' in data:  # Object
+            class_ = namespace[data['__type__']]
+            return class_.deserialize(data, memo)
+        elif '@' in data:
+            return memo[data['@']]
+        return {key:_deserialize(value, namespace, memo) for key, value in data.items()}
+    elif isinstance(data, list):
+        return [_deserialize(value, namespace, memo) for value in data]
+    return data
+
+
+_T = TypeVar("_T", bound="Serialize")
+
+class Serialize:
+    """Safe-ish serialization interface that doesn't rely on Pickle
+
+    Attributes:
+        __serialize_fields__ (List[str]): Fields (aka attributes) to serialize.
+        __serialize_namespace__ (list): List of classes that deserialization is allowed to instantiate.
+                                        Should include all field types that aren't builtin types.
+    """
+
+    def memo_serialize(self, types_to_memoize: List) -> Any:
+        memo = SerializeMemoizer(types_to_memoize)
+        return self.serialize(memo), memo.serialize()
+
+    def serialize(self, memo = None) -> Dict[str, Any]:
+        if memo and memo.in_types(self):
+            return {'@': memo.memoized.get(self)}
+
+        fields = getattr(self, '__serialize_fields__')
+        res = {f: _serialize(getattr(self, f), memo) for f in fields}
+        res['__type__'] = type(self).__name__
+        if hasattr(self, '_serialize'):
+            self._serialize(res, memo)
+        return res
+
+    @classmethod
+    def deserialize(cls: Type[_T], data: Dict[str, Any], memo: Dict[int, Any]) -> _T:
+        namespace = getattr(cls, '__serialize_namespace__', [])
+        namespace = {c.__name__:c for c in namespace}
+
+        fields = getattr(cls, '__serialize_fields__')
+
+        if '@' in data:
+            return memo[data['@']]
+
+        inst = cls.__new__(cls)
+        for f in fields:
+            try:
+                setattr(inst, f, _deserialize(data[f], namespace, memo))
+            except KeyError as e:
+                raise KeyError("Cannot find key for class", cls, e)
+
+        if hasattr(inst, '_deserialize'):
+            inst._deserialize()
+
+        return inst
+
+
+class SerializeMemoizer(Serialize):
+    "A version of serialize that memoizes objects to reduce space"
+
+    __serialize_fields__ = 'memoized',
+
+    def __init__(self, types_to_memoize: List) -> None:
+        self.types_to_memoize = tuple(types_to_memoize)
+        self.memoized = Enumerator()
+
+    def in_types(self, value: Serialize) -> bool:
+        return isinstance(value, self.types_to_memoize)
+
+    def serialize(self) -> Dict[int, Any]:  # type: ignore[override]
+        return _serialize(self.memoized.reversed(), None)
+
+    @classmethod
+    def deserialize(cls, data: Dict[int, Any], namespace: Dict[str, Any], memo: Dict[Any, Any]) -> Dict[int, Any]:  # type: ignore[override]
+        return _deserialize(data, namespace, memo)
+
+
+try:
+    import regex
+    _has_regex = True
+except ImportError:
+    _has_regex = False
+
+if sys.version_info >= (3, 11):
+    import re._parser as sre_parse
+    import re._constants as sre_constants
+else:
+    import sre_parse
+    import sre_constants
+
+categ_pattern = re.compile(r'\\p{[A-Za-z_]+}')
+
+def get_regexp_width(expr: str) -> Union[Tuple[int, int], List[int]]:
+    if _has_regex:
+        # Since `sre_parse` cannot deal with Unicode categories of the form `\p{Mn}`, we replace these with
+        # a simple letter, which makes no difference as we are only trying to get the possible lengths of the regex
+        # match here below.
+        regexp_final = re.sub(categ_pattern, 'A', expr)
+    else:
+        if re.search(categ_pattern, expr):
+            raise ImportError('`regex` module must be installed in order to use Unicode categories.', expr)
+        regexp_final = expr
+    try:
+        # Fixed in next version (past 0.960) of typeshed
+        return [int(x) for x in sre_parse.parse(regexp_final).getwidth()]
+    except sre_constants.error:
+        if not _has_regex:
+            raise ValueError(expr)
+        else:
+            # sre_parse does not support the new features in regex. To not completely fail in that case,
+            # we manually test for the most important info (whether the empty string is matched)
+            c = regex.compile(regexp_final)
+            # Python 3.11.7 introducded sre_parse.MAXWIDTH that is used instead of MAXREPEAT
+            # See lark-parser/lark#1376 and python/cpython#109859
+            MAXWIDTH = getattr(sre_parse, "MAXWIDTH", sre_constants.MAXREPEAT)
+            if c.match('') is None:
+                # MAXREPEAT is a none pickable subclass of int, therefore needs to be converted to enable caching
+                return 1, int(MAXWIDTH)
+            else:
+                return 0, int(MAXWIDTH)
+
+###}
+
+
+_ID_START =    'Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Mn', 'Mc', 'Pc'
+_ID_CONTINUE = _ID_START + ('Nd', 'Nl',)
+
+def _test_unicode_category(s: str, categories: Sequence[str]) -> bool:
+    if len(s) != 1:
+        return all(_test_unicode_category(char, categories) for char in s)
+    return s == '_' or unicodedata.category(s) in categories
+
+def is_id_continue(s: str) -> bool:
+    """
+    Checks if all characters in `s` are alphanumeric characters (Unicode standard, so diacritics, indian vowels, non-latin
+    numbers, etc. all pass). Synonymous with a Python `ID_CONTINUE` identifier. See PEP 3131 for details.
+    """
+    return _test_unicode_category(s, _ID_CONTINUE)
+
+def is_id_start(s: str) -> bool:
+    """
+    Checks if all characters in `s` are alphabetic characters (Unicode standard, so diacritics, indian vowels, non-latin
+    numbers, etc. all pass). Synonymous with a Python `ID_START` identifier. See PEP 3131 for details.
+    """
+    return _test_unicode_category(s, _ID_START)
+
+
+def dedup_list(l: Sequence[T]) -> List[T]:
+    """Given a list (l) will removing duplicates from the list,
+       preserving the original order of the list. Assumes that
+       the list entries are hashable."""
+    return list(dict.fromkeys(l))
+
+
+class Enumerator(Serialize):
+    def __init__(self) -> None:
+        self.enums: Dict[Any, int] = {}
+
+    def get(self, item) -> int:
+        if item not in self.enums:
+            self.enums[item] = len(self.enums)
+        return self.enums[item]
+
+    def __len__(self):
+        return len(self.enums)
+
+    def reversed(self) -> Dict[int, Any]:
+        r = {v: k for k, v in self.enums.items()}
+        assert len(r) == len(self.enums)
+        return r
+
+
+
+def combine_alternatives(lists):
+    """
+    Accepts a list of alternatives, and enumerates all their possible concatenations.
+
+    Examples:
+        >>> combine_alternatives([range(2), [4,5]])
+        [[0, 4], [0, 5], [1, 4], [1, 5]]
+
+        >>> combine_alternatives(["abc", "xy", '$'])
+        [['a', 'x', '$'], ['a', 'y', '$'], ['b', 'x', '$'], ['b', 'y', '$'], ['c', 'x', '$'], ['c', 'y', '$']]
+
+        >>> combine_alternatives([])
+        [[]]
+    """
+    if not lists:
+        return [[]]
+    assert all(l for l in lists), lists
+    return list(product(*lists))
+
+try:
+    import atomicwrites
+    _has_atomicwrites = True
+except ImportError:
+    _has_atomicwrites = False
+
+class FS:
+    exists = staticmethod(os.path.exists)
+
+    @staticmethod
+    def open(name, mode="r", **kwargs):
+        if _has_atomicwrites and "w" in mode:
+            return atomicwrites.atomic_write(name, mode=mode, overwrite=True, **kwargs)
+        else:
+            return open(name, mode, **kwargs)
+
+
+class fzset(frozenset):
+    def __repr__(self):
+        return '{%s}' % ', '.join(map(repr, self))
+
+
+def classify_bool(seq: Iterable, pred: Callable) -> Any:
+    false_elems = []
+    true_elems = [elem for elem in seq if pred(elem) or false_elems.append(elem)]  # type: ignore[func-returns-value]
+    return true_elems, false_elems
+
+
+def bfs(initial: Iterable, expand: Callable) -> Iterator:
+    open_q = deque(list(initial))
+    visited = set(open_q)
+    while open_q:
+        node = open_q.popleft()
+        yield node
+        for next_node in expand(node):
+            if next_node not in visited:
+                visited.add(next_node)
+                open_q.append(next_node)
+
+def bfs_all_unique(initial, expand):
+    "bfs, but doesn't keep track of visited (aka seen), because there can be no repetitions"
+    open_q = deque(list(initial))
+    while open_q:
+        node = open_q.popleft()
+        yield node
+        open_q += expand(node)
+
+
+def _serialize(value: Any, memo: Optional[SerializeMemoizer]) -> Any:
+    if isinstance(value, Serialize):
+        return value.serialize(memo)
+    elif isinstance(value, list):
+        return [_serialize(elem, memo) for elem in value]
+    elif isinstance(value, frozenset):
+        return list(value)  # TODO reversible?
+    elif isinstance(value, dict):
+        return {key:_serialize(elem, memo) for key, elem in value.items()}
+    # assert value is None or isinstance(value, (int, float, str, tuple)), value
+    return value
+
+
+
+
+def small_factors(n: int, max_factor: int) -> List[Tuple[int, int]]:
+    """
+    Splits n up into smaller factors and summands <= max_factor.
+    Returns a list of [(a, b), ...]
+    so that the following code returns n:
+
+    n = 1
+    for a, b in values:
+        n = n * a + b
+
+    Currently, we also keep a + b <= max_factor, but that might change
+    """
+    assert n >= 0
+    assert max_factor > 2
+    if n <= max_factor:
+        return [(n, 0)]
+
+    for a in range(max_factor, 1, -1):
+        r, b = divmod(n, a)
+        if a + b <= max_factor:
+            return small_factors(r, max_factor) + [(a, b)]
+    assert False, "Failed to factorize %s" % n
+
+
+class OrderedSet(AbstractSet[T]):
+    """A minimal OrderedSet implementation, using a dictionary.
+
+    (relies on the dictionary being ordered)
+    """
+    def __init__(self, items: Iterable[T] =()):
+        self.d = dict.fromkeys(items)
+
+    def __contains__(self, item: Any) -> bool:
+        return item in self.d
+
+    def add(self, item: T):
+        self.d[item] = None
+
+    def __iter__(self) -> Iterator[T]:
+        return iter(self.d)
+
+    def remove(self, item: T):
+        del self.d[item]
+
+    def __bool__(self):
+        return bool(self.d)
+
+    def __len__(self) -> int:
+        return len(self.d)
+
+    def __repr__(self):
+        return f"{type(self).__name__}({', '.join(map(repr,self))})"
diff --git a/server/libs/lark/visitors.py b/server/libs/lark/visitors.py
new file mode 100644
index 0000000..18455d9
--- /dev/null
+++ b/server/libs/lark/visitors.py
@@ -0,0 +1,596 @@
+from typing import TypeVar, Tuple, List, Callable, Generic, Type, Union, Optional, Any, cast
+from abc import ABC
+
+from .utils import combine_alternatives
+from .tree import Tree, Branch
+from .exceptions import VisitError, GrammarError
+from .lexer import Token
+
+###{standalone
+from functools import wraps, update_wrapper
+from inspect import getmembers, getmro
+
+_Return_T = TypeVar('_Return_T')
+_Return_V = TypeVar('_Return_V')
+_Leaf_T = TypeVar('_Leaf_T')
+_Leaf_U = TypeVar('_Leaf_U')
+_R = TypeVar('_R')
+_FUNC = Callable[..., _Return_T]
+_DECORATED = Union[_FUNC, type]
+
+class _DiscardType:
+    """When the Discard value is returned from a transformer callback,
+    that node is discarded and won't appear in the parent.
+
+    Note:
+        This feature is disabled when the transformer is provided to Lark
+        using the ``transformer`` keyword (aka Tree-less LALR mode).
+
+    Example:
+        ::
+
+            class T(Transformer):
+                def ignore_tree(self, children):
+                    return Discard
+
+                def IGNORE_TOKEN(self, token):
+                    return Discard
+    """
+
+    def __repr__(self):
+        return "lark.visitors.Discard"
+
+Discard = _DiscardType()
+
+# Transformers
+
+class _Decoratable:
+    "Provides support for decorating methods with @v_args"
+
+    @classmethod
+    def _apply_v_args(cls, visit_wrapper):
+        mro = getmro(cls)
+        assert mro[0] is cls
+        libmembers = {name for _cls in mro[1:] for name, _ in getmembers(_cls)}
+        for name, value in getmembers(cls):
+
+            # Make sure the function isn't inherited (unless it's overwritten)
+            if name.startswith('_') or (name in libmembers and name not in cls.__dict__):
+                continue
+            if not callable(value):
+                continue
+
+            # Skip if v_args already applied (at the function level)
+            if isinstance(cls.__dict__[name], _VArgsWrapper):
+                continue
+
+            setattr(cls, name, _VArgsWrapper(cls.__dict__[name], visit_wrapper))
+        return cls
+
+    def __class_getitem__(cls, _):
+        return cls
+
+
+class Transformer(_Decoratable, ABC, Generic[_Leaf_T, _Return_T]):
+    """Transformers work bottom-up (or depth-first), starting with visiting the leaves and working
+    their way up until ending at the root of the tree.
+
+    For each node visited, the transformer will call the appropriate method (callbacks), according to the
+    node's ``data``, and use the returned value to replace the node, thereby creating a new tree structure.
+
+    Transformers can be used to implement map & reduce patterns. Because nodes are reduced from leaf to root,
+    at any point the callbacks may assume the children have already been transformed (if applicable).
+
+    If the transformer cannot find a method with the right name, it will instead call ``__default__``, which by
+    default creates a copy of the node.
+
+    To discard a node, return Discard (``lark.visitors.Discard``).
+
+    ``Transformer`` can do anything ``Visitor`` can do, but because it reconstructs the tree,
+    it is slightly less efficient.
+
+    A transformer without methods essentially performs a non-memoized partial deepcopy.
+
+    All these classes implement the transformer interface:
+
+    - ``Transformer`` - Recursively transforms the tree. This is the one you probably want.
+    - ``Transformer_InPlace`` - Non-recursive. Changes the tree in-place instead of returning new instances
+    - ``Transformer_InPlaceRecursive`` - Recursive. Changes the tree in-place instead of returning new instances
+
+    Parameters:
+        visit_tokens (bool, optional): Should the transformer visit tokens in addition to rules.
+                                       Setting this to ``False`` is slightly faster. Defaults to ``True``.
+                                       (For processing ignored tokens, use the ``lexer_callbacks`` options)
+
+    """
+    __visit_tokens__ = True   # For backwards compatibility
+
+    def __init__(self,  visit_tokens: bool=True) -> None:
+        self.__visit_tokens__ = visit_tokens
+
+    def _call_userfunc(self, tree, new_children=None):
+        # Assumes tree is already transformed
+        children = new_children if new_children is not None else tree.children
+        try:
+            f = getattr(self, tree.data)
+        except AttributeError:
+            return self.__default__(tree.data, children, tree.meta)
+        else:
+            try:
+                wrapper = getattr(f, 'visit_wrapper', None)
+                if wrapper is not None:
+                    return f.visit_wrapper(f, tree.data, children, tree.meta)
+                else:
+                    return f(children)
+            except GrammarError:
+                raise
+            except Exception as e:
+                raise VisitError(tree.data, tree, e)
+
+    def _call_userfunc_token(self, token):
+        try:
+            f = getattr(self, token.type)
+        except AttributeError:
+            return self.__default_token__(token)
+        else:
+            try:
+                return f(token)
+            except GrammarError:
+                raise
+            except Exception as e:
+                raise VisitError(token.type, token, e)
+
+    def _transform_children(self, children):
+        for c in children:
+            if isinstance(c, Tree):
+                res = self._transform_tree(c)
+            elif self.__visit_tokens__ and isinstance(c, Token):
+                res = self._call_userfunc_token(c)
+            else:
+                res = c
+
+            if res is not Discard:
+                yield res
+
+    def _transform_tree(self, tree):
+        children = list(self._transform_children(tree.children))
+        return self._call_userfunc(tree, children)
+
+    def transform(self, tree: Tree[_Leaf_T]) -> _Return_T:
+        "Transform the given tree, and return the final result"
+        res = list(self._transform_children([tree]))
+        if not res:
+            return None     # type: ignore[return-value]
+        assert len(res) == 1
+        return res[0]
+
+    def __mul__(
+            self: 'Transformer[_Leaf_T, Tree[_Leaf_U]]',
+            other: 'Union[Transformer[_Leaf_U, _Return_V], TransformerChain[_Leaf_U, _Return_V,]]'
+    ) -> 'TransformerChain[_Leaf_T, _Return_V]':
+        """Chain two transformers together, returning a new transformer.
+        """
+        return TransformerChain(self, other)
+
+    def __default__(self, data, children, meta):
+        """Default function that is called if there is no attribute matching ``data``
+
+        Can be overridden. Defaults to creating a new copy of the tree node (i.e. ``return Tree(data, children, meta)``)
+        """
+        return Tree(data, children, meta)
+
+    def __default_token__(self, token):
+        """Default function that is called if there is no attribute matching ``token.type``
+
+        Can be overridden. Defaults to returning the token as-is.
+        """
+        return token
+
+
+def merge_transformers(base_transformer=None, **transformers_to_merge):
+    """Merge a collection of transformers into the base_transformer, each into its own 'namespace'.
+
+    When called, it will collect the methods from each transformer, and assign them to base_transformer,
+    with their name prefixed with the given keyword, as ``prefix__methodname``.
+
+    This function is especially useful for processing grammars that import other grammars,
+    thereby creating some of their rules in a 'namespace'. (i.e with a consistent name prefix).
+    In this case, the key for the transformer should match the name of the imported grammar.
+
+    Parameters:
+        base_transformer (Transformer, optional): The transformer that all other transformers will be added to.
+        **transformers_to_merge: Keyword arguments, in the form of ``name_prefix = transformer``.
+
+    Raises:
+        AttributeError: In case of a name collision in the merged methods
+
+    Example:
+        ::
+
+            class TBase(Transformer):
+                def start(self, children):
+                    return children[0] + 'bar'
+
+            class TImportedGrammar(Transformer):
+                def foo(self, children):
+                    return "foo"
+
+            composed_transformer = merge_transformers(TBase(), imported=TImportedGrammar())
+
+            t = Tree('start', [ Tree('imported__foo', []) ])
+
+            assert composed_transformer.transform(t) == 'foobar'
+
+    """
+    if base_transformer is None:
+        base_transformer = Transformer()
+    for prefix, transformer in transformers_to_merge.items():
+        for method_name in dir(transformer):
+            method = getattr(transformer, method_name)
+            if not callable(method):
+                continue
+            if method_name.startswith("_") or method_name == "transform":
+                continue
+            prefixed_method = prefix + "__" + method_name
+            if hasattr(base_transformer, prefixed_method):
+                raise AttributeError("Cannot merge: method '%s' appears more than once" % prefixed_method)
+
+            setattr(base_transformer, prefixed_method, method)
+
+    return base_transformer
+
+
+class InlineTransformer(Transformer):   # XXX Deprecated
+    def _call_userfunc(self, tree, new_children=None):
+        # Assumes tree is already transformed
+        children = new_children if new_children is not None else tree.children
+        try:
+            f = getattr(self, tree.data)
+        except AttributeError:
+            return self.__default__(tree.data, children, tree.meta)
+        else:
+            return f(*children)
+
+
+class TransformerChain(Generic[_Leaf_T, _Return_T]):
+
+    transformers: 'Tuple[Union[Transformer, TransformerChain], ...]'
+
+    def __init__(self, *transformers: 'Union[Transformer, TransformerChain]') -> None:
+        self.transformers = transformers
+
+    def transform(self, tree: Tree[_Leaf_T]) -> _Return_T:
+        for t in self.transformers:
+            tree = t.transform(tree)
+        return cast(_Return_T, tree)
+
+    def __mul__(
+            self: 'TransformerChain[_Leaf_T, Tree[_Leaf_U]]',
+            other: 'Union[Transformer[_Leaf_U, _Return_V], TransformerChain[_Leaf_U, _Return_V]]'
+    ) -> 'TransformerChain[_Leaf_T, _Return_V]':
+        return TransformerChain(*self.transformers + (other,))
+
+
+class Transformer_InPlace(Transformer[_Leaf_T, _Return_T]):
+    """Same as Transformer, but non-recursive, and changes the tree in-place instead of returning new instances
+
+    Useful for huge trees. Conservative in memory.
+    """
+    def _transform_tree(self, tree):           # Cancel recursion
+        return self._call_userfunc(tree)
+
+    def transform(self, tree: Tree[_Leaf_T]) -> _Return_T:
+        for subtree in tree.iter_subtrees():
+            subtree.children = list(self._transform_children(subtree.children))
+
+        return self._transform_tree(tree)
+
+
+class Transformer_NonRecursive(Transformer[_Leaf_T, _Return_T]):
+    """Same as Transformer but non-recursive.
+
+    Like Transformer, it doesn't change the original tree.
+
+    Useful for huge trees.
+    """
+
+    def transform(self, tree: Tree[_Leaf_T]) -> _Return_T:
+        # Tree to postfix
+        rev_postfix = []
+        q: List[Branch[_Leaf_T]] = [tree]
+        while q:
+            t = q.pop()
+            rev_postfix.append(t)
+            if isinstance(t, Tree):
+                q += t.children
+
+        # Postfix to tree
+        stack: List = []
+        for x in reversed(rev_postfix):
+            if isinstance(x, Tree):
+                size = len(x.children)
+                if size:
+                    args = stack[-size:]
+                    del stack[-size:]
+                else:
+                    args = []
+
+                res = self._call_userfunc(x, args)
+                if res is not Discard:
+                    stack.append(res)
+
+            elif self.__visit_tokens__ and isinstance(x, Token):
+                res = self._call_userfunc_token(x)
+                if res is not Discard:
+                    stack.append(res)
+            else:
+                stack.append(x)
+
+        result, = stack  # We should have only one tree remaining
+        # There are no guarantees on the type of the value produced by calling a user func for a
+        # child will produce. This means type system can't statically know that the final result is
+        # _Return_T. As a result a cast is required.
+        return cast(_Return_T, result)
+
+
+class Transformer_InPlaceRecursive(Transformer):
+    "Same as Transformer, recursive, but changes the tree in-place instead of returning new instances"
+    def _transform_tree(self, tree):
+        tree.children = list(self._transform_children(tree.children))
+        return self._call_userfunc(tree)
+
+
+# Visitors
+
+class VisitorBase:
+    def _call_userfunc(self, tree):
+        return getattr(self, tree.data, self.__default__)(tree)
+
+    def __default__(self, tree):
+        """Default function that is called if there is no attribute matching ``tree.data``
+
+        Can be overridden. Defaults to doing nothing.
+        """
+        return tree
+
+    def __class_getitem__(cls, _):
+        return cls
+
+
+class Visitor(VisitorBase, ABC, Generic[_Leaf_T]):
+    """Tree visitor, non-recursive (can handle huge trees).
+
+    Visiting a node calls its methods (provided by the user via inheritance) according to ``tree.data``
+    """
+
+    def visit(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]:
+        "Visits the tree, starting with the leaves and finally the root (bottom-up)"
+        for subtree in tree.iter_subtrees():
+            self._call_userfunc(subtree)
+        return tree
+
+    def visit_topdown(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]:
+        "Visit the tree, starting at the root, and ending at the leaves (top-down)"
+        for subtree in tree.iter_subtrees_topdown():
+            self._call_userfunc(subtree)
+        return tree
+
+
+class Visitor_Recursive(VisitorBase, Generic[_Leaf_T]):
+    """Bottom-up visitor, recursive.
+
+    Visiting a node calls its methods (provided by the user via inheritance) according to ``tree.data``
+
+    Slightly faster than the non-recursive version.
+    """
+
+    def visit(self, tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]:
+        "Visits the tree, starting with the leaves and finally the root (bottom-up)"
+        for child in tree.children:
+            if isinstance(child, Tree):
+                self.visit(child)
+
+        self._call_userfunc(tree)
+        return tree
+
+    def visit_topdown(self,tree: Tree[_Leaf_T]) -> Tree[_Leaf_T]:
+        "Visit the tree, starting at the root, and ending at the leaves (top-down)"
+        self._call_userfunc(tree)
+
+        for child in tree.children:
+            if isinstance(child, Tree):
+                self.visit_topdown(child)
+
+        return tree
+
+
+class Interpreter(_Decoratable, ABC, Generic[_Leaf_T, _Return_T]):
+    """Interpreter walks the tree starting at the root.
+
+    Visits the tree, starting with the root and finally the leaves (top-down)
+
+    For each tree node, it calls its methods (provided by user via inheritance) according to ``tree.data``.
+
+    Unlike ``Transformer`` and ``Visitor``, the Interpreter doesn't automatically visit its sub-branches.
+    The user has to explicitly call ``visit``, ``visit_children``, or use the ``@visit_children_decor``.
+    This allows the user to implement branching and loops.
+    """
+
+    def visit(self, tree: Tree[_Leaf_T]) -> _Return_T:
+        # There are no guarantees on the type of the value produced by calling a user func for a
+        # child will produce. So only annotate the public method and use an internal method when
+        # visiting child trees.
+        return self._visit_tree(tree)
+
+    def _visit_tree(self, tree: Tree[_Leaf_T]):
+        f = getattr(self, tree.data)
+        wrapper = getattr(f, 'visit_wrapper', None)
+        if wrapper is not None:
+            return f.visit_wrapper(f, tree.data, tree.children, tree.meta)
+        else:
+            return f(tree)
+
+    def visit_children(self, tree: Tree[_Leaf_T]) -> List:
+        return [self._visit_tree(child) if isinstance(child, Tree) else child
+                for child in tree.children]
+
+    def __getattr__(self, name):
+        return self.__default__
+
+    def __default__(self, tree):
+        return self.visit_children(tree)
+
+
+_InterMethod = Callable[[Type[Interpreter], _Return_T], _R]
+
+def visit_children_decor(func: _InterMethod) -> _InterMethod:
+    "See Interpreter"
+    @wraps(func)
+    def inner(cls, tree):
+        values = cls.visit_children(tree)
+        return func(cls, values)
+    return inner
+
+# Decorators
+
+def _apply_v_args(obj, visit_wrapper):
+    try:
+        _apply = obj._apply_v_args
+    except AttributeError:
+        return _VArgsWrapper(obj, visit_wrapper)
+    else:
+        return _apply(visit_wrapper)
+
+
+class _VArgsWrapper:
+    """
+    A wrapper around a Callable. It delegates `__call__` to the Callable.
+    If the Callable has a `__get__`, that is also delegate and the resulting function is wrapped.
+    Otherwise, we use the original function mirroring the behaviour without a __get__.
+    We also have the visit_wrapper attribute to be used by Transformers.
+    """
+    base_func: Callable
+
+    def __init__(self, func: Callable, visit_wrapper: Callable[[Callable, str, list, Any], Any]):
+        if isinstance(func, _VArgsWrapper):
+            func = func.base_func
+        self.base_func = func
+        self.visit_wrapper = visit_wrapper
+        update_wrapper(self, func)
+
+    def __call__(self, *args, **kwargs):
+        return self.base_func(*args, **kwargs)
+
+    def __get__(self, instance, owner=None):
+        try:
+            # Use the __get__ attribute of the type instead of the instance
+            # to fully mirror the behavior of getattr
+            g = type(self.base_func).__get__
+        except AttributeError:
+            return self
+        else:
+            return _VArgsWrapper(g(self.base_func, instance, owner), self.visit_wrapper)
+
+    def __set_name__(self, owner, name):
+        try:
+            f = type(self.base_func).__set_name__
+        except AttributeError:
+            return
+        else:
+            f(self.base_func, owner, name)
+
+
+def _vargs_inline(f, _data, children, _meta):
+    return f(*children)
+def _vargs_meta_inline(f, _data, children, meta):
+    return f(meta, *children)
+def _vargs_meta(f, _data, children, meta):
+    return f(meta, children)
+def _vargs_tree(f, data, children, meta):
+    return f(Tree(data, children, meta))
+
+
+def v_args(inline: bool = False, meta: bool = False, tree: bool = False, wrapper: Optional[Callable] = None) -> Callable[[_DECORATED], _DECORATED]:
+    """A convenience decorator factory for modifying the behavior of user-supplied visitor methods.
+
+    By default, callback methods of transformers/visitors accept one argument - a list of the node's children.
+
+    ``v_args`` can modify this behavior. When used on a transformer/visitor class definition,
+    it applies to all the callback methods inside it.
+
+    ``v_args`` can be applied to a single method, or to an entire class. When applied to both,
+    the options given to the method take precedence.
+
+    Parameters:
+        inline (bool, optional): Children are provided as ``*args`` instead of a list argument (not recommended for very long lists).
+        meta (bool, optional): Provides two arguments: ``meta`` and ``children`` (instead of just the latter)
+        tree (bool, optional): Provides the entire tree as the argument, instead of the children.
+        wrapper (function, optional): Provide a function to decorate all methods.
+
+    Example:
+        ::
+
+            @v_args(inline=True)
+            class SolveArith(Transformer):
+                def add(self, left, right):
+                    return left + right
+
+                @v_args(meta=True)
+                def mul(self, meta, children):
+                    logger.info(f'mul at line {meta.line}')
+                    left, right = children
+                    return left * right
+
+
+            class ReverseNotation(Transformer_InPlace):
+                @v_args(tree=True)
+                def tree_node(self, tree):
+                    tree.children = tree.children[::-1]
+    """
+    if tree and (meta or inline):
+        raise ValueError("Visitor functions cannot combine 'tree' with 'meta' or 'inline'.")
+
+    func = None
+    if meta:
+        if inline:
+            func = _vargs_meta_inline
+        else:
+            func = _vargs_meta
+    elif inline:
+        func = _vargs_inline
+    elif tree:
+        func = _vargs_tree
+
+    if wrapper is not None:
+        if func is not None:
+            raise ValueError("Cannot use 'wrapper' along with 'tree', 'meta' or 'inline'.")
+        func = wrapper
+
+    def _visitor_args_dec(obj):
+        return _apply_v_args(obj, func)
+    return _visitor_args_dec
+
+
+###}
+
+
+# --- Visitor Utilities ---
+
+class CollapseAmbiguities(Transformer):
+    """
+    Transforms a tree that contains any number of _ambig nodes into a list of trees,
+    each one containing an unambiguous tree.
+
+    The length of the resulting list is the product of the length of all _ambig nodes.
+
+    Warning: This may quickly explode for highly ambiguous trees.
+
+    """
+    def _ambig(self, options):
+        return sum(options, [])
+
+    def __default__(self, data, children_lists, meta):
+        return [Tree(data, children, meta) for children in combine_alternatives(children_lists)]
+
+    def __default_token__(self, t):
+        return [t]
diff --git a/server/requirements.in b/server/requirements.in
index fe5e44a..46312f1 100644
--- a/server/requirements.in
+++ b/server/requirements.in
@@ -13,4 +13,5 @@ pygls
 packaging
 
 # TODO: Add your tool here
-ply
\ No newline at end of file
+ply
+lark
\ No newline at end of file
diff --git a/server/requirements.txt b/server/requirements.txt
index cadc061..94f34c4 100644
--- a/server/requirements.txt
+++ b/server/requirements.txt
@@ -16,6 +16,10 @@ cattrs==25.1.1 \
     # via
     #   lsprotocol
     #   pygls
+lark==1.2.2 \
+    --hash=sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c \
+    --hash=sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80
+    # via -r ./requirements.in
 lsprotocol==2023.0.1 \
     --hash=sha256:c75223c9e4af2f24272b14c6375787438279369236cd568f596d4951052a60f2 \
     --hash=sha256:cc5c15130d2403c18b734304339e51242d3018a05c4f7d0f198ad6e0cd21861d
diff --git a/server/src/common/completion_list.json b/server/src/common/completion_list.json
index 78a9971..3bdb27c 100644
--- a/server/src/common/completion_list.json
+++ b/server/src/common/completion_list.json
@@ -187,6 +187,52 @@
                 "set cutter_list \"UGTI0202_030 UGTI0202_025 UGTI0201_062\"\nMOM_ask_cutter_info cutter_list"
             ]
         },
+        {
+            "label": "MOM_ask_definition_element",
+            "kind": "function",
+            "description": "Gets the content of an element or an element plus an optional sub-element which is defined in a definition file. If the element is defined, returns 1 and sets mom_result with the format {key \"value\"}{key1 \"value1, \\opt\"}. If the element or sub-element does not exist, returns 0 and sets mom_result to \"UNDEFINED ELEMENT\" or \"UNDEFINED SUB-ELEMENT\".",
+            "format": "MOM_ask_definition_element   []",
+            "parameters": [
+                {
+                    "name": "FORMAT|ADDRESS|BLOCK",
+                    "desc": "The type of definition element (format, address, or block)."
+                },
+                {
+                    "name": "element",
+                    "desc": "The name of the definition element."
+                },
+                {
+                    "name": "sub-element",
+                    "desc": "The name of the definition sub-element."
+                }
+            ],
+            "returns": [
+                "1 - Element is defined.",
+                "0 - Does not exist."
+            ],
+            "example": [
+                "MOM_ask_definition_element BLOCK \"linear_move\" \"X\"",
+                "# The command returns 1 and sets mom_result = {X \"$mom_pos(0)\"}."
+            ]
+        },
+        {
+            "label": "MOM_ask_env_var",
+            "kind": "function",
+            "description": "This extension allows you to determine how an environment variable is set.",
+            "format": "MOM_ask_env_var ",
+            "parameters": [
+                {
+                    "name": "variable_name",
+                    "desc": "Name of environment variable."
+                }
+            ],
+            "returns": [
+                "Value of environment variable."
+            ],
+            "example": [
+                "set path [MOM_ask_env_var UGII_CAM_POST_CONFIG_FILE]"
+            ]
+        },
         {
             "label": "MOM_ask_ude_info",
             "kind": "function",
diff --git a/server/src/parser/tcl.lark b/server/src/parser/tcl.lark
index b8a7353..b654233 100644
--- a/server/src/parser/tcl.lark
+++ b/server/src/parser/tcl.lark
@@ -1,3 +1,8 @@
+%import common.WS_INLINE
+%import common.NUMBER
+%import common.STRING
+%ignore WS_INLINE
+
 start: statement*
 
 ?statement: command | COMMENT
@@ -23,5 +28,3 @@ cmdsubst: "[" start "]"
 
 bare: /[^\s{}$"]+/
 
-%import common.WS_INLINE
-%ignore WS_INLINE
\ No newline at end of file

From e045e1533c48629566affe03ad436117818f55ae Mon Sep 17 00:00:00 2001
From: christoph_xd 
Date: Fri, 18 Jul 2025 22:49:49 +0200
Subject: [PATCH 09/12] add tcl parser

---
 server/src/lsp_server.py    | 27 ++++++++++--------
 server/src/parser/parser.py | 57 +++++++++++++++++++++----------------
 server/src/parser/tcl.lark  | 38 +++++++++++--------------
 3 files changed, 63 insertions(+), 59 deletions(-)

diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py
index 07cec2a..288306d 100644
--- a/server/src/lsp_server.py
+++ b/server/src/lsp_server.py
@@ -13,6 +13,7 @@ import sys
 import sysconfig
 import traceback
 from typing import Any, Optional, Sequence
+from parser.parser import parse_tcl, format_tree
 
 
 # **********************************************************
@@ -179,18 +180,20 @@ def _get_severity(*_codes: list[str]) -> lsp.DiagnosticSeverity:
 @LSP_SERVER.feature(lsp.TEXT_DOCUMENT_FORMATTING)
 def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | None:
     """LSP handler for textDocument/formatting request."""
-    # If your tool is a formatter you can use this handler to provide
-    # formatting support on save. You have to return an array of lsp.TextEdit
-    # objects, to provide your formatted results.
-
-    document = LSP_SERVER.workspace.get_document(params.text_document.uri)
-    edits = _formatting_helper(document)
-    if edits:
-        return edits
-
-    # NOTE: If you provide [] array, VS Code will clear the file of all contents.
-    # To indicate no changes to file return None.
-    return None
+    text = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
+    tree, _ = parse_tcl(text)
+    if not tree:
+        return []
+    new_text = format_tree(tree)
+    return [
+        {
+            "range": {
+                "start": {"line": 0, "character": 0},
+                "end": {"line": len(text.splitlines()), "character": 0},
+            },
+            "newText": new_text,
+        }
+    ]
 
 
 def _formatting_helper(document: workspace.Document) -> list[lsp.TextEdit] | None:
diff --git a/server/src/parser/parser.py b/server/src/parser/parser.py
index 366230d..0481917 100644
--- a/server/src/parser/parser.py
+++ b/server/src/parser/parser.py
@@ -1,32 +1,39 @@
-from lark import Lark, Transformer, Token, v_args
 from pathlib import Path
+from lark import Lark, Tree, Token, UnexpectedInput
 
-GRAMMAR_FILE = Path(__file__).parent.joinpath("tcl.lark")
-TCL_GRAMMAR = GRAMMAR_FILE.read_text(encoding="utf-8")
+tcl_parser = Lark.open(
+    Path(__file__).parent.joinpath("tcl.lark"), parser="lalr", propagate_positions=True
+)
 
 
-@v_args(inline=True)
-class TCLTransformer(Transformer):
-    def start(self, *stmts):
-        return list(stmts)
+def parse_tcl(source: str):
+    try:
+        tree = tcl_parser.parse(source)
+        return tree, []
+    except UnexpectedInput as e:
+        # e.line, e.column enthalten Position
+        diagnostic = {
+            "range": {
+                "start": {"line": e.line - 1, "character": e.column - 1},
+                "end": {"line": e.line - 1, "character": e.column},
+            },
+            "message": f"Syntaxfehler: {e}",
+            "severity": 1,  # Error
+        }
+        return None, [diagnostic]
 
-    def command(self, *elements):
-        return list(elements)
 
-    def braced(self, *content):
-        return "{" + "".join(c for c in content) + "}"
+def format_tree(tree):
+    def fmt(node, indent=0):
+        if isinstance(node, Token):
+            return node.value
+        elif isinstance(node, Tree):
+            if node.data == "cmd":
+                parts = []
+                for child in node.children:
+                    parts.append(fmt(child, indent))
+                return " ".join(parts) + ";\n"
+            # weitere Node-Typen …
+        return ""
 
-    def quoted(self, *content):
-        return '"' + "".join(c for c in content) + '"'
-
-    def variable(self, token):
-        return token.value
-
-    def cmdsubst(self, *inner):
-        return "[" + " ".join(inner) + "]"
-
-    def bare(self, token):
-        return token.value
-
-    def COMMENT(self, token):
-        return token.value
+    return "".join(fmt(tree))
diff --git a/server/src/parser/tcl.lark b/server/src/parser/tcl.lark
index b8a7353..3674b1b 100644
--- a/server/src/parser/tcl.lark
+++ b/server/src/parser/tcl.lark
@@ -1,27 +1,21 @@
-start: statement*
+// tcl.lark
+// sehr vereinfachtes Beispiel – für volle Abdeckung musst du die offizielle TCL-Spec implementieren
 
-?statement: command | COMMENT
+?start: script
 
-COMMENT: /#[^\n]*/
+script: stmt*
+stmt: command ";"?                      -> cmd
+     | newline                        -> empty
 
-command: element+
+command: WORD arg*
+arg: WORD                             -> bareword
+   | STRING                           -> string
+   | BRACED                            -> braced
 
-?element: braced
-| quoted
-| variable
-| cmdsubst
-| bare
+WORD: /[^\s\{\}";]+/
+STRING: "\"" /([^"\\]|\\.)*/ "\""     // "…"
+BRACED:  "{" /([^{}\\]|\\.)*/ "}"     // {…}
 
-braced: "{" inner_braced* "}"
-inner_braced: /[^{}]+/ | braced
-
-quoted: '"' inner_quoted* '"'
-inner_quoted: /[^"{}]+/ | braced
-
-variable: "$" /[A-Za-z_][A-Za-z0-9_]*/
-cmdsubst: "[" start "]"
-
-bare: /[^\s{}$"]+/
-
-%import common.WS_INLINE
-%ignore WS_INLINE
\ No newline at end of file
+%import common.NEWLINE   -> newline
+%import common.WS
+%ignore WS

From 9fe2202b8ba2d134349985d1bc8157b3f38bd430 Mon Sep 17 00:00:00 2001
From: christoph_xd 
Date: Sat, 19 Jul 2025 20:51:36 +0200
Subject: [PATCH 10/12] lark file

---
 server/src/parser/tcl.lark | 147 +++++++++++++++++++++++++++++++++----
 1 file changed, 132 insertions(+), 15 deletions(-)

diff --git a/server/src/parser/tcl.lark b/server/src/parser/tcl.lark
index 3674b1b..2bea195 100644
--- a/server/src/parser/tcl.lark
+++ b/server/src/parser/tcl.lark
@@ -1,21 +1,138 @@
-// tcl.lark
-// sehr vereinfachtes Beispiel – für volle Abdeckung musst du die offizielle TCL-Spec implementieren
+start: script
 
-?start: script
+script: statement*
 
-script: stmt*
-stmt: command ";"?                      -> cmd
-     | newline                        -> empty
+statement: declaracion_funcion modulo_ppal
 
-command: WORD arg*
-arg: WORD                             -> bareword
-   | STRING                           -> string
-   | BRACED                            -> braced
+declaracion_funcion: "proc" IDENTIFICADOR "{" args_funcion "}" "{" cuerpo_funcion "}" declaracion_funcion |
+args_funcion: "{" IDENTIFICADOR "}" args_funcion |
 
-WORD: /[^\s\{\}";]+/
-STRING: "\"" /([^"\\]|\\.)*/ "\""     // "…"
-BRACED:  "{" /([^{}\\]|\\.)*/ "}"     // {…}
+cuerpo_funcion: declaracion cuerpo_funcion | puts cuerpo_funcion | gets ";" cuerpo_funcion | if_funcion cuerpo_funcion 
+				| for_funcion cuerpo_funcion | while_funcion cuerpo_funcion | switch_funcion cuerpo_funcion 
+				| r_return cuerpo_funcion | agrup ";" cuerpo_funcion |
+
+if_funcion:	inicio_if cuerpo_funcion "}" elseif_funcion
+elseif_funcion:	inicio_elseif cuerpo_funcion "}" elseif_funcion | else_funcion
+else_funcion: inicio_else cuerpo_funcion "}" |
+
+switch_funcion: inicio_switch case_funcion "}"
+case_funcion: inicio_case cuerpo_funcion "}" case2_funcion
+case2_funcion: inicio_case cuerpo_funcion "}" case2_funcion | default_funcion
+default_funcion: inicio_default cuerpo_funcion "}" |  
+
+for_funcion: inicio_for cuerpo_loop_func "}"
+while_funcion: inicio_while cuerpo_loop_func "}"
+
+cuerpo_loop_func: r_break cuerpo_loop_func | r_continue cuerpo_loop_func | declaracion cuerpo_loop_func 
+					| gets ";" cuerpo_loop_func | puts cuerpo_loop_func | r_return cuerpo_loop_func 
+					| if_loop_func cuerpo_loop_func | switch_loop_func cuerpo_loop_func | for_funcion cuerpo_loop_func 
+					| while_funcion cuerpo_loop_func | agrup ";" cuerpo_loop_func |
+
+if_loop_func: inicio_if cuerpo_loop_func "}" elseif_loop_func
+elseif_loop_func: inicio_elseif cuerpo_loop_func "}" elseif_loop_func | else_loop_func
+else_loop_func: inicio_else cuerpo_loop_func "}" | 
+
+switch_loop_func: inicio_switch case_loop_func "}"
+case_loop_func: inicio_case cuerpo_loop_func "}" case2_loop_func  
+case2_loop_func: inicio_case cuerpo_loop_func "}" case2_loop_func | default_loop_func
+default_loop_func: inicio_default cuerpo_loop_func "}" |  
+
+modulo_ppal: declaracion modulo_ppal | r_for modulo_ppal | gets ";" modulo_ppal | r_if modulo_ppal 
+			| puts modulo_ppal | r_while modulo_ppal | r_switch modulo_ppal | agrup ";" modulo_ppal |
+
+r_if: inicio_if cuerpo_inst "}" elseif  
+elseif:	inicio_elseif cuerpo_inst "}" elseif | r_else  
+r_else:	inicio_else cuerpo_inst "}" |  
+
+r_switch: inicio_switch r_case "}"
+r_case:	inicio_case cuerpo_inst "}" case2  
+case2: inicio_case cuerpo_inst "}" case2 | r_default
+r_default: inicio_default cuerpo_inst "}" | 
+
+cuerpo_inst: declaracion cuerpo_inst | r_for cuerpo_inst | gets ";" cuerpo_inst | r_if cuerpo_inst 
+			| puts cuerpo_inst | r_while cuerpo_inst | r_switch cuerpo_inst | agrup ";" cuerpo_inst |
+			
+r_for: inicio_for cuerpo_loop "}"
+r_while: inicio_while cuerpo_loop "}"
+
+cuerpo_loop: declaracion cuerpo_loop | r_for cuerpo_loop | gets ";" cuerpo_loop | if_loop cuerpo_loop 
+			| puts cuerpo_loop | r_while cuerpo_loop | switch_loop cuerpo_loop | r_break cuerpo_loop 
+			| r_continue cuerpo_loop | agrup ";" cuerpo_loop |
+			
+if_loop: inicio_if cuerpo_loop "}" elseif_loop
+elseif_loop: inicio_elseif cuerpo_loop "}" elseif_loop | else_loop
+else_loop: inicio_else cuerpo_loop "}" |  
+
+switch_loop: inicio_switch case_loop "}"
+case_loop: inicio_case cuerpo_loop "}" case2_loop  
+case2_loop: inicio_case cuerpo_loop "}" case2_loop | default_loop
+default_loop: inicio_default cuerpo_loop "}" |  
+
+puts: "puts" asignacion ";"
+gets: "gets" "stdin"
+declaracion: "set" IDENTIFICADOR indice asignacion ";"
+
+agrup: "[" aux_agrup
+aux_agrup: expr "]" | IDENTIFICADOR param_func "]" | gets "]" | "array" aux_array
+aux_array: "size" IDENTIFICADOR "]" | "exists" IDENTIFICADOR "]"
+
+param_func: "{" aux_param |  
+aux_param: asignacion "}" param_func | expr "}" param_func
+
+asignacion: valor | "$" IDENTIFICADOR indice | agrup
+
+indice: "(" val_indice ")" |  
+val_indice:	valor | agrup | "$" IDENTIFICADOR indice
+
+valor: VALOR_STRING | VALOR_ENTERO | VALOR_DOUBLE
+
+incremento:	VALOR_ENTERO |
+
+r_break: "break" ";"  
+r_continue: "continue" ";" 
+r_return: "return" value_return ";" 
+value_return: asignacion |  
+
+expr: "expr" "{" expresion "}"
+asig_for: VALOR_ENTERO | "$" IDENTIFICADOR indice | expr
+
+inicio_if: "if" "{" expresion "}" "then" "{" 
+inicio_elseif: "elseif" "{" expresion "}" "then" "{" 
+inicio_else: "else" "{"
+inicio_switch: "switch" "$" IDENTIFICADOR indice "{"
+inicio_case: "case" VALOR_ENTERO "{"
+inicio_default: "default" "{"  
+
+inicio_for: "for" "{" dec_for "}" "{" expresion "}" "{" "incr" IDENTIFICADOR incremento "}" "{"  
+dec_for: "set" IDENTIFICADOR asig_for 
+
+inicio_while	:	"while" "{" expresion "}" "{"  
+
+expresion: exp_or
+exp_or:	exp_or "||" exp_and | exp_and
+exp_and: exp_and "&&" exp_ig | exp_ig
+exp_ig:	exp_ig op_ig exp_rel | exp_rel
+exp_rel: exp_rel op_rel exp_add | exp_add
+exp_add: exp_add op_add exp_mul | exp_mul
+exp_mul: exp_mul op_mul exp_pot | exp_pot
+exp_pot: exp_pot "**" exp_una | exp_una
+exp_una: op_una exp_una | term
+term: "$" IDENTIFICADOR indice | agrup | valor | "(" exp_or ")"
+
+op_ig: "eq" | "==" | "ne" | "!="
+op_rel:	">" | "<" | ">=" | "<="
+op_add: "+" | "-"
+op_mul: "*" | "/" | "%"
+op_una: "-" | "!"
+
+
+IDENTIFICADOR: /[a-zA-Z_][a-zA-Z0-9_]*/
+VALOR_ENTERO: /'-'?[0-9]+/
+VALOR_DOUBLE: /[0-9]+ '.' [0-9]+/
+VALOR_STRING: /'"' ~[\r\n"]* '"'/
+COMMENT: /#[^\r\n]*/
+WS: /[ \t]+/
+NEWLINE: /\r?\n/
 
-%import common.NEWLINE   -> newline
-%import common.WS
 %ignore WS
+%ignore COMMENT
\ No newline at end of file

From d77bd5dc293ef03a8254055ad145bc1286f2366e Mon Sep 17 00:00:00 2001
From: christoph_xd 
Date: Sat, 19 Jul 2025 21:07:22 +0200
Subject: [PATCH 11/12] update

---
 server/src/parser/tcl.lark | 318 +++++++++++++++++++++++++------------
 1 file changed, 215 insertions(+), 103 deletions(-)

diff --git a/server/src/parser/tcl.lark b/server/src/parser/tcl.lark
index 2bea195..83734cd 100644
--- a/server/src/parser/tcl.lark
+++ b/server/src/parser/tcl.lark
@@ -1,138 +1,250 @@
-start: script
+// tcl.lark
 
+start: script
 script: statement*
 
-statement: declaracion_funcion modulo_ppal
+statement: function_declaration
+         | top_level
 
-declaracion_funcion: "proc" IDENTIFICADOR "{" args_funcion "}" "{" cuerpo_funcion "}" declaracion_funcion |
-args_funcion: "{" IDENTIFICADOR "}" args_funcion |
+// --- Function declarations (zero or more) ---
+function_declaration: ("proc" IDENTIFIER "{" function_args "}" "{" function_body "}")*
 
-cuerpo_funcion: declaracion cuerpo_funcion | puts cuerpo_funcion | gets ";" cuerpo_funcion | if_funcion cuerpo_funcion 
-				| for_funcion cuerpo_funcion | while_funcion cuerpo_funcion | switch_funcion cuerpo_funcion 
-				| r_return cuerpo_funcion | agrup ";" cuerpo_funcion |
+// --- Function arguments (0..n blocks) ---
+function_args: ("{" IDENTIFIER "}")*
 
-if_funcion:	inicio_if cuerpo_funcion "}" elseif_funcion
-elseif_funcion:	inicio_elseif cuerpo_funcion "}" elseif_funcion | else_funcion
-else_funcion: inicio_else cuerpo_funcion "}" |
+// --- Function body: zero or more statements ---
+function_body: (assignment_statement
+               | print_statement
+               | input_statement ";"
+               | if_statement
+               | for_statement
+               | while_statement
+               | switch_statement
+               | return_statement
+               | grouping ";"
+               )*
 
-switch_funcion: inicio_switch case_funcion "}"
-case_funcion: inicio_case cuerpo_funcion "}" case2_funcion
-case2_funcion: inicio_case cuerpo_funcion "}" case2_funcion | default_funcion
-default_funcion: inicio_default cuerpo_funcion "}" |  
+// --- If / elseif / else in functions ---
+if_statement: if_header function_body "}" elseif_clause
 
-for_funcion: inicio_for cuerpo_loop_func "}"
-while_funcion: inicio_while cuerpo_loop_func "}"
+elseif_clause: elseif_header function_body "}" elseif_clause
+             | else_clause
 
-cuerpo_loop_func: r_break cuerpo_loop_func | r_continue cuerpo_loop_func | declaracion cuerpo_loop_func 
-					| gets ";" cuerpo_loop_func | puts cuerpo_loop_func | r_return cuerpo_loop_func 
-					| if_loop_func cuerpo_loop_func | switch_loop_func cuerpo_loop_func | for_funcion cuerpo_loop_func 
-					| while_funcion cuerpo_loop_func | agrup ";" cuerpo_loop_func |
+else_clause: else_header function_body "}"
+           |    /* empty */
 
-if_loop_func: inicio_if cuerpo_loop_func "}" elseif_loop_func
-elseif_loop_func: inicio_elseif cuerpo_loop_func "}" elseif_loop_func | else_loop_func
-else_loop_func: inicio_else cuerpo_loop_func "}" | 
+// --- Switch in functions ---
+switch_statement: switch_header case_clause "}"
 
-switch_loop_func: inicio_switch case_loop_func "}"
-case_loop_func: inicio_case cuerpo_loop_func "}" case2_loop_func  
-case2_loop_func: inicio_case cuerpo_loop_func "}" case2_loop_func | default_loop_func
-default_loop_func: inicio_default cuerpo_loop_func "}" |  
+case_clause: case_header function_body "}" case_clauses
 
-modulo_ppal: declaracion modulo_ppal | r_for modulo_ppal | gets ";" modulo_ppal | r_if modulo_ppal 
-			| puts modulo_ppal | r_while modulo_ppal | r_switch modulo_ppal | agrup ";" modulo_ppal |
+case_clauses: case_header function_body "}" case_clauses
+            | default_clause
 
-r_if: inicio_if cuerpo_inst "}" elseif  
-elseif:	inicio_elseif cuerpo_inst "}" elseif | r_else  
-r_else:	inicio_else cuerpo_inst "}" |  
+default_clause: default_header function_body "}"
+               |    /* empty */
 
-r_switch: inicio_switch r_case "}"
-r_case:	inicio_case cuerpo_inst "}" case2  
-case2: inicio_case cuerpo_inst "}" case2 | r_default
-r_default: inicio_default cuerpo_inst "}" | 
+// --- For / While in functions ---
+for_statement: for_header loop_body_in_function "}"
+while_statement: while_header loop_body_in_function "}"
 
-cuerpo_inst: declaracion cuerpo_inst | r_for cuerpo_inst | gets ";" cuerpo_inst | r_if cuerpo_inst 
-			| puts cuerpo_inst | r_while cuerpo_inst | r_switch cuerpo_inst | agrup ";" cuerpo_inst |
-			
-r_for: inicio_for cuerpo_loop "}"
-r_while: inicio_while cuerpo_loop "}"
+loop_body_in_function: (break_statement
+                      | continue_statement
+                      | assignment_statement
+                      | input_statement ";"
+                      | print_statement
+                      | return_statement
+                      | if_loop_statement
+                      | switch_loop_statement
+                      | for_statement
+                      | while_statement
+                      | grouping ";"
+                      )*
 
-cuerpo_loop: declaracion cuerpo_loop | r_for cuerpo_loop | gets ";" cuerpo_loop | if_loop cuerpo_loop 
-			| puts cuerpo_loop | r_while cuerpo_loop | switch_loop cuerpo_loop | r_break cuerpo_loop 
-			| r_continue cuerpo_loop | agrup ";" cuerpo_loop |
-			
-if_loop: inicio_if cuerpo_loop "}" elseif_loop
-elseif_loop: inicio_elseif cuerpo_loop "}" elseif_loop | else_loop
-else_loop: inicio_else cuerpo_loop "}" |  
+if_loop_statement: if_header loop_body_in_function "}" elseif_loop_clause
 
-switch_loop: inicio_switch case_loop "}"
-case_loop: inicio_case cuerpo_loop "}" case2_loop  
-case2_loop: inicio_case cuerpo_loop "}" case2_loop | default_loop
-default_loop: inicio_default cuerpo_loop "}" |  
+elseif_loop_clause: elseif_header loop_body_in_function "}" elseif_loop_clause
+                  | else_loop_clause
 
-puts: "puts" asignacion ";"
-gets: "gets" "stdin"
-declaracion: "set" IDENTIFICADOR indice asignacion ";"
+else_loop_clause: else_header loop_body_in_function "}"
+                |    /* empty */
 
-agrup: "[" aux_agrup
-aux_agrup: expr "]" | IDENTIFICADOR param_func "]" | gets "]" | "array" aux_array
-aux_array: "size" IDENTIFICADOR "]" | "exists" IDENTIFICADOR "]"
+switch_loop_statement: switch_header case_loop_clause "}"
 
-param_func: "{" aux_param |  
-aux_param: asignacion "}" param_func | expr "}" param_func
+case_loop_clause: case_header loop_body_in_function "}" case_loop_clauses
 
-asignacion: valor | "$" IDENTIFICADOR indice | agrup
+case_loop_clauses: case_header loop_body_in_function "}" case_loop_clauses
+                 | default_loop_clause
 
-indice: "(" val_indice ")" |  
-val_indice:	valor | agrup | "$" IDENTIFICADOR indice
+default_loop_clause: default_header loop_body_in_function "}"
+                   |    /* empty */
 
-valor: VALOR_STRING | VALOR_ENTERO | VALOR_DOUBLE
+// --- Top-level (module) statements (zero or more) ---
+top_level: (assignment_statement
+          | for_main
+          | input_statement ";"
+          | if_main
+          | print_statement
+          | while_main
+          | switch_main
+          | grouping ";"
+          )*
 
-incremento:	VALOR_ENTERO |
+// --- If / Else at top level ---
+if_main: if_header main_body "}" elseif_main_clause
 
-r_break: "break" ";"  
-r_continue: "continue" ";" 
-r_return: "return" value_return ";" 
-value_return: asignacion |  
+elseif_main_clause: elseif_header main_body "}" elseif_main_clause
+                  | else_main_clause
 
-expr: "expr" "{" expresion "}"
-asig_for: VALOR_ENTERO | "$" IDENTIFICADOR indice | expr
+else_main_clause: else_header main_body "}"
+                |    /* empty */
 
-inicio_if: "if" "{" expresion "}" "then" "{" 
-inicio_elseif: "elseif" "{" expresion "}" "then" "{" 
-inicio_else: "else" "{"
-inicio_switch: "switch" "$" IDENTIFICADOR indice "{"
-inicio_case: "case" VALOR_ENTERO "{"
-inicio_default: "default" "{"  
+main_body: (assignment_statement
+          | for_main
+          | input_statement ";"
+          | if_main
+          | print_statement
+          | while_main
+          | switch_main
+          | grouping ";"
+          )*
 
-inicio_for: "for" "{" dec_for "}" "{" expresion "}" "{" "incr" IDENTIFICADOR incremento "}" "{"  
-dec_for: "set" IDENTIFICADOR asig_for 
+// --- Switch at top level ---
+switch_main: switch_header main_case_clause "}"
 
-inicio_while	:	"while" "{" expresion "}" "{"  
+main_case_clause: case_header main_body "}" main_case_clauses
 
-expresion: exp_or
-exp_or:	exp_or "||" exp_and | exp_and
-exp_and: exp_and "&&" exp_ig | exp_ig
-exp_ig:	exp_ig op_ig exp_rel | exp_rel
-exp_rel: exp_rel op_rel exp_add | exp_add
-exp_add: exp_add op_add exp_mul | exp_mul
-exp_mul: exp_mul op_mul exp_pot | exp_pot
-exp_pot: exp_pot "**" exp_una | exp_una
-exp_una: op_una exp_una | term
-term: "$" IDENTIFICADOR indice | agrup | valor | "(" exp_or ")"
+main_case_clauses: case_header main_body "}" main_case_clauses
+                 | default_main_clause
 
-op_ig: "eq" | "==" | "ne" | "!="
-op_rel:	">" | "<" | ">=" | "<="
-op_add: "+" | "-"
-op_mul: "*" | "/" | "%"
-op_una: "-" | "!"
+default_main_clause: default_header main_body "}"
+                   |    /* empty */
 
+// --- For / While at top level ---
+for_main: for_header main_loop_body "}"
+while_main: while_header main_loop_body "}"
 
-IDENTIFICADOR: /[a-zA-Z_][a-zA-Z0-9_]*/
-VALOR_ENTERO: /'-'?[0-9]+/
-VALOR_DOUBLE: /[0-9]+ '.' [0-9]+/
-VALOR_STRING: /'"' ~[\r\n"]* '"'/
+main_loop_body: (assignment_statement
+               | for_main
+               | input_statement ";"
+               | if_loop_main
+               | print_statement
+               | while_main
+               | switch_loop_main
+               | break_statement
+               | continue_statement
+               | grouping ";"
+               )*
+
+if_loop_main: if_header main_loop_body "}" elseif_loop_main
+
+elseif_loop_main: elseif_header main_loop_body "}" elseif_loop_main
+                | else_loop_main
+
+else_loop_main: else_header main_loop_body "}"
+              |    /* empty */
+
+switch_loop_main: switch_header case_loop_main "}"
+
+case_loop_main: case_header main_loop_body "}" case_loop_main_clauses
+
+case_loop_main_clauses: case_header main_loop_body "}" case_loop_main_clauses
+                      | default_loop_main
+
+default_loop_main: default_header main_loop_body "}"
+                 |    /* empty */
+
+// --- Primitive statements ---
+print_statement: "puts" assignment ";"
+input_statement: "gets" "stdin"
+assignment_statement: "set" IDENTIFIER index access_value ";"
+
+grouping: "[" grouping_aux "]"
+grouping_aux: expression
+            | IDENTIFIER function_param
+            | input_statement
+            | "array" array_aux
+
+array_aux: "size" IDENTIFIER
+         | "exists" IDENTIFIER
+
+function_param: "{" param_aux "}"
+param_aux: assignment_value
+          | expression
+
+assignment_value: value
+                | "$" IDENTIFIER index
+                | grouping
+
+index: "(" index_value ")"
+index_value: value
+            | grouping
+            | "$" IDENTIFIER index
+
+value: STRING_LITERAL
+     | INT
+     | FLOAT
+
+increment: INT?
+
+break_statement: "break" ";"
+continue_statement: "continue" ";"
+return_statement: "return" return_value ";"
+return_value: assignment_value?
+
+expr_command: "expr" "{" expression "}"
+for_init: INT
+        | "$" IDENTIFIER index
+        | expr_command
+
+// --- Headers for control structures ---
+if_header: "if" "{" expression "}" "then" "{"
+elseif_header: "elseif" "{" expression "}" "then" "{"
+else_header: "else" "{"
+switch_header: "switch" "$" IDENTIFIER index "{"
+case_header: "case" INT "{"
+default_header: "default" "{"
+for_header: "for" "{" for_declaration "}" "{" expression "}" "{" "incr" IDENTIFIER increment "}" "{"
+for_declaration: "set" IDENTIFIER for_init
+while_header: "while" "{" expression "}" "{"
+
+// --- Expression grammar ---
+expression: or_expression
+or_expression: or_expression "||" and_expression
+             | and_expression
+and_expression: and_expression "&&" equality_expression
+              | equality_expression
+equality_expression: equality_expression eq_ops relational_expression
+                   | relational_expression
+relational_expression: relational_expression rel_ops additive_expression
+                     | additive_expression
+additive_expression: additive_expression add_ops multiplicative_expression
+                   | multiplicative_expression
+multiplicative_expression: multiplicative_expression mul_ops power_expression
+                         | power_expression
+power_expression: power_expression "**" unary_expression
+                | unary_expression
+unary_expression: unary_ops unary_expression
+                | term
+term: "$" IDENTIFIER index
+    | grouping
+    | value
+    | "(" or_expression ")"
+
+eq_ops: "eq" | "==" | "ne" | "!="
+rel_ops: ">" | "<" | ">=" | "<="
+add_ops: "+" | "-"
+mul_ops: "*" | "/" | "%"
+unary_ops: "-" | "!"
+
+// --- Terminals ---
+IDENTIFIER: /[a-zA-Z_][a-zA-Z0-9_]*/
+INT: /-?[0-9]+/
+FLOAT: /[0-9]+\.[0-9]+/
+STRING_LITERAL: /"([^"\\]|\\.)*"/
 COMMENT: /#[^\r\n]*/
-WS: /[ \t]+/
-NEWLINE: /\r?\n/
 
-%ignore WS
-%ignore COMMENT
\ No newline at end of file
+
+%ignore /[ \t]+/    // spaces & tabs
+%ignore COMMENT
+%ignore _NL  
\ No newline at end of file

From 7c8634b9714ca325acb8b2d1ba1ee9569a813f2d Mon Sep 17 00:00:00 2001
From: christoph_xd 
Date: Sun, 20 Jul 2025 11:53:40 +0200
Subject: [PATCH 12/12] update formatter

---
 .gitignore                     |   4 +-
 server/src/common/formatter.py |  58 ++++++
 server/src/common/tokens.py    |  81 ++++++++
 server/src/lsp_server.py       | 352 +++------------------------------
 server/src/parser/parser.py    |  55 +++---
 server/src/parser/tcl.lark     | 269 ++++---------------------
 6 files changed, 228 insertions(+), 591 deletions(-)
 create mode 100644 server/src/common/formatter.py
 create mode 100644 server/src/common/tokens.py

diff --git a/.gitignore b/.gitignore
index bbdd8dd..de3edd7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,6 @@ target
 .venv
 __pycache__
 *.pyc
-.nox
\ No newline at end of file
+.nox
+*.g4
+.antlr
\ No newline at end of file
diff --git a/server/src/common/formatter.py b/server/src/common/formatter.py
new file mode 100644
index 0000000..ff9469b
--- /dev/null
+++ b/server/src/common/formatter.py
@@ -0,0 +1,58 @@
+import re
+
+
+def format_tcl(src: str, indent_str="    ") -> str:
+    """
+    Simple Tcl formatter with special handling for:
+    1) Single-line 'if {cond} {action}' blocks remain on one line.
+    2) Combined closing-and-opening lines like '} else {' dedent then re-indent.
+    3) Lines like '} Tag' stay on the same line: '} Tag'.
+    4) Standard multi-line blocks for 'if', 'elseif', 'else', '{', '}'.
+    """
+    level = 0
+    out_lines = []
+
+    for raw_line in src.splitlines():
+        stripped = raw_line.strip()
+
+        # 1) Single-line 'if {cond} {action}' → no indent change
+        if re.match(r"^(if|elseif)\s*\{[^}]+\}\s*\{[^}]+\}$", stripped):
+            out_lines.append(indent_str * level + stripped)
+            continue
+
+        # 2) Combined '} else {' → dedent, print, then indent
+        if re.match(r"^\}\s*(elseif|else)\b.*\{$", stripped):
+            level = max(level - 1, 0)
+            out_lines.append(indent_str * level + stripped)
+            level += 1
+            continue
+
+        # 3) SPECIAL: closing brace plus tag on same line: '} Tag'
+        m = re.match(r"^\}\s+(\w+)$", stripped)
+        if m:
+            # close one block
+            level = max(level - 1, 0)
+            # stay on one line: "} Tag"
+            out_lines.append(f"{indent_str * level}}} {m.group(1)}")
+            continue
+
+        # 4) Pure '}' → dedent then print
+        if stripped == "}":
+            level = max(level - 1, 0)
+            out_lines.append(f"{indent_str * level}{stripped}")
+            continue
+
+        # 5) 'elseif' or 'else' alone → align with matching 'if'
+        if re.match(r"^(elseif|else)\b(?!.*\{)", stripped):
+            level = max(level - 1, 0)
+            out_lines.append(f"{indent_str * level}{stripped}")
+            continue
+
+        # 6) Default: print at current indent
+        out_lines.append(f"{indent_str * level}{stripped}")
+
+        # 7) Open a new block on lines ending with '{'
+        if re.match(r"^(if|elseif)\b.*\{$", stripped) or stripped.endswith("{"):
+            level += 1
+
+    return "\n".join(out_lines)
diff --git a/server/src/common/tokens.py b/server/src/common/tokens.py
new file mode 100644
index 0000000..b2cea98
--- /dev/null
+++ b/server/src/common/tokens.py
@@ -0,0 +1,81 @@
+# tokens.py
+from typing import List
+import attrs
+import enum
+from lark import Tree, Token
+
+# Legend must match the client
+SEMANTIC_TOKEN_TYPES = {
+    "keyword": 0,
+    "variable": 2,
+    "string": 3,
+}
+SEMANTIC_TOKEN_MODIFIERS = {
+    "declaration": 1 << 0,
+}
+
+
+class TokenModifier(enum.IntFlag):
+    deprecated = enum.auto()
+    readonly = enum.auto()
+    defaultLibrary = enum.auto()
+    definition = enum.auto()
+
+
+@attrs.define
+class TokenData:
+    line: int
+    offset: int
+    text: str
+
+    tok_type: str = ""
+    tok_modifiers: List[TokenModifier] = attrs.field(factory=list)
+
+
+TokenTypes = ["keyword", "variable", "function", "operator", "parameter", "type"]
+
+
+def collect_semantic_tokens(tree: Tree) -> list[int]:
+    """
+    Walk the parse tree and return a flat LSP semanticTokens/full array:
+      [line, char, length, tokenType, tokenModifiers, …]
+    """
+    data = []
+    for tok in tree.scan_values(lambda v: isinstance(v, Token)):
+        # 'set' keyword
+        if tok.type == "SET":
+            data.append(
+                [
+                    tok.line - 1,
+                    tok.column - 1,
+                    len(tok.value),
+                    SEMANTIC_TOKEN_TYPES["keyword"],
+                    0,
+                ]
+            )
+        # variable being declared
+        elif tok.type == "NAME":
+            data.append(
+                [
+                    tok.line - 1,
+                    tok.column - 1,
+                    len(tok.value),
+                    SEMANTIC_TOKEN_TYPES["variable"],
+                    SEMANTIC_TOKEN_MODIFIERS["declaration"],
+                ]
+            )
+        # string literal
+        elif tok.type == "STRING":
+            data.append(
+                [
+                    tok.line - 1,
+                    tok.column - 1,
+                    len(tok.value),
+                    SEMANTIC_TOKEN_TYPES["string"],
+                    0,
+                ]
+            )
+
+    # sort by position and flatten
+    data.sort(key=lambda x: (x[0], x[1]))
+    return [p for token in data for p in token]
diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py
index 288306d..c49b3ea 100644
--- a/server/src/lsp_server.py
+++ b/server/src/lsp_server.py
@@ -13,7 +13,6 @@ import sys
 import sysconfig
 import traceback
 from typing import Any, Optional, Sequence
-from parser.parser import parse_tcl, format_tree
 
 
 # **********************************************************
@@ -43,6 +42,8 @@ import lsp_utils as utils
 import lsprotocol.types as lsp
 from pygls import server, uris, workspace
 from common.load_data import standard_items
+from common.formatter import format_tcl
+
 
 WORKSPACE_SETTINGS = {}
 GLOBAL_SETTINGS = {}
@@ -105,71 +106,11 @@ def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]:
     return lsp.CompletionList(is_incomplete=False, items=items)
 
 
-# TODO: If your linter outputs in a known format like JSON, then parse
-# accordingly. But incase you need to parse the output using RegEx here
-# is a helper you can work with.
-# flake8 example:
-# If you use following format argument with flake8 you can use the regex below to parse it.
-# TOOL_ARGS += ["--format='%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s'"]
-# DIAGNOSTIC_RE =
-#    r"(?P\d+),(?P-?\d+),(?P\w+),(?P\w+\d+):(?P[^\r\n]*)"
-DIAGNOSTIC_RE = re.compile(r"")
-
-
-def _parse_output_using_regex(content: str) -> list[lsp.Diagnostic]:
-    lines: list[str] = content.splitlines()
-    diagnostics: list[lsp.Diagnostic] = []
-
-    # TODO: Determine if your linter reports line numbers starting at 1 (True) or 0 (False).
-    line_at_1 = True
-    # TODO: Determine if your linter reports column numbers starting at 1 (True) or 0 (False).
-    column_at_1 = True
-
-    line_offset = 1 if line_at_1 else 0
-    col_offset = 1 if column_at_1 else 0
-    for line in lines:
-        if line.startswith("'") and line.endswith("'"):
-            line = line[1:-1]
-        match = DIAGNOSTIC_RE.match(line)
-        if match:
-            data = match.groupdict()
-            position = lsp.Position(
-                line=max([int(data["line"]) - line_offset, 0]),
-                character=int(data["column"]) - col_offset,
-            )
-            diagnostic = lsp.Diagnostic(
-                range=lsp.Range(
-                    start=position,
-                    end=position,
-                ),
-                message=data.get("message"),
-                severity=_get_severity(data["code"], data["type"]),
-                code=data["code"],
-                source=TOOL_MODULE,
-            )
-            diagnostics.append(diagnostic)
-
-    return diagnostics
-
-
-# TODO: if you want to handle setting specific severity for your linter
-# in a user configurable way, then look at look at how it is implemented
-# for `pylint` extension from our team.
-# Pylint: https://github.com/microsoft/vscode-pylint
-# Follow the flow of severity from the settings in package.json to the server.
-def _get_severity(*_codes: list[str]) -> lsp.DiagnosticSeverity:
-    # TODO: All reported issues from linter are treated as warning.
-    # change it as appropriate for your linter.
-    return lsp.DiagnosticSeverity.Warning
-
-
 # **********************************************************
 # Linting features end here
 # **********************************************************
 
-# TODO: If your tool is a formatter then update this section.
-# Delete "Formatting features" section if your tool is NOT a
-# formatter.
+
 # **********************************************************
 # Formatting features start here
 # **********************************************************
@@ -177,74 +118,31 @@ def _get_severity(*_codes: list[str]) -> lsp.DiagnosticSeverity:
 #  Black: https://github.com/microsoft/vscode-black-formatter/blob/main/bundled/tool
 
 
-@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_FORMATTING)
-def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | None:
-    """LSP handler for textDocument/formatting request."""
-    text = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
-    tree, _ = parse_tcl(text)
-    if not tree:
-        return []
-    new_text = format_tree(tree)
-    return [
-        {
-            "range": {
-                "start": {"line": 0, "character": 0},
-                "end": {"line": len(text.splitlines()), "character": 0},
-            },
-            "newText": new_text,
-        }
-    ]
-
-
-def _formatting_helper(document: workspace.Document) -> list[lsp.TextEdit] | None:
-    # TODO: For formatting on save support the formatter you use must support
-    # formatting via stdin.
-    # Read, and update_run_tool_on_document and _run_tool functions as needed
-    # for your formatter.
-    result = _run_tool_on_document(document, use_stdin=True)
-    if result.stdout:
-        new_source = _match_line_endings(document, result.stdout)
-        return [
-            lsp.TextEdit(
-                range=lsp.Range(
-                    start=lsp.Position(line=0, character=0),
-                    end=lsp.Position(line=len(document.lines), character=0),
-                ),
-                new_text=new_source,
-            )
-        ]
-    return None
-
-
-def _get_line_endings(lines: list[str]) -> str:
-    """Returns line endings used in the text."""
-    try:
-        if lines[0][-2:] == "\r\n":
-            return "\r\n"
-        return "\n"
-    except Exception:  # pylint: disable=broad-except
-        return None
-
-
-def _match_line_endings(document: workspace.Document, text: str) -> str:
-    """Ensures that the edited text line endings matches the document line endings."""
-    expected = _get_line_endings(document.source.splitlines(keepends=True))
-    actual = _get_line_endings(text.splitlines(keepends=True))
-    if actual == expected or actual is None or expected is None:
-        return text
-    return text.replace(actual, expected)
-
-
 # **********************************************************
 # Formatting features ends here
 # **********************************************************
+@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_FORMATTING)
+def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | None:
+    """LSP handler for textDocument/formatting request."""
+    doc = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
+    text = doc.source
+
+    formatted = format_tcl(text)
+
+    last_line = len(text.splitlines())
+    full_range = lsp.Range(
+        start=lsp.Position(line=0, character=0),
+        end=lsp.Position(line=last_line, character=0),
+    )
+    edit = lsp.TextEdit(range=full_range, new_text=formatted)
+    return [edit]
 
 
 # **********************************************************
 # Required Language Server Initialization and Exit handlers.
 # **********************************************************
 @LSP_SERVER.feature(lsp.INITIALIZE)
-def initialize(params: lsp.InitializeParams) -> None:
+def initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
     """LSP handler for initialize request."""
     log_to_output(f"CWD Server: {os.getcwd()}")
 
@@ -261,6 +159,11 @@ def initialize(params: lsp.InitializeParams) -> None:
     log_to_output(
         f"Global settings:\r\n{json.dumps(GLOBAL_SETTINGS, indent=4, ensure_ascii=False)}\r\n"
     )
+    return lsp.InitializeResult(
+        capabilities=lsp.ServerCapabilities(
+            document_formatting_provider=True,
+        )
+    )
 
 
 @LSP_SERVER.feature(lsp.EXIT)
@@ -350,213 +253,6 @@ def _get_settings_by_document(document: workspace.Document | None):
     return WORKSPACE_SETTINGS[str(key)]
 
 
-# *****************************************************
-# Internal execution APIs.
-# *****************************************************
-def _run_tool_on_document(
-    document: workspace.Document,
-    use_stdin: bool = False,
-    extra_args: Optional[Sequence[str]] = None,
-) -> utils.RunResult | None:
-    """Runs tool on the given document.
-
-    if use_stdin is true then contents of the document is passed to the
-    tool via stdin.
-    """
-    if extra_args is None:
-        extra_args = []
-    if str(document.uri).startswith("vscode-notebook-cell"):
-        # TODO: Decide on if you want to skip notebook cells.
-        # Skip notebook cells
-        return None
-
-    if utils.is_stdlib_file(document.path):
-        # TODO: Decide on if you want to skip standard library files.
-        # Skip standard library python files.
-        return None
-
-    # deep copy here to prevent accidentally updating global settings.
-    settings = copy.deepcopy(_get_settings_by_document(document))
-
-    code_workspace = settings["workspaceFS"]
-    cwd = settings["cwd"]
-
-    use_path = False
-    use_rpc = False
-    if settings["path"]:
-        # 'path' setting takes priority over everything.
-        use_path = True
-        argv = settings["path"]
-    elif settings["interpreter"] and not utils.is_current_interpreter(
-        settings["interpreter"][0]
-    ):
-        # If there is a different interpreter set use JSON-RPC to the subprocess
-        # running under that interpreter.
-        argv = [TOOL_MODULE]
-        use_rpc = True
-    else:
-        # if the interpreter is same as the interpreter running this
-        # process then run as module.
-        argv = [TOOL_MODULE]
-
-    argv += TOOL_ARGS + settings["args"] + extra_args
-
-    if use_stdin:
-        # TODO: update these to pass the appropriate arguments to provide document contents
-        # to tool via stdin.
-        # For example, for pylint args for stdin looks like this:
-        #     pylint --from-stdin 
-        # Here `--from-stdin` path is used by pylint to make decisions on the file contents
-        # that are being processed. Like, applying exclusion rules.
-        # It should look like this when you pass it:
-        #     argv += ["--from-stdin", document.path]
-        # Read up on how your tool handles contents via stdin. If stdin is not supported use
-        # set use_stdin to False, or provide path, what ever is appropriate for your tool.
-        argv += []
-    else:
-        argv += [document.path]
-
-    if use_path:
-        # This mode is used when running executables.
-        log_to_output(" ".join(argv))
-        log_to_output(f"CWD Server: {cwd}")
-        result = utils.run_path(
-            argv=argv,
-            use_stdin=use_stdin,
-            cwd=cwd,
-            source=document.source.replace("\r\n", "\n"),
-        )
-        if result.stderr:
-            log_to_output(result.stderr)
-    elif use_rpc:
-        # This mode is used if the interpreter running this server is different from
-        # the interpreter used for running this server.
-        log_to_output(" ".join(settings["interpreter"] + ["-m"] + argv))
-        log_to_output(f"CWD Linter: {cwd}")
-
-        result = jsonrpc.run_over_json_rpc(
-            workspace=code_workspace,
-            interpreter=settings["interpreter"],
-            module=TOOL_MODULE,
-            argv=argv,
-            use_stdin=use_stdin,
-            cwd=cwd,
-            source=document.source,
-        )
-        if result.exception:
-            log_error(result.exception)
-            result = utils.RunResult(result.stdout, result.stderr)
-        elif result.stderr:
-            log_to_output(result.stderr)
-    else:
-        # In this mode the tool is run as a module in the same process as the language server.
-        log_to_output(" ".join([sys.executable, "-m"] + argv))
-        log_to_output(f"CWD Linter: {cwd}")
-        # This is needed to preserve sys.path, in cases where the tool modifies
-        # sys.path and that might not work for this scenario next time around.
-        with utils.substitute_attr(sys, "path", sys.path[:]):
-            try:
-                # TODO: `utils.run_module` is equivalent to running `python -m `.
-                # If your tool supports a programmatic API then replace the function below
-                # with code for your tool. You can also use `utils.run_api` helper, which
-                # handles changing working directories, managing io streams, etc.
-                # Also update `_run_tool` function and `utils.run_module` in `lsp_runner.py`.
-                result = utils.run_module(
-                    module=TOOL_MODULE,
-                    argv=argv,
-                    use_stdin=use_stdin,
-                    cwd=cwd,
-                    source=document.source,
-                )
-            except Exception:
-                log_error(traceback.format_exc(chain=True))
-                raise
-        if result.stderr:
-            log_to_output(result.stderr)
-
-    log_to_output(f"{document.uri} :\r\n{result.stdout}")
-    return result
-
-
-def _run_tool(extra_args: Sequence[str]) -> utils.RunResult:
-    """Runs tool."""
-    # deep copy here to prevent accidentally updating global settings.
-    settings = copy.deepcopy(_get_settings_by_document(None))
-
-    code_workspace = settings["workspaceFS"]
-    cwd = settings["workspaceFS"]
-
-    use_path = False
-    use_rpc = False
-    if len(settings["path"]) > 0:
-        # 'path' setting takes priority over everything.
-        use_path = True
-        argv = settings["path"]
-    elif len(settings["interpreter"]) > 0 and not utils.is_current_interpreter(
-        settings["interpreter"][0]
-    ):
-        # If there is a different interpreter set use JSON-RPC to the subprocess
-        # running under that interpreter.
-        argv = [TOOL_MODULE]
-        use_rpc = True
-    else:
-        # if the interpreter is same as the interpreter running this
-        # process then run as module.
-        argv = [TOOL_MODULE]
-
-    argv += extra_args
-
-    if use_path:
-        # This mode is used when running executables.
-        log_to_output(" ".join(argv))
-        log_to_output(f"CWD Server: {cwd}")
-        result = utils.run_path(argv=argv, use_stdin=True, cwd=cwd)
-        if result.stderr:
-            log_to_output(result.stderr)
-    elif use_rpc:
-        # This mode is used if the interpreter running this server is different from
-        # the interpreter used for running this server.
-        log_to_output(" ".join(settings["interpreter"] + ["-m"] + argv))
-        log_to_output(f"CWD Linter: {cwd}")
-        result = jsonrpc.run_over_json_rpc(
-            workspace=code_workspace,
-            interpreter=settings["interpreter"],
-            module=TOOL_MODULE,
-            argv=argv,
-            use_stdin=True,
-            cwd=cwd,
-        )
-        if result.exception:
-            log_error(result.exception)
-            result = utils.RunResult(result.stdout, result.stderr)
-        elif result.stderr:
-            log_to_output(result.stderr)
-    else:
-        # In this mode the tool is run as a module in the same process as the language server.
-        log_to_output(" ".join([sys.executable, "-m"] + argv))
-        log_to_output(f"CWD Linter: {cwd}")
-        # This is needed to preserve sys.path, in cases where the tool modifies
-        # sys.path and that might not work for this scenario next time around.
-        with utils.substitute_attr(sys, "path", sys.path[:]):
-            try:
-                # TODO: `utils.run_module` is equivalent to running `python -m `.
-                # If your tool supports a programmatic API then replace the function below
-                # with code for your tool. You can also use `utils.run_api` helper, which
-                # handles changing working directories, managing io streams, etc.
-                # Also update `_run_tool_on_document` function and `utils.run_module` in `lsp_runner.py`.
-                result = utils.run_module(
-                    module=TOOL_MODULE, argv=argv, use_stdin=True, cwd=cwd
-                )
-            except Exception:
-                log_error(traceback.format_exc(chain=True))
-                raise
-        if result.stderr:
-            log_to_output(result.stderr)
-
-    log_to_output(f"\r\n{result.stdout}\r\n")
-    return result
-
-
 # *****************************************************
 # Logging and notification.
 # *****************************************************
diff --git a/server/src/parser/parser.py b/server/src/parser/parser.py
index 0481917..4442313 100644
--- a/server/src/parser/parser.py
+++ b/server/src/parser/parser.py
@@ -1,39 +1,28 @@
 from pathlib import Path
-from lark import Lark, Tree, Token, UnexpectedInput
+from lark import Lark
 
-tcl_parser = Lark.open(
-    Path(__file__).parent.joinpath("tcl.lark"), parser="lalr", propagate_positions=True
-)
+# Grammar einlesen
+with open(Path(__file__).parent.joinpath("tcl.lark"), encoding="utf-8") as f:
+    grammar = f.read()
+
+# LALR-Parser instanziieren
+parser = Lark(grammar, parser="lalr", propagate_positions=True)
 
 
-def parse_tcl(source: str):
-    try:
-        tree = tcl_parser.parse(source)
-        return tree, []
-    except UnexpectedInput as e:
-        # e.line, e.column enthalten Position
-        diagnostic = {
-            "range": {
-                "start": {"line": e.line - 1, "character": e.column - 1},
-                "end": {"line": e.line - 1, "character": e.column},
-            },
-            "message": f"Syntaxfehler: {e}",
-            "severity": 1,  # Error
-        }
-        return None, [diagnostic]
+def parse(source: str):
+    return parser.parse(source)
 
 
-def format_tree(tree):
-    def fmt(node, indent=0):
-        if isinstance(node, Token):
-            return node.value
-        elif isinstance(node, Tree):
-            if node.data == "cmd":
-                parts = []
-                for child in node.children:
-                    parts.append(fmt(child, indent))
-                return " ".join(parts) + ";\n"
-            # weitere Node-Typen …
-        return ""
-
-    return "".join(fmt(tree))
+if __name__ == "__main__":
+    samples = [
+        "set x 42",
+        "set myArray(1) 123",
+        "set myArray2(1,2,3,$string) 123",
+        "proc myProc {} {}",
+        "proc myProc {arg} {}",
+        "proc myProc {arg arg2} {}",
+        "proc myProc {arg {arg2 test}} {}",
+    ]
+    for s in samples:
+        tree = parse(s)
+        print(tree.pretty())
diff --git a/server/src/parser/tcl.lark b/server/src/parser/tcl.lark
index 83734cd..0097c91 100644
--- a/server/src/parser/tcl.lark
+++ b/server/src/parser/tcl.lark
@@ -1,250 +1,61 @@
-// tcl.lark
+// Lark grammar for Tcl scripts with proc definitions and optional args
+
+%import common.CNAME        -> IDENTIFIER
+%import common.INT          -> INT
+%import common.FLOAT        -> FLOAT
+%import common.ESCAPED_STRING -> STRING_LITERAL
+%import common.WS_INLINE    -> WS_INLINE
+%import common.NEWLINE      -> NEWLINE
+
+// Ignore only spaces, tabs; treat newlines as separators
+%ignore WS_INLINE
+// Tcl comments
+%ignore COMMENT
 
 start: script
 script: statement*
 
-statement: function_declaration
-         | top_level
+statement: command (";" | NEWLINE)?
 
-// --- Function declarations (zero or more) ---
-function_declaration: ("proc" IDENTIFIER "{" function_args "}" "{" function_body "}")*
+command: set_command
+       | proc_command
+       | r_break
+       | r_continue
 
-// --- Function arguments (0..n blocks) ---
-function_args: ("{" IDENTIFIER "}")*
+proc_command: "proc" IDENTIFIER "{" argument_list "}" body_block
 
-// --- Function body: zero or more statements ---
-function_body: (assignment_statement
-               | print_statement
-               | input_statement ";"
-               | if_statement
-               | for_statement
-               | while_statement
-               | switch_statement
-               | return_statement
-               | grouping ";"
-               )*
+argument_list: [argument (argument)*]
 
-// --- If / elseif / else in functions ---
-if_statement: if_header function_body "}" elseif_clause
+argument: IDENTIFIER                             -> required_arg
+        | "{" IDENTIFIER (value)? "}"  -> optional_arg
 
-elseif_clause: elseif_header function_body "}" elseif_clause
-             | else_clause
 
-else_clause: else_header function_body "}"
-           |    /* empty */
 
-// --- Switch in functions ---
-switch_statement: switch_header case_clause "}"
-
-case_clause: case_header function_body "}" case_clauses
-
-case_clauses: case_header function_body "}" case_clauses
-            | default_clause
-
-default_clause: default_header function_body "}"
-               |    /* empty */
-
-// --- For / While in functions ---
-for_statement: for_header loop_body_in_function "}"
-while_statement: while_header loop_body_in_function "}"
-
-loop_body_in_function: (break_statement
-                      | continue_statement
-                      | assignment_statement
-                      | input_statement ";"
-                      | print_statement
-                      | return_statement
-                      | if_loop_statement
-                      | switch_loop_statement
-                      | for_statement
-                      | while_statement
-                      | grouping ";"
-                      )*
-
-if_loop_statement: if_header loop_body_in_function "}" elseif_loop_clause
-
-elseif_loop_clause: elseif_header loop_body_in_function "}" elseif_loop_clause
-                  | else_loop_clause
-
-else_loop_clause: else_header loop_body_in_function "}"
-                |    /* empty */
-
-switch_loop_statement: switch_header case_loop_clause "}"
-
-case_loop_clause: case_header loop_body_in_function "}" case_loop_clauses
-
-case_loop_clauses: case_header loop_body_in_function "}" case_loop_clauses
-                 | default_loop_clause
-
-default_loop_clause: default_header loop_body_in_function "}"
-                   |    /* empty */
-
-// --- Top-level (module) statements (zero or more) ---
-top_level: (assignment_statement
-          | for_main
-          | input_statement ";"
-          | if_main
-          | print_statement
-          | while_main
-          | switch_main
-          | grouping ";"
-          )*
-
-// --- If / Else at top level ---
-if_main: if_header main_body "}" elseif_main_clause
-
-elseif_main_clause: elseif_header main_body "}" elseif_main_clause
-                  | else_main_clause
-
-else_main_clause: else_header main_body "}"
-                |    /* empty */
-
-main_body: (assignment_statement
-          | for_main
-          | input_statement ";"
-          | if_main
-          | print_statement
-          | while_main
-          | switch_main
-          | grouping ";"
-          )*
-
-// --- Switch at top level ---
-switch_main: switch_header main_case_clause "}"
-
-main_case_clause: case_header main_body "}" main_case_clauses
-
-main_case_clauses: case_header main_body "}" main_case_clauses
-                 | default_main_clause
-
-default_main_clause: default_header main_body "}"
-                   |    /* empty */
-
-// --- For / While at top level ---
-for_main: for_header main_loop_body "}"
-while_main: while_header main_loop_body "}"
-
-main_loop_body: (assignment_statement
-               | for_main
-               | input_statement ";"
-               | if_loop_main
-               | print_statement
-               | while_main
-               | switch_loop_main
-               | break_statement
-               | continue_statement
-               | grouping ";"
-               )*
-
-if_loop_main: if_header main_loop_body "}" elseif_loop_main
-
-elseif_loop_main: elseif_header main_loop_body "}" elseif_loop_main
-                | else_loop_main
-
-else_loop_main: else_header main_loop_body "}"
-              |    /* empty */
-
-switch_loop_main: switch_header case_loop_main "}"
-
-case_loop_main: case_header main_loop_body "}" case_loop_main_clauses
-
-case_loop_main_clauses: case_header main_loop_body "}" case_loop_main_clauses
-                      | default_loop_main
-
-default_loop_main: default_header main_loop_body "}"
-                 |    /* empty */
-
-// --- Primitive statements ---
-print_statement: "puts" assignment ";"
-input_statement: "gets" "stdin"
-assignment_statement: "set" IDENTIFIER index access_value ";"
-
-grouping: "[" grouping_aux "]"
-grouping_aux: expression
-            | IDENTIFIER function_param
-            | input_statement
-            | "array" array_aux
-
-array_aux: "size" IDENTIFIER
-         | "exists" IDENTIFIER
-
-function_param: "{" param_aux "}"
-param_aux: assignment_value
-          | expression
-
-assignment_value: value
-                | "$" IDENTIFIER index
-                | grouping
-
-index: "(" index_value ")"
-index_value: value
-            | grouping
-            | "$" IDENTIFIER index
-
-value: STRING_LITERAL
-     | INT
+value: STRING_LITERAL 
      | FLOAT
+     | INT
+     | IDENTIFIER
+     | list
 
-increment: INT?
+list: LBRACE [value (WS_INLINE value)*] RBRACE
 
-break_statement: "break" ";"
-continue_statement: "continue" ";"
-return_statement: "return" return_value ";"
-return_value: assignment_value?
+body_block: LBRACE body_content RBRACE      -> body
+body_content: ( /[^{}]+/ | body_block )*    // allow nested braces
 
-expr_command: "expr" "{" expression "}"
-for_init: INT
-        | "$" IDENTIFIER index
-        | expr_command
+set_command: "set" IDENTIFIER index? value
+// Index: parentheses with comma-separated items
+index: "(" index_item ("," index_item)* ")"
+index_item: INT                                  -> index_int
+          | STRING_VAR                       -> index_string
+          | "$" IDENTIFIER index?                -> nested_index
 
-// --- Headers for control structures ---
-if_header: "if" "{" expression "}" "then" "{"
-elseif_header: "elseif" "{" expression "}" "then" "{"
-else_header: "else" "{"
-switch_header: "switch" "$" IDENTIFIER index "{"
-case_header: "case" INT "{"
-default_header: "default" "{"
-for_header: "for" "{" for_declaration "}" "{" expression "}" "{" "incr" IDENTIFIER increment "}" "{"
-for_declaration: "set" IDENTIFIER for_init
-while_header: "while" "{" expression "}" "{"
 
-// --- Expression grammar ---
-expression: or_expression
-or_expression: or_expression "||" and_expression
-             | and_expression
-and_expression: and_expression "&&" equality_expression
-              | equality_expression
-equality_expression: equality_expression eq_ops relational_expression
-                   | relational_expression
-relational_expression: relational_expression rel_ops additive_expression
-                     | additive_expression
-additive_expression: additive_expression add_ops multiplicative_expression
-                   | multiplicative_expression
-multiplicative_expression: multiplicative_expression mul_ops power_expression
-                         | power_expression
-power_expression: power_expression "**" unary_expression
-                | unary_expression
-unary_expression: unary_ops unary_expression
-                | term
-term: "$" IDENTIFIER index
-    | grouping
-    | value
-    | "(" or_expression ")"
+r_break: "break"
+r_continue: "continue"
 
-eq_ops: "eq" | "==" | "ne" | "!="
-rel_ops: ">" | "<" | ">=" | "<="
-add_ops: "+" | "-"
-mul_ops: "*" | "/" | "%"
-unary_ops: "-" | "!"
+LBRACE: "{"
+RBRACE: "}"
 
-// --- Terminals ---
-IDENTIFIER: /[a-zA-Z_][a-zA-Z0-9_]*/
-INT: /-?[0-9]+/
-FLOAT: /[0-9]+\.[0-9]+/
-STRING_LITERAL: /"([^"\\]|\\.)*"/
+STRING_VAR: /(?:[^"\\]|\\.)+/
 COMMENT: /#[^\r\n]*/
 
-
-%ignore /[ \t]+/    // spaces & tabs
-%ignore COMMENT
-%ignore _NL  
\ No newline at end of file