diff --git a/lib/default.nix b/lib/default.nix index 51dc36c8..b0897f50 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -24,7 +24,7 @@ lib.makeExtensible ( modules = call ./modules.nix { inherit flake; }; options = call ./options.nix { }; plugins = call ./plugins { }; - utils = call ./utils.nix { inherit _nixvimTests; }; + utils = call ./utils.nix { inherit _nixvimTests; } // call ./utils.internal.nix { }; # Top-level helper aliases: # TODO: deprecate some aliases diff --git a/lib/utils.internal.nix b/lib/utils.internal.nix new file mode 100644 index 00000000..4940e206 --- /dev/null +++ b/lib/utils.internal.nix @@ -0,0 +1,348 @@ +{ lib }: +rec { + # Whether a string contains something other than whitespaces + hasContent = str: builtins.match "[[:space:]]*" str == null; + + # Concatenate a list of strings, adding a newline at the end of each one, + # but skipping strings containing only whitespace characters + concatNonEmptyLines = lines: lib.concatLines (builtins.filter hasContent lines); + + /** + Add a prefix to the keys of an attrs. + + # Example + ```nix + applyPrefixToAttrs "prefix_" { foo = 1; bar = 2; } + => { prefix_foo = 1; prefix_bar = 2; } + ``` + + # Type + + ``` + applyPrefixToAttrs :: String -> AttrSet -> AttrSet + ``` + */ + applyPrefixToAttrs = prefix: lib.mapAttrs' (n: lib.nameValuePair (prefix + n)); + + /* + Convert a string from camelCase to snake_case + Type: string -> string + */ + toSnakeCase = + let + splitByWords = builtins.split "([A-Z])"; + processWord = s: if lib.isString s then s else "_" + lib.toLower (lib.elemAt s 0); + in + string: + let + words = splitByWords string; + in + lib.concatStrings (map processWord words); + + /** + Those helpers control the lua sections split in `pre, content, post` + */ + mkBeforeSection = lib.mkOrder 300; + mkAfterSection = lib.mkOrder 2000; + + /** + Capitalize a string by making the first character uppercase. + + # Example + + ```nix + upperFirstChar "hello, world!" + => "Hello, world!" + ``` + + # Type + + ``` + upperFirstChar :: String -> String + ``` + */ + upperFirstChar = + s: + let + first = lib.substring 0 1 s; + rest = lib.substring 1 (lib.stringLength s) s; + result = (lib.toUpper first) + rest; + in + lib.optionalString (s != "") result; + + mkIfNonNull' = x: y: (lib.mkIf (x != null) y); + + mkIfNonNull = x: (mkIfNonNull' x x); + + ifNonNull' = x: y: if (x == null) then null else y; + + /** + Convert the given string into a literalExpression mkRaw. + + For use in option documentation, such as examples and defaults. + + # Example + + ```nix + literalLua "print('hi')" + => literalExpression ''lib.nixvim.mkRaw "print('hi')"'' + => { + _type = "literalExpression"; + text = ''lib.nixvim.mkRaw "print('hi')"''; + } + ``` + + # Type + + ``` + literalLua :: String -> AttrSet + ``` + */ + literalLua = + r: + let + # Pass the value through mkRaw for validation + raw = lib.nixvim.mkRaw r; + # TODO: consider switching to lib.generators.mkLuaInline ? + exp = "lib.nixvim.mkRaw " + lib.generators.toPretty { } raw.__raw; + in + lib.literalExpression exp; + + __messagePrefix = scope: "Nixvim (${scope}):"; + + /** + Process one or several assertions by prepending the common 'Nixvim (): ' prefix to messages. + The second argument can either be a list of assertions or a single one. + + # Example + + ```nix + assertions = mkAssertions "plugins.foo" { + assertion = plugins.foo.settings.barIntegration && (!plugins.bar.enable); + message = "`barIntegration` is enabled but the `bar` plugin is not." + } + ``` + + # Type + + ``` + mkAssertions :: String -> List -> List + ``` + */ + mkAssertions = + scope: assertions: + let + prefix = __messagePrefix scope; + processAssertion = a: { + inherit (a) assertion; + message = "${prefix} ${lib.trim a.message}"; + }; + in + builtins.map processAssertion (lib.toList assertions); + + /** + Convert one or several conditional warnings to a final warning list. + The second argument can either be a list of _conditional warnings_ or a single one. + + # Example + + ```nix + warnings = mkWarnings "plugins.foo" { + when = plugins.foo.settings.barIntegration && (!plugins.bar.enable); + message = "`barIntegration` is enabled but the `bar` plugin is not." + } + ``` + + # Type + + ``` + mkWarnings :: String -> List -> List + ``` + */ + mkWarnings = + scope: warnings: + let + prefix = __messagePrefix scope; + processWarning = + warning: lib.optional (warning.when or true) "${prefix} ${lib.trim (warning.message or warning)}"; + in + builtins.concatMap processWarning (lib.toList warnings); + + /** + Convert the given string into a `__pretty` printed mkRaw expression. + + For use in nested values that will be printed by `lib.generators.toPretty`, + or `lib.options.renderOptionValue`. + + # Example + + ```nix + nestedLiteralLua "print('hi')" + => nestedLiteral (literalLua "print('hi')") + => { + __pretty = lib.getAttr "text"; + val = literalLua "print('hi')"; + } + ``` + + # Type + + ``` + nestedLiteralLua :: String -> AttrSet + ``` + */ + nestedLiteralLua = r: nestedLiteral (literalLua r); + + /** + Convert the given string or literalExpression into a `__pretty` printed expression. + + For use in nested values that will be printed by `lib.generators.toPretty`, + or `lib.options.renderOptionValue`. + + # Examples + + ```nix + nestedLiteral "example" + => { + __pretty = lib.getAttr "text"; + val = literalExpression "example"; + } + ``` + + ```nix + nestedLiteral (literalExpression ''"hello, world"'') + => { + __pretty = lib.getAttr "text"; + val = literalExpression ''"hello, world"''; + } + ``` + + # Type + + ``` + nestedLiteral :: (String | literalExpression) -> AttrSet + ``` + */ + nestedLiteral = val: { + __pretty = lib.getAttr "text"; + val = if val._type or null == "literalExpression" then val else lib.literalExpression val; + }; + + wrapDo = string: '' + do + ${string} + end + ''; + + /** + Convert the given String to a Lua [long literal]. + For example, you could use this to safely pass a Vimscript string to the + `vim.cmd` function. + + [long literal]: https://www.lua.org/manual/5.4/manual.html#3.1 + + # Examples + + ```nix + nix-repl> toLuaLongLiteral "simple" + "[[simple]]" + ``` + + ```nix + nix-repl> toLuaLongLiteral "]]" + "[=[]]]=]" + ``` + + # Type + + ``` + toLuaLongLiteral :: String -> String + ``` + */ + toLuaLongLiteral = + string: + let + findTokens = + depth: + let + infix = lib.strings.replicate depth "="; + tokens.open = "[${infix}["; + tokens.close = "]${infix}]"; + in + if lib.hasInfix tokens.close string then findTokens (depth + 1) else tokens; + + tokens = findTokens 0; + in + tokens.open + string + tokens.close; + + /** + Convert the given String into a Vimscript [:let-heredoc]. + For example, you could use this to invoke [:lua]. + + [:let-heredoc]: https://neovim.io/doc/user/eval.html#%3Alet-heredoc + [:lua]: https://neovim.io/doc/user/lua.html#%3Alua-heredoc + + # Examples + + ```nix + toVimscriptHeredoc "simple" + => "<< EOF\nsimple\nEOF" + ``` + + ```nix + toVimscriptHeredoc "EOF" + => "<< EOFF\nEOF\nEOFF" + ``` + + # Type + + ``` + toVimscriptHeredoc :: String -> String + ``` + */ + toVimscriptHeredoc = + string: + let + findToken = + depth: + let + token = "EOF" + lib.strings.replicate depth "F"; + in + if lib.hasInfix token string then findToken (depth + 1) else token; + + token = findToken 0; + in + '' + << ${token} + ${string} + ${token}''; + + # Wrap Vimscript for using in lua, + # but only if the string contains something other than whitespaces + wrapVimscriptForLua = + string: lib.optionalString (hasContent string) "vim.cmd(${toLuaLongLiteral string})"; + + # Wrap lua script for using in Vimscript, + # but only if the string contains something other than whitespaces + wrapLuaForVimscript = + string: lib.optionalString (hasContent string) "lua ${toVimscriptHeredoc string}"; + + # Split a list into a several sub-list, each with a max-size of `size` + groupListBySize = + size: list: + lib.reverseList ( + lib.foldl' ( + lists: item: + let + first = lib.head lists; + rest = lib.drop 1 lists; + in + if lists == [ ] then + [ [ item ] ] + else if lib.length first < size then + [ (first ++ [ item ]) ] ++ rest + else + [ [ item ] ] ++ lists + ) [ ] list + ); +} diff --git a/lib/utils.nix b/lib/utils.nix index 133c3b3e..98680c00 100644 --- a/lib/utils.nix +++ b/lib/utils.nix @@ -3,40 +3,18 @@ _nixvimTests, }: rec { - # Whether a string contains something other than whitespaces - hasContent = str: builtins.match "[[:space:]]*" str == null; - - # Concatenate a list of strings, adding a newline at the end of each one, - # but skipping strings containing only whitespace characters - concatNonEmptyLines = lines: lib.concatLines (builtins.filter hasContent lines); - listToUnkeyedAttrs = list: builtins.listToAttrs (lib.lists.imap0 (idx: lib.nameValuePair "__unkeyed-${toString idx}") list); + # TODO: replace and deprecate + # We shouldn't need to use another instance of `lib` when building a test drv enableExceptInTests = !_nixvimTests; emptyTable = { "__empty" = null; }; - /** - Add a prefix to the keys of an attrs. - - # Example - ```nix - applyPrefixToAttrs "prefix_" { foo = 1; bar = 2; } - => { prefix_foo = 1; prefix_bar = 2; } - ``` - - # Type - - ``` - applyPrefixToAttrs :: String -> AttrSet -> AttrSet - ``` - */ - applyPrefixToAttrs = prefix: lib.mapAttrs' (n: lib.nameValuePair (prefix + n)); - /** Turn all the keys of an attrs into raw lua. @@ -78,58 +56,6 @@ rec { */ mkRawKey = n: v: toRawKeys { "${n}" = v; }; - /* - Convert a string from camelCase to snake_case - Type: string -> string - */ - toSnakeCase = - let - splitByWords = builtins.split "([A-Z])"; - processWord = s: if lib.isString s then s else "_" + lib.toLower (lib.elemAt s 0); - in - string: - let - words = splitByWords string; - in - lib.concatStrings (map processWord words); - - /** - Those helpers control the lua sections split in `pre, content, post` - */ - mkBeforeSection = lib.mkOrder 300; - mkAfterSection = lib.mkOrder 2000; - - /** - Capitalize a string by making the first character uppercase. - - # Example - - ```nix - upperFirstChar "hello, world!" - => "Hello, world!" - ``` - - # Type - - ``` - upperFirstChar :: String -> String - ``` - */ - upperFirstChar = - s: - let - first = lib.substring 0 1 s; - rest = lib.substring 1 (lib.stringLength s) s; - result = (lib.toUpper first) + rest; - in - lib.optionalString (s != "") result; - - mkIfNonNull' = x: y: (lib.mkIf (x != null) y); - - mkIfNonNull = x: (mkIfNonNull' x x); - - ifNonNull' = x: y: if (x == null) then null else y; - mkRaw = r: if r == null || r == "" then @@ -140,273 +66,4 @@ rec { r else throw "mkRaw: invalid input: ${lib.generators.toPretty { multiline = false; } r}"; - - /** - Convert the given string into a literalExpression mkRaw. - - For use in option documentation, such as examples and defaults. - - # Example - - ```nix - literalLua "print('hi')" - => literalExpression ''lib.nixvim.mkRaw "print('hi')"'' - => { - _type = "literalExpression"; - text = ''lib.nixvim.mkRaw "print('hi')"''; - } - ``` - - # Type - - ``` - literalLua :: String -> AttrSet - ``` - */ - literalLua = - r: - let - # Pass the value through mkRaw for validation - raw = mkRaw r; - # TODO: consider switching to lib.generators.mkLuaInline ? - exp = "lib.nixvim.mkRaw " + lib.generators.toPretty { } raw.__raw; - in - lib.literalExpression exp; - - __messagePrefix = scope: "Nixvim (${scope}):"; - /** - Process one or several assertions by prepending the common 'Nixvim (): ' prefix to messages. - The second argument can either be a list of assertions or a single one. - - # Example - - ```nix - assertions = mkAssertions "plugins.foo" { - assertion = plugins.foo.settings.barIntegration && (!plugins.bar.enable); - message = "`barIntegration` is enabled but the `bar` plugin is not." - } - ``` - - # Type - - ``` - mkAssertions :: String -> List -> List - ``` - */ - mkAssertions = - scope: assertions: - let - prefix = __messagePrefix scope; - processAssertion = a: { - inherit (a) assertion; - message = "${prefix} ${lib.trim a.message}"; - }; - in - builtins.map processAssertion (lib.toList assertions); - - /** - Convert one or several conditional warnings to a final warning list. - The second argument can either be a list of _conditional warnings_ or a single one. - - # Example - - ```nix - warnings = mkWarnings "plugins.foo" { - when = plugins.foo.settings.barIntegration && (!plugins.bar.enable); - message = "`barIntegration` is enabled but the `bar` plugin is not." - } - ``` - - # Type - - ``` - mkWarnings :: String -> List -> List - ``` - */ - mkWarnings = - scope: warnings: - let - prefix = __messagePrefix scope; - processWarning = - warning: lib.optional (warning.when or true) "${prefix} ${lib.trim (warning.message or warning)}"; - in - builtins.concatMap processWarning (lib.toList warnings); - - /** - Convert the given string into a `__pretty` printed mkRaw expression. - - For use in nested values that will be printed by `lib.generators.toPretty`, - or `lib.options.renderOptionValue`. - - # Example - - ```nix - nestedLiteralLua "print('hi')" - => nestedLiteral (literalLua "print('hi')") - => { - __pretty = lib.getAttr "text"; - val = literalLua "print('hi')"; - } - ``` - - # Type - - ``` - nestedLiteralLua :: String -> AttrSet - ``` - */ - nestedLiteralLua = r: nestedLiteral (literalLua r); - - /** - Convert the given string or literalExpression into a `__pretty` printed expression. - - For use in nested values that will be printed by `lib.generators.toPretty`, - or `lib.options.renderOptionValue`. - - # Examples - - ```nix - nestedLiteral "example" - => { - __pretty = lib.getAttr "text"; - val = literalExpression "example"; - } - ``` - - ```nix - nestedLiteral (literalExpression ''"hello, world"'') - => { - __pretty = lib.getAttr "text"; - val = literalExpression ''"hello, world"''; - } - ``` - - # Type - - ``` - nestedLiteral :: (String | literalExpression) -> AttrSet - ``` - */ - nestedLiteral = val: { - __pretty = lib.getAttr "text"; - val = if val._type or null == "literalExpression" then val else lib.literalExpression val; - }; - - wrapDo = string: '' - do - ${string} - end - ''; - - /** - Convert the given String to a Lua [long literal]. - For example, you could use this to safely pass a Vimscript string to the - `vim.cmd` function. - - [long literal]: https://www.lua.org/manual/5.4/manual.html#3.1 - - # Examples - - ```nix - nix-repl> toLuaLongLiteral "simple" - "[[simple]]" - ``` - - ```nix - nix-repl> toLuaLongLiteral "]]" - "[=[]]]=]" - ``` - - # Type - - ``` - toLuaLongLiteral :: String -> String - ``` - */ - toLuaLongLiteral = - string: - let - findTokens = - depth: - let - infix = lib.strings.replicate depth "="; - tokens.open = "[${infix}["; - tokens.close = "]${infix}]"; - in - if lib.hasInfix tokens.close string then findTokens (depth + 1) else tokens; - - tokens = findTokens 0; - in - tokens.open + string + tokens.close; - - /** - Convert the given String into a Vimscript [:let-heredoc]. - For example, you could use this to invoke [:lua]. - - [:let-heredoc]: https://neovim.io/doc/user/eval.html#%3Alet-heredoc - [:lua]: https://neovim.io/doc/user/lua.html#%3Alua-heredoc - - # Examples - - ```nix - toVimscriptHeredoc "simple" - => "<< EOF\nsimple\nEOF" - ``` - - ```nix - toVimscriptHeredoc "EOF" - => "<< EOFF\nEOF\nEOFF" - ``` - - # Type - - ``` - toVimscriptHeredoc :: String -> String - ``` - */ - toVimscriptHeredoc = - string: - let - findToken = - depth: - let - token = "EOF" + lib.strings.replicate depth "F"; - in - if lib.hasInfix token string then findToken (depth + 1) else token; - - token = findToken 0; - in - '' - << ${token} - ${string} - ${token}''; - - # Wrap Vimscript for using in lua, - # but only if the string contains something other than whitespaces - wrapVimscriptForLua = - string: lib.optionalString (hasContent string) "vim.cmd(${toLuaLongLiteral string})"; - - # Wrap lua script for using in Vimscript, - # but only if the string contains something other than whitespaces - wrapLuaForVimscript = - string: lib.optionalString (hasContent string) "lua ${toVimscriptHeredoc string}"; - - # Split a list into a several sub-list, each with a max-size of `size` - groupListBySize = - size: list: - lib.reverseList ( - lib.foldl' ( - lists: item: - let - first = lib.head lists; - rest = lib.drop 1 lists; - in - if lists == [ ] then - [ [ item ] ] - else if lib.length first < size then - [ (first ++ [ item ]) ] ++ rest - else - [ [ item ] ] ++ lists - ) [ ] list - ); }