# # This derivation creates a Nix file that describes the Nix module that needs to be instantiated # # The create file is of the form: # # { # "<rust-analyzer.option.name>" = { # description = "<option description>"; # type = { # kind = "<name of the type>"; # # Other values depending on the kind, like values for enum or subTypes for oneOf # }; # }; # } # { lib, rust-analyzer, writeText, pandoc, runCommand, }: let packageJSON = "${rust-analyzer.src}/editors/code/package.json"; options = (lib.importJSON packageJSON).contributes.configuration; generatedStart = lib.lists.findFirstIndex ( e: e == { title = "$generated-start"; } ) (throw "missing generated start") options; generatedEnd = lib.lists.findFirstIndex ( e: e == { title = "$generated-end"; } ) (throw "missing generated end") options; # Extract only the generated properties, removing vscode specific options rustAnalyzerProperties = lib.lists.sublist (generatedStart + 1) ( generatedEnd - generatedStart - 1 ) options; mkRustAnalyzerOptionType = nullable: property_name: { type, enum ? null, minimum ? null, maximum ? null, items ? null, anyOf ? null, properties ? null, # Not used in the function, but anyOf values contain it enumDescriptions ? null, }@property: if enum != null then { kind = "enum"; values = enum; } else if anyOf != null then let possibleTypes = lib.filter (sub: !(sub.type == "null" && nullable)) anyOf; in { kind = "oneOf"; subTypes = builtins.map ( t: mkRustAnalyzerOptionType nullable "${property_name}-sub" t ) possibleTypes; } else if lib.isList type then ( if lib.head type == "null" then assert lib.assertMsg ( lib.length type == 2 ) "Lists starting with null are assumed to mean nullOr, so length 2"; let innerType = property // { type = lib.elemAt type 1; }; inner = mkRustAnalyzerOptionType nullable "${property_name}-inner" innerType; in assert lib.assertMsg nullable "nullOr types are not yet handled"; inner else let innerTypes = builtins.map ( t: mkRustAnalyzerOptionType nullable "${property_name}-inner" (property // { type = t; }) ) type; in { kind = "oneOf"; subTypes = innerTypes; } ) else if type == "array" then { kind = "list"; item = mkRustAnalyzerOptionType false "${property_name}-item" items; } else if type == "number" || type == "integer" then { kind = type; inherit minimum maximum; } else if type == "object" && properties != null then { kind = "submodule"; options = lib.mapAttrs ( name: value: mkRustAnalyzerOptionType false "${property_name}.${name}" value ) properties; } else if lib.elem type [ "object" "string" "boolean" ] then { kind = type; } else throw "Unhandled value in ${property_name}: ${lib.generators.toPretty { } property}"; mkRustAnalyzerOption = property_name: { # List all possible values so that we are sure no new values are introduced default, markdownDescription, enum ? null, enumDescriptions ? null, anyOf ? null, minimum ? null, maximum ? null, items ? null, # TODO: add this in the documentation ? uniqueItems ? null, type ? null, }: let filteredMarkdownDesc = # If there is a risk that the string contains an heading filter it out if lib.hasInfix "# " markdownDescription then builtins.readFile ( runCommand "filtered-documentation" { inherit markdownDescription; } '' ${lib.getExe pandoc} -o $out -t markdown \ --lua-filter=${./heading_filter.lua} <<<"$markdownDescription" '' ) else markdownDescription; enumDesc = values: descriptions: let valueDesc = builtins.map ({ fst, snd }: ''- ${fst}: ${snd}'') ( lib.lists.zipLists values descriptions ); in '' ${filteredMarkdownDesc} Values: ${builtins.concatStringsSep "\n" valueDesc} ''; in { type = mkRustAnalyzerOptionType true property_name { inherit type enum minimum maximum items anyOf enumDescriptions ; }; pluginDefault = default; description = if enum == null && (anyOf == null || builtins.all (subProp: !(lib.hasAttr "enum" subProp)) anyOf) then '' ${filteredMarkdownDesc} '' else if enum != null then assert lib.assertMsg (anyOf == null) "enum + anyOf types are not yet handled"; enumDesc enum enumDescriptions else let subEnums = lib.filter (lib.hasAttr "enum") anyOf; subEnum = assert lib.assertMsg ( lib.length subEnums == 1 ) "anyOf types may currently only contain a single enum"; lib.head subEnums; in enumDesc subEnum.enum subEnum.enumDescriptions; }; rustAnalyzerOptions = builtins.map ( v: let props = lib.attrsToList v.properties; prop = assert lib.assertMsg ( lib.length props == 1 ) "Rust analyzer configuration items are only supported with a single element"; lib.head props; in { "${prop.name}" = mkRustAnalyzerOption prop.name prop.value; } ) rustAnalyzerProperties; in writeText "rust-analyzer-options.nix" ( "# WARNING: DO NOT EDIT\n" + "# This file is generated with packages.<system>.rust-analyzer-options, which is run automatically by CI\n" + (lib.generators.toPretty { } (lib.mergeAttrsList rustAnalyzerOptions)) )