Skip to content

Commit

Permalink
arrayUtilities: init
Browse files Browse the repository at this point in the history
  • Loading branch information
ConnorBaker committed Mar 10, 2025
1 parent 995f763 commit 0a22f98
Show file tree
Hide file tree
Showing 43 changed files with 1,844 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# shellcheck shell=bash

# arrayDifference
# Computes the difference of two arrays.
# Arguments:
# - inputArr1Ref: a reference to an array (not mutated)
# - inputArr2Ref: a reference to an array (not mutated)
# - outputArrRef: a reference to an array (mutated)
# Returns 0.
arrayDifference() {
if (($# != 3)); then
nixErrorLog "expected three arguments!"
nixErrorLog "usage: arrayDifference inputArr1Ref inputArr2Ref outputArrRef"
exit 1
fi

local -rn inputArr1Ref="$1"
local -rn inputArr2Ref="$2"
# shellcheck disable=SC2178
# don't warn about outputArrRef being used as an array because it is an array.
local -rn outputArrRef="$3"

if ! isDeclaredArray "${!inputArr1Ref}"; then
nixErrorLog "first arugment inputArr1Ref must be an array reference"
exit 1
fi

if ! isDeclaredArray "${!inputArr2Ref}"; then
nixErrorLog "second arugment inputArr2Ref must be an array reference"
exit 1
fi

if ! isDeclaredArray "${!outputArrRef}"; then
nixErrorLog "third arugment outputArrRef must be an array reference"
exit 1
fi

# TODO: Use an O(n) algorithm instead of O(n^2).
local entry
for entry in "${inputArr1Ref[@]}"; do
if ! occursInArray "$entry" "${!inputArr2Ref}"; then
outputArrRef+=("$entry")
fi
done

return 0
}

# Prevent re-declaration
readonly -f arrayDifference
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
callPackages,
isDeclaredArray,
lib,
makeSetupHook',
occursInArray,
}:
makeSetupHook' {
name = "arrayDifference";
script = ./arrayDifference.bash;
nativeBuildInputs = [
isDeclaredArray
occursInArray
];
passthru.tests = callPackages ./tests.nix { };
meta = {
description = "Computes the difference of two arrays";
maintainers = [ lib.maintainers.connorbaker ];
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# NOTE: Tests related to arrayDifference go here.
{
arrayDifference,
lib,
testers,
}:
let
inherit (lib.attrsets) recurseIntoAttrs removeAttrs;
inherit (testers) testEqualArrayOrMap;
check =
args:
(testEqualArrayOrMap (
(removeAttrs args [ "valuesToRemoveArray" ])
// {
script = ''
set -eu
if isDeclaredArray valuesToRemoveArray; then
nixLog "using valuesToRemoveArray: $(declare -p valuesToRemoveArray)"
else
nixErrorLog "valuesToRemoveArray must be a declared array"
fi
nixLog "running arrayDifference with valuesArray and valuesToRemoveArray to populate actualArray"
arrayDifference valuesArray valuesToRemoveArray actualArray
'';
}
)).overrideAttrs
(prevAttrs: {
inherit (args) valuesToRemoveArray;
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ arrayDifference ];
});
in
recurseIntoAttrs {
empty = check {
name = "empty";
valuesArray = [ ];
valuesToRemoveArray = [ ];
expectedArray = [ ];
};
singleton = check {
name = "singleton";
valuesArray = [ "apple" ];
valuesToRemoveArray = [ ];
expectedArray = [ "apple" ];
};
singleton-made-empty = check {
name = "singleton-made-empty";
valuesArray = [ "apple" ];
valuesToRemoveArray = [ "apple" ];
expectedArray = [ ];
};
singleton-none-matching = check {
name = "singleton-none-matching";
valuesArray = [ "apple" ];
valuesToRemoveArray = [ "bee" ];
expectedArray = [ "apple" ];
};
preserves-duplicates = check {
name = "preserves-duplicates";
valuesArray = [
"apple"
"bee"
"apple"
];
valuesToRemoveArray = [ "bee" ];
expectedArray = [
"apple"
"apple"
];
};
order-of-first-array-preserved = check {
name = "order-of-first-array-preserved";
valuesArray = [
"apple"
"cat"
"bee"
"dog"
"apple"
"bee"
];
valuesToRemoveArray = [
"bee"
"apple"
];
expectedArray = [
"cat"
"dog"
];
};
# TODO: Negative tests.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# shellcheck shell=bash

# arrayReplace
# Replaces all occurrences of each key of inputMapRef in inputArrRef with the values provided by the delimted string in
# the corresponding value of inputMapRef.
# Arguments:
# - inputArrRef: a reference to an array (not mutated)
# - inputMapRef: a reference to an associative array (not mutated)
# - delimiter: a character used to delimit the values in inputMapRef
# - outputArrRef: a reference to an array (mutated)
arrayReplace() {
if (($# != 4)); then
nixErrorLog "expected four arguments!"
nixErrorLog "usage: arrayReplace inputArrRef inputMapRef delimiter outputArrRef"
exit 1
fi

local -rn inputArrRef="$1"
local -rn inputMapRef="$2"
local -r delimiter="$3"
# shellcheck disable=SC2178
local -rn outputArrRef="$4"

if ! isDeclaredArray "${!inputArrRef}"; then
nixErrorLog "first arugment inputArrRef must be an array reference"
exit 1
fi

if ! isDeclaredMap "${!inputMapRef}"; then
nixErrorLog "second arugment inputMapRef must be an associative array reference"
exit 1
fi

if ! isDeclaredArray "${!outputArrRef}"; then
nixErrorLog "third arugment outputArrRef must be an array reference"
exit 1
fi

# Early return for empty array and replacement map.
if ((${#inputArrRef[@]} == 0)); then
outputArrRef=()
return 0
elif ((${#inputMapRef[@]} == 0)); then
outputArrRef=("${inputArrRef[@]}")
return 0
fi

local elem
local replacementString
local -a replacementElemArray=()
for elem in "${inputArrRef[@]}"; do
# NOTE: We must use the slow check for key presence because we need to be able to discern between the key being
# absent and the key being present with an empty string as the value.
if occursInMapKeys "$elem" "${!inputMapRef}"; then
replacementString="${inputMapRef["$elem"]}"
replacementElemArray=()
if [[ -n $replacementString ]]; then
mapfile -d "$delimiter" -t replacementElemArray < <(echo -n "$replacementString")
fi
outputArrRef+=("${replacementElemArray[@]}")
else
outputArrRef+=("$elem")
fi
done

return 0
}

# Prevent re-declaration
readonly -f arrayReplace
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
callPackages,
lib,
makeSetupHook',
isDeclaredArray,
isDeclaredMap,
occursInMapKeys,
}:
makeSetupHook' {
name = "arrayReplace";
script = ./arrayReplace.bash;
nativeBuildInputs = [
isDeclaredArray
isDeclaredMap
occursInMapKeys
];
passthru.tests = callPackages ./tests.nix { };
meta = {
description = "Replaces all occurrences of a value in an array with other value(s)";
maintainers = [ lib.maintainers.connorbaker ];
};
}
102 changes: 102 additions & 0 deletions pkgs/build-support/setup-hooks/arrayUtilities/arrayReplace/tests.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# NOTE: Tests related to arrayReplace go here.
{
arrayReplace,
lib,
testers,
}:
let
inherit (lib.attrsets) recurseIntoAttrs removeAttrs;
inherit (testers) testEqualArrayOrMap;
check =
args:
(testEqualArrayOrMap (
(removeAttrs args [ "delimiter" ])
// {
script = ''
set -eu
nixLog "running arrayReplace with valuesArray and valuesMap to populate actualArray"
arrayReplace valuesArray valuesMap "$delimiter" actualArray
'';
}
)).overrideAttrs
(prevAttrs: {
nativeBuildInputs = prevAttrs.nativeBuildInputs or [ ] ++ [ arrayReplace ];
inherit (args) delimiter;
});
in
recurseIntoAttrs {
empty-with-string = check {
name = "empty";
valuesArray = [ ];
valuesMap = { };
delimiter = " ";
expectedArray = [ ];
};
singleton = check {
name = "singleton";
valuesArray = [ "apple" ];
valuesMap = { };
delimiter = " ";
expectedArray = [ "apple" ];
};
singleton-made-empty = check {
name = "singleton-made-empty";
valuesArray = [ "apple" ];
valuesMap = {
"apple" = "";
};
delimiter = " ";
expectedArray = [ ];
};
singleton-made-singleton = check {
name = "singleton-made-singleton";
valuesArray = [ "apple" ];
valuesMap = {
"apple" = "bee";
};
delimiter = " ";
expectedArray = [ "bee" ];
};
singleton-none-matching = check {
name = "singleton-none-matching";
valuesArray = [ "apple" ];
valuesMap = {
"bee" = "cat";
};
delimiter = " ";
expectedArray = [ "apple" ];
};
# preserves-duplicates = check {
# name = "preserves-duplicates";
# valuesArray = [
# "apple"
# "bee"
# "apple"
# ];
# valuesToRemoveArray = [ "bee" ];
# expectedArray = [
# "apple"
# "apple"
# ];
# };
# order-of-first-array-preserved = check {
# name = "order-of-first-array-preserved";
# valuesArray = [
# "apple"
# "cat"
# "bee"
# "dog"
# "apple"
# "bee"
# ];
# valuesToRemoveArray = [
# "bee"
# "apple"
# ];
# expectedArray = [
# "cat"
# "dog"
# ];
# };
# TODO: Negative tests.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# shellcheck shell=bash

arraysAreEqual() {
if (($# != 2)); then
nixErrorLog "expected two arguments!"
nixErrorLog "usage: arraysAreEqual inputArr1Ref inputArr2Ref"
exit 1
fi

local -rn inputArr1Ref="$1"
local -rn inputArr2Ref="$2"

if ! isDeclaredArray "${!inputArr1Ref}"; then
nixErrorLog "first arugment inputArr1Ref must be an array reference"
exit 1
fi

if ! isDeclaredArray "${!inputArr2Ref}"; then
nixErrorLog "second arugment inputArr2Ref must be an array reference"
exit 1
fi

if [[ ${#inputArr1Ref[@]} -ne ${#inputArr2Ref[@]} || ${inputArr1Ref[*]@K} != "${inputArr2Ref[*]@K}" ]]; then
return 1
fi

return 0
}

# Prevent re-declaration
readonly -f arraysAreEqual
Loading

0 comments on commit 0a22f98

Please sign in to comment.