using System.Collections.Frozen; using AIStudio.Tools.PluginSystem; namespace AIStudio.Tools; /// /// The languages AI Studio recognizes behind the opening fence of a code block, so that the block /// can be saved as a file of its own. /// /// /// Models do not agree on how they spell a language: Python comes as python, py, or Python3. The /// catalog therefore knows the usual aliases, the way GitHub Linguist and highlight.js spell them, /// in any case. The formats AI Studio writes as formats of their own, such as html, latex, markdown, /// or csv, are missing on purpose: FileExportFormatExtensions.TryFromCodeFenceLanguage reads them /// first, and a file in one of them is treated apart from code, it takes the sources of the answer /// along, for example. Each language has exactly one extension; whoever needs another one renames /// the file. /// public static class SourceCodeLanguages { private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SourceCodeLanguages).Namespace, nameof(SourceCodeLanguages)); /// /// What a model writes behind the fence when it means plain text. /// private static readonly FrozenSet PLAIN_TEXT_ALIASES = new[] { "text", "txt", "plaintext", "plain", "none", "nohighlight" }.ToFrozenSet(StringComparer.OrdinalIgnoreCase); private static readonly FrozenDictionary LANGUAGES_BY_ALIAS = BuildCatalog( [ (new("ABAP", ".abap"), ["abap"]), (new("Ada", ".adb"), ["ada"]), (new("Apache", ".conf"), ["apache", "apacheconf"]), (new("Arduino", ".ino"), ["arduino", "ino"]), (new("AsciiDoc", ".adoc"), ["asciidoc", "adoc"]), (new("Assembly", ".asm"), ["asm", "assembly", "nasm", "x86asm"]), (new("AWK", ".awk"), ["awk"]), (new("Batch", ".bat", NeedsWindowsLineEndings: true), ["bat", "batch", "batchfile", "cmd", "dos"]), (new("BibTeX", ".bib"), ["bibtex", "biblatex", "bib"]), (new("Bicep", ".bicep"), ["bicep"]), (new("C", ".c"), ["c"]), (new("C#", ".cs"), ["csharp", "cs", "c#"]), (new("C++", ".cpp"), ["cpp", "c++", "cc", "cxx"]), (new("Clojure", ".clj"), ["clojure", "clj"]), (new("CMake", string.Empty, "CMakeLists.txt"), ["cmake"]), (new("COBOL", ".cbl"), ["cobol"]), (new("Containerfile", string.Empty, "Containerfile"), ["containerfile"]), (new("CSS", ".css"), ["css"]), (new("CUDA", ".cu"), ["cuda", "cu"]), (new("Cypher", ".cypher"), ["cypher"]), (new("D", ".d"), ["d"]), (new("Dart", ".dart"), ["dart"]), (new("Diff", ".diff"), ["diff", "udiff"]), (new("Dockerfile", string.Empty, "Dockerfile"), ["dockerfile", "docker"]), (new("Dotenv", ".env"), ["dotenv", "env"]), (new("Elixir", ".ex"), ["elixir", "ex"]), (new("Elm", ".elm"), ["elm"]), (new("Emacs Lisp", ".el"), ["elisp", "emacs-lisp"]), (new("Erlang", ".erl"), ["erlang", "erl"]), (new("F#", ".fs"), ["fsharp", "fs", "f#"]), (new("Fish", ".fish"), ["fish"]), (new("Fortran", ".f90"), ["fortran", "f90", "f95", "f03", "f08"]), (new("GDScript", ".gd"), ["gdscript", "gd"]), (new("Gherkin", ".feature"), ["gherkin", "cucumber", "feature"]), (new("GLSL", ".glsl"), ["glsl"]), (new("Gnuplot", ".gp"), ["gnuplot"]), (new("Go", ".go"), ["go", "golang"]), (new("GraphQL", ".graphql"), ["graphql", "gql"]), (new("Graphviz", ".dot"), ["graphviz", "dot", "gv"]), (new("Groovy", ".groovy"), ["groovy", "gradle"]), (new("Haskell", ".hs"), ["haskell", "hs"]), (new("HCL", ".hcl"), ["hcl"]), (new("HLSL", ".hlsl"), ["hlsl"]), (new("HTTP", ".http"), ["http"]), (new("INI", ".ini"), ["ini", "dosini"]), (new("Java", ".java"), ["java"]), (new("Java Properties", ".properties"), ["properties", "java-properties"]), (new("JavaScript", ".js"), ["javascript", "js", "node", "nodejs"]), (new("Jenkinsfile", string.Empty, "Jenkinsfile"), ["jenkinsfile", "jenkins"]), (new("Jinja", ".j2"), ["jinja", "jinja2", "j2"]), (new("JSON", ".json"), ["json"]), (new("JSON5", ".json5"), ["json5"]), (new("JSONC", ".jsonc"), ["jsonc"]), (new("JSX", ".jsx"), ["jsx"]), (new("Julia", ".jl"), ["julia", "jl"]), (new("Kotlin", ".kt"), ["kotlin", "kt"]), (new("Less", ".less"), ["less"]), (new("Lisp", ".lisp"), ["lisp", "commonlisp", "common-lisp"]), (new("Log", ".log"), ["log"]), (new("Lua", ".lua"), ["lua"]), (new("Makefile", string.Empty, "Makefile"), ["makefile", "make"]), (new("Mathematica", ".wl"), ["mathematica", "wolfram", "wl"]), (new("MATLAB", ".m"), ["matlab"]), (new("Mermaid", ".mmd"), ["mermaid", "mmd"]), (new("Modelica", ".mo"), ["modelica"]), (new("Mojo", ".mojo"), ["mojo"]), (new("Nginx", ".conf"), ["nginx", "nginxconf"]), (new("Nim", ".nim"), ["nim"]), (new("Nix", ".nix"), ["nix"]), (new("Objective-C", ".m"), ["objectivec", "objective-c", "objc", "obj-c"]), (new("OCaml", ".ml"), ["ocaml", "ml"]), (new("Octave", ".m"), ["octave"]), (new("Pascal", ".pas"), ["pascal", "delphi", "objectpascal", "pas"]), (new("Patch", ".patch"), ["patch"]), (new("Perl", ".pl"), ["perl", "pl"]), (new("PHP", ".php"), ["php"]), (new("PlantUML", ".puml"), ["plantuml", "puml"]), (new("PowerShell", ".ps1", NeedsByteOrderMark: true), ["powershell", "ps1", "pwsh", "posh"]), (new("Prisma", ".prisma"), ["prisma"]), (new("Prolog", ".pl"), ["prolog"]), (new("Protocol Buffers", ".proto"), ["protobuf", "proto", "proto3"]), (new("Python", ".py"), ["python", "py", "python3", "py3"]), (new("R", ".R"), ["r"]), (new("Racket", ".rkt"), ["racket", "rkt"]), (new("Razor", ".razor"), ["razor"]), (new("reStructuredText", ".rst"), ["restructuredtext", "rst"]), (new("Ruby", ".rb"), ["ruby", "rb"]), (new("Rust", ".rs"), ["rust", "rs"]), (new("SAS", ".sas"), ["sas"]), (new("Sass", ".sass"), ["sass"]), (new("Scala", ".scala"), ["scala"]), (new("Scheme", ".scm"), ["scheme", "scm"]), (new("SCSS", ".scss"), ["scss"]), (new("sed", ".sed"), ["sed"]), (new("Shell", ".sh"), ["bash", "sh", "shell", "ksh"]), (new("Solidity", ".sol"), ["solidity", "sol"]), (new("SPARQL", ".rq"), ["sparql", "rq"]), (new("SQL", ".sql"), ["sql", "mysql", "mssql", "postgresql", "postgres", "pgsql", "plpgsql", "plsql", "sqlite", "tsql"]), (new("Stata", ".do"), ["stata"]), (new("Svelte", ".svelte"), ["svelte"]), (new("SVG", ".svg"), ["svg"]), (new("Swift", ".swift"), ["swift"]), (new("SystemVerilog", ".sv"), ["systemverilog", "sv"]), (new("Tcl", ".tcl"), ["tcl"]), (new("Terraform", ".tf"), ["terraform", "tf"]), (new("TOML", ".toml"), ["toml"]), (new("TSX", ".tsx"), ["tsx"]), (new("Turtle", ".ttl"), ["turtle", "ttl"]), (new("TypeScript", ".ts"), ["typescript", "ts"]), (new("Typst", ".typ"), ["typst", "typ"]), (new("VB.NET", ".vb"), ["vbnet", "vb.net", "vb", "visualbasic"]), (new("VBScript", ".vbs"), ["vbscript", "vbs"]), (new("Verilog", ".v"), ["verilog"]), (new("VHDL", ".vhd"), ["vhdl", "vhd"]), (new("Vim script", ".vim"), ["vim", "vimscript", "viml"]), (new("Vue", ".vue"), ["vue"]), (new("WebAssembly", ".wat"), ["wat", "wast", "wasm"]), (new("XAML", ".xaml"), ["xaml"]), (new("XML", ".xml"), ["xml"]), (new("XSLT", ".xslt"), ["xslt", "xsl"]), (new("YAML", ".yaml"), ["yaml", "yml"]), (new("Zig", ".zig"), ["zig"]), (new("Zsh", ".zsh"), ["zsh"]), ]); /// /// Every alias the catalog knows, including the ones for plain text. /// internal static IEnumerable Aliases => [..PLAIN_TEXT_ALIASES, ..LANGUAGES_BY_ALIAS.Keys]; /// /// Every language the catalog knows. /// internal static IEnumerable Languages => LANGUAGES_BY_ALIAS.Values.Distinct(); /// /// Reads which language a model means when it names one behind the opening fence of a code block. /// /// /// Every code block becomes a file. A language we do not know keeps its name and becomes a text /// file, so the user still sees which block it is; a block without any language is plain text. /// /// The language behind the opening fence, as Markdig reads it into /// FencedCodeBlock.Info. /// The language, never NONE. public static SourceCodeLanguage FromCodeFenceLanguage(string? language) { var name = language?.Trim() ?? string.Empty; if (name.Length is 0 || PLAIN_TEXT_ALIASES.Contains(name)) return new(TB("Text"), ".txt"); return LANGUAGES_BY_ALIAS.TryGetValue(name, out var knownLanguage) ? knownLanguage : new(name, ".txt"); } private static FrozenDictionary BuildCatalog(IEnumerable<(SourceCodeLanguage Language, string[] Aliases)> languages) { // // Add throws for an alias named twice. A mistake in the list above therefore fails every test // which touches the catalog, instead of quietly hiding one of the two languages: // var catalog = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var (language, aliases) in languages) foreach (var alias in aliases) catalog.Add(alias, language); return catalog.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase); } }