Merge pull request #173949 from jacoblambda/fix-toInt-zero-padding
lib: add strings.toIntBase10 to parse zero-padded strings
This commit is contained in:
commit
bc4ce318bf
4 changed files with 160 additions and 8 deletions
|
@ -103,7 +103,7 @@ let
|
|||
getName getVersion
|
||||
nameFromURL enableFeature enableFeatureAs withFeature
|
||||
withFeatureAs fixedWidthString fixedWidthNumber isStorePath
|
||||
toInt readPathsFromFile fileContents;
|
||||
toInt toIntBase10 readPathsFromFile fileContents;
|
||||
inherit (self.stringsWithDeps) textClosureList textClosureMap
|
||||
noDepEntry fullDepEntry packEntry stringAfter;
|
||||
inherit (self.customisation) overrideDerivation makeOverridable
|
||||
|
|
|
@ -783,24 +783,105 @@ rec {
|
|||
else
|
||||
false;
|
||||
|
||||
/* Parse a string as an int.
|
||||
/* Parse a string as an int. Does not support parsing of integers with preceding zero due to
|
||||
ambiguity between zero-padded and octal numbers. See toIntBase10.
|
||||
|
||||
Type: string -> int
|
||||
|
||||
Example:
|
||||
|
||||
toInt "1337"
|
||||
=> 1337
|
||||
|
||||
toInt "-4"
|
||||
=> -4
|
||||
|
||||
toInt " 123 "
|
||||
=> 123
|
||||
|
||||
toInt "00024"
|
||||
=> error: Ambiguity in interpretation of 00024 between octal and zero padded integer.
|
||||
|
||||
toInt "3.14"
|
||||
=> error: floating point JSON numbers are not supported
|
||||
*/
|
||||
# Obviously, it is a bit hacky to use fromJSON this way.
|
||||
toInt = str:
|
||||
let may_be_int = fromJSON str; in
|
||||
if isInt may_be_int
|
||||
then may_be_int
|
||||
else throw "Could not convert ${str} to int.";
|
||||
let
|
||||
# RegEx: Match any leading whitespace, then any digits, and finally match any trailing
|
||||
# whitespace.
|
||||
strippedInput = match "[[:space:]]*([[:digit:]]+)[[:space:]]*" str;
|
||||
|
||||
# RegEx: Match a leading '0' then one or more digits.
|
||||
isLeadingZero = match "0[[:digit:]]+" (head strippedInput) == [];
|
||||
|
||||
# Attempt to parse input
|
||||
parsedInput = fromJSON (head strippedInput);
|
||||
|
||||
generalError = "toInt: Could not convert ${escapeNixString str} to int.";
|
||||
|
||||
octalAmbigError = "toInt: Ambiguity in interpretation of ${escapeNixString str}"
|
||||
+ " between octal and zero padded integer.";
|
||||
|
||||
in
|
||||
# Error on presence of non digit characters.
|
||||
if strippedInput == null
|
||||
then throw generalError
|
||||
# Error on presence of leading zero/octal ambiguity.
|
||||
else if isLeadingZero
|
||||
then throw octalAmbigError
|
||||
# Error if parse function fails.
|
||||
else if !isInt parsedInput
|
||||
then throw generalError
|
||||
# Return result.
|
||||
else parsedInput;
|
||||
|
||||
|
||||
/* Parse a string as a base 10 int. This supports parsing of zero-padded integers.
|
||||
|
||||
Type: string -> int
|
||||
|
||||
Example:
|
||||
toIntBase10 "1337"
|
||||
=> 1337
|
||||
|
||||
toIntBase10 "-4"
|
||||
=> -4
|
||||
|
||||
toIntBase10 " 123 "
|
||||
=> 123
|
||||
|
||||
toIntBase10 "00024"
|
||||
=> 24
|
||||
|
||||
toIntBase10 "3.14"
|
||||
=> error: floating point JSON numbers are not supported
|
||||
*/
|
||||
toIntBase10 = str:
|
||||
let
|
||||
# RegEx: Match any leading whitespace, then match any zero padding, capture any remaining
|
||||
# digits after that, and finally match any trailing whitespace.
|
||||
strippedInput = match "[[:space:]]*0*([[:digit:]]+)[[:space:]]*" str;
|
||||
|
||||
# RegEx: Match at least one '0'.
|
||||
isZero = match "0+" (head strippedInput) == [];
|
||||
|
||||
# Attempt to parse input
|
||||
parsedInput = fromJSON (head strippedInput);
|
||||
|
||||
generalError = "toIntBase10: Could not convert ${escapeNixString str} to int.";
|
||||
|
||||
in
|
||||
# Error on presence of non digit characters.
|
||||
if strippedInput == null
|
||||
then throw generalError
|
||||
# In the special case zero-padded zero (00000), return early.
|
||||
else if isZero
|
||||
then 0
|
||||
# Error if parse function fails.
|
||||
else if !isInt parsedInput
|
||||
then throw generalError
|
||||
# Return result.
|
||||
else parsedInput;
|
||||
|
||||
/* Read a list of paths from `file`, relative to the `rootPath`.
|
||||
Lines beginning with `#` are treated as comments and ignored.
|
||||
|
|
|
@ -327,6 +327,77 @@ runTests {
|
|||
expected = "Hello\\x20World";
|
||||
};
|
||||
|
||||
testToInt = testAllTrue [
|
||||
# Naive
|
||||
(123 == toInt "123")
|
||||
(0 == toInt "0")
|
||||
# Whitespace Padding
|
||||
(123 == toInt " 123")
|
||||
(123 == toInt "123 ")
|
||||
(123 == toInt " 123 ")
|
||||
(123 == toInt " 123 ")
|
||||
(0 == toInt " 0")
|
||||
(0 == toInt "0 ")
|
||||
(0 == toInt " 0 ")
|
||||
];
|
||||
|
||||
testToIntFails = testAllTrue [
|
||||
( builtins.tryEval (toInt "") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt "123 123") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt "0 123") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt " 0d ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt " 1d ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt " d0 ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt "00") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt "01") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt "002") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt " 002 ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt " foo ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt " foo 123 ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toInt " foo123 ") == { success = false; value = false; } )
|
||||
];
|
||||
|
||||
testToIntBase10 = testAllTrue [
|
||||
# Naive
|
||||
(123 == toIntBase10 "123")
|
||||
(0 == toIntBase10 "0")
|
||||
# Whitespace Padding
|
||||
(123 == toIntBase10 " 123")
|
||||
(123 == toIntBase10 "123 ")
|
||||
(123 == toIntBase10 " 123 ")
|
||||
(123 == toIntBase10 " 123 ")
|
||||
(0 == toIntBase10 " 0")
|
||||
(0 == toIntBase10 "0 ")
|
||||
(0 == toIntBase10 " 0 ")
|
||||
# Zero Padding
|
||||
(123 == toIntBase10 "0123")
|
||||
(123 == toIntBase10 "0000123")
|
||||
(0 == toIntBase10 "000000")
|
||||
# Whitespace and Zero Padding
|
||||
(123 == toIntBase10 " 0123")
|
||||
(123 == toIntBase10 "0123 ")
|
||||
(123 == toIntBase10 " 0123 ")
|
||||
(123 == toIntBase10 " 0000123")
|
||||
(123 == toIntBase10 "0000123 ")
|
||||
(123 == toIntBase10 " 0000123 ")
|
||||
(0 == toIntBase10 " 000000")
|
||||
(0 == toIntBase10 "000000 ")
|
||||
(0 == toIntBase10 " 000000 ")
|
||||
];
|
||||
|
||||
testToIntBase10Fails = testAllTrue [
|
||||
( builtins.tryEval (toIntBase10 "") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 "123 123") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 "0 123") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 " 0d ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 " 1d ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 " d0 ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 " foo ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 " foo 123 ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 " foo 00123 ") == { success = false; value = false; } )
|
||||
( builtins.tryEval (toIntBase10 " foo00123 ") == { success = false; value = false; } )
|
||||
];
|
||||
|
||||
# LISTS
|
||||
|
||||
testFilter = {
|
||||
|
|
|
@ -162,7 +162,7 @@ checkConfigError 'A definition for option .* is not.*string or signed integer co
|
|||
# Check coerced value with unsound coercion
|
||||
checkConfigOutput '^12$' config.value ./declare-coerced-value-unsound.nix
|
||||
checkConfigError 'A definition for option .* is not of type .*. Definition values:\n\s*- In .*: "1000"' config.value ./declare-coerced-value-unsound.nix ./define-value-string-bigint.nix
|
||||
checkConfigError 'json.exception.parse_error' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
|
||||
checkConfigError 'toInt: Could not convert .* to int' config.value ./declare-coerced-value-unsound.nix ./define-value-string-arbitrary.nix
|
||||
|
||||
# Check mkAliasOptionModule.
|
||||
checkConfigOutput '^true$' config.enable ./alias-with-priority.nix
|
||||
|
|
Loading…
Reference in a new issue