nix-community.nixvim/lib/to-lua.nix

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

279 lines
8.8 KiB
Nix
Raw Normal View History

2024-01-25 15:43:06 +01:00
{ lib }:
rec {
# Whether the string is a reserved keyword in lua
isKeyword =
s:
2024-08-30 14:49:20 -05:00
lib.elem s [
"and"
"break"
"do"
"else"
"elseif"
"end"
"false"
"for"
"function"
"if"
"in"
"local"
"nil"
"not"
"or"
"repeat"
"return"
"then"
"true"
"until"
"while"
];
# Valid lua identifiers are not reserved keywords, do not start with a digit,
# and contain only letters, digits, and underscores.
isIdentifier = s: !(isKeyword s) && (builtins.match "[A-Za-z_][0-9A-Za-z_]*" s) == [ ];
# Alias for nixpkgs lib's `mkLuaInline`,
# but can also convert rawLua to lua-inline
mkInline = v: lib.generators.mkLuaInline (v.__raw or v);
# Whether the value is a lua-inline type
isInline = v: v._type or null == "lua-inline";
/**
Serialise a nix value as a lua object.
Useful for defining your own plugins or structured config.
# Type
```
toLuaObject :: Any -> String
```
*/
toLuaObject =
# toLua' with backwards-compatible options
toLua' { };
# toLua' with default options, aliased as toLuaObject at the top-level
toLua = toLua' { };
2024-01-25 15:43:06 +01:00
# Black functional magic that converts a bunch of different Nix types to their
# lua equivalents!
toLua' =
{
/**
If this option is true, attrsets like { __pretty = fn; val = ; }
will use fn to convert val to a lua representation.
*/
allowPrettyValues ? false,
/**
If this option is true, values like { __raw = "print('hi)"; }
will render as print('hi')
*/
allowRawValues ? true,
/**
If this option is true, attrsets like { "__rawKey__print('hi')" = "a"; }
will render as { [print('hi')] = "a"] }
*/
allowRawAttrKeys ? true,
/**
If this option is true, attrsets like { __unkeyed.1 = "a"; b = "b"; }
will render as { "a", b = "b" }
*/
allowUnkeyedAttrs ? true,
/**
If this option is true, attrsets like { a = "a"; b = "b"; }
will render as { a, b = "b" } instead of { ["a"] = "a", ["b"] = "b" }
*/
allowUnquotedAttrKeys ? true,
# TODO: smartQuoteStyle: use ' or " based on string content
# TODO: allowRawStrings: use [=[ ]=] strings for multiline strings
/**
If this option is true, attrsets like { a = null; b = ""; }
will render as { ["b"] = "" }
*/
removeNullAttrValues ? true,
/**
If this option is true, attrsets like { a = { }; b = [ ]; c = ""; }
will render as { ["c"] = "" }
*/
removeEmptyAttrValues ? true,
/**
If this option is true, lists like [ { } [ ] "" ]
will render as { "" }
*/
removeEmptyListEntries ? false,
/**
If this option is true, lists like [ null "" ]
will render as { "" }
*/
removeNullListEntries ? false,
/**
If this option is true, attrsets like { a.__empty = null; }
will render as { ["a"] = { } }, ignoring other filters such as removeEmptyAttrValues.
*/
allowExplicitEmpty ? true,
/**
If this option is true, attrsets like { __emptyString = "foo"; }
will render as { [""] = "foo" }.
This is deprecated: use an attrset like { "" = "foo"; } instead.
*/
allowLegacyEmptyStringAttr ? true,
/**
If this option is true, the output is indented with newlines for attribute sets and lists
*/
# TODO: make this true by default
multiline ? false,
/**
Initial indentation level
*/
indent ? "",
}:
let
# If any of these are options are set, we need to preprocess the value
needsPreprocessing =
allowExplicitEmpty
|| removeEmptyAttrValues
|| removeNullAttrValues
|| removeEmptyListEntries
|| removeNullListEntries;
# Slight optimization: only preprocess if we actually need to
preprocessValue = value: if needsPreprocessing then removeEmptiesRecursive value else value;
# Recursively filters `value`, removing any empty/null attrs (as configured)
# Does not recurse into "special" attrs, such as `__raw`
removeEmptiesRecursive =
value:
if allowPrettyValues && value ? __pretty && value ? val then
value
else if allowRawValues && value ? __raw then
value
else if isInline value then
value
2024-08-30 14:49:20 -05:00
else if lib.isDerivation value then
value
2024-08-30 14:49:20 -05:00
else if lib.isList value then
let
needsFiltering = removeNullListEntries || removeEmptyListEntries;
fn =
v: (removeNullListEntries -> (v != null)) && (removeEmptyListEntries -> (v != [ ] && v != { }));
v' = map removeEmptiesRecursive value;
in
2024-08-30 14:49:20 -05:00
if needsFiltering then lib.filter fn v' else v'
else if lib.isAttrs value then
lib.concatMapAttrs (
2024-01-25 15:43:06 +01:00
n: v:
let
v' = removeEmptiesRecursive v;
in
if removeNullAttrValues && v == null then
{ }
else if allowExplicitEmpty && v ? __empty then
{ ${n} = { }; }
# Optimisation: check if v is empty before evaluating v'
else if removeEmptyAttrValues && (v == [ ] || v == { }) then
{ }
else if removeEmptyAttrValues && (v' == [ ] || v' == { }) then
{ }
else
{ ${n} = v'; }
) value
else
value;
# Return the dict-style table key, formatted as per the config
toTableKey =
s:
2024-08-30 14:49:20 -05:00
if allowRawAttrKeys && lib.hasPrefix "__rawKey__" s then
"[${lib.removePrefix "__rawKey__" s}]"
else if allowUnquotedAttrKeys && isIdentifier s then
s
else if allowLegacyEmptyStringAttr && s == "__emptyString" then
2024-08-30 14:49:20 -05:00
lib.trace ''nixvim(toLua): __emptyString is deprecated, just use an attribute named "".'' (
toTableKey ""
)
else
"[${go "" s}]";
# The main recursive function implementing `toLua`:
# Visit a value and print it as lua, with the specified indentation.
# Recursively visits child values with increasing indentation.
go =
indent: v:
let
# Calculate the start/end padding, including any linebreaks,
# based on multiline config and current indentation.
introSpace = if multiline then "\n${indent} " else " ";
outroSpace = if multiline then "\n${indent}" else " ";
in
if v == null then
"nil"
2024-08-30 14:49:20 -05:00
else if lib.isInt v then
toString v
2024-08-30 14:49:20 -05:00
# toString loses precision on floats, so we use toJSON instead.
# It can output an exponent form supported by lua.
2024-08-30 14:49:20 -05:00
else if lib.isFloat v then
builtins.toJSON v
2024-08-30 14:49:20 -05:00
else if lib.isBool v then
lib.boolToString v
else if lib.isPath v || lib.isDerivation v then
go indent "${v}"
2024-08-30 14:49:20 -05:00
else if lib.isString v then
# TODO: support lua's escape sequences, literal string, and content-appropriate quote style
# See https://www.lua.org/pil/2.4.html
# and https://www.lua.org/manual/5.1/manual.html#2.1
# and https://github.com/NixOS/nixpkgs/blob/00ba4c2c35f5e450f28e13e931994c730df05563/lib/generators.nix#L351-L365
builtins.toJSON v
else if v == [ ] || v == { } then
"{ }"
2024-08-30 14:49:20 -05:00
else if lib.isFunction v then
abort "nixvim(toLua): Unexpected function: " + lib.generators.toPretty { } v
else if lib.isList v then
"{"
+ introSpace
+ lib.concatMapStringsSep ("," + introSpace) (go (indent + " ")) v
+ outroSpace
+ "}"
else if lib.isAttrs v then
# apply pretty values if allowed
if allowPrettyValues && v ? __pretty && v ? val then
v.__pretty v.val
# apply raw values if allowed
else if allowRawValues && v ? __raw then
# TODO: deprecate in favour of inline-lua
v.__raw
else if isInline v then
# TODO: apply indentation to multiline raw values?
"(${v.expr})"
else
"{"
+ introSpace
2024-08-30 14:49:20 -05:00
+ lib.concatStringsSep ("," + introSpace) (
lib.mapAttrsToList (
name: value:
(if allowUnkeyedAttrs && lib.hasPrefix "__unkeyed" name then "" else toTableKey name + " = ")
2024-08-30 14:49:20 -05:00
+ lib.addErrorContext "while evaluating an attribute `${name}`" (go (indent + " ") value)
) v
)
+ outroSpace
+ "}"
else
abort "nixvim(toLua): should never happen (v = ${v})";
in
value: go indent (preprocessValue value);
2024-01-25 15:43:06 +01:00
}