initial commit

This commit is contained in:
chaos 2024-11-07 08:58:38 +00:00
commit 56029c7cef
10 changed files with 1048 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
export.json

8
run.sh Executable file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -eu
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
cd "$SCRIPT_DIR"
${JQ:-jq} ${JQ_ARGS:-} -L "tool" -f tool/main.jq -Cr ${EXPORT_FILE:-export.json} --args -- "$@"

10
runTests.sh Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -eu
SCRIPT_DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
cd "$SCRIPT_DIR/tool"
jq -n -r -L . "include \"testLib\"; testLibMain"
jq -n -r -L . "include \"tests\"; testsMain" \
--slurpfile "file:testdata/tests_export.json" testdata/tests_export.json

205
testdata/tests_export.json vendored Normal file
View file

@ -0,0 +1,205 @@
{
"substanceCompanions": [
{
"substanceName": "Caffeine",
"color": "BLUE"
},
{
"color": "BROWN",
"substanceName": "N-Acetylcysteine"
},
{
"substanceName": "Custom",
"color": "CYAN"
}
],
"customUnits": [
{
"substanceName": "Caffeine",
"isEstimate": false,
"isArchived": false,
"creationDate": 1730892759000,
"administrationRoute": "ORAL",
"unit": "pill",
"originalUnit": "mg",
"id": 1251361475,
"estimatedDoseStandardDeviation": null,
"name": "Tablet",
"dose": 200,
"note": ""
}
],
"experiences": [
{
"isFavorite": false,
"text": "",
"creationDate": 1730892508000,
"title": "Experience 1",
"ratings": [],
"sortDate": 1730892507000,
"timedNotes": [],
"ingestions": [
{
"estimatedDoseStandardDeviation": null,
"dose": 20,
"stomachFullness": null,
"administrationRoute": "INSUFFLATED",
"substanceName": "Caffeine",
"creationDate": 1730892508000,
"units": "mg",
"time": 1730892507000,
"isDoseAnEstimate": false,
"notes": "",
"consumerName": null,
"customUnitId": null
},
{
"customUnitId": null,
"estimatedDoseStandardDeviation": null,
"time": 1730892510000,
"stomachFullness": null,
"substanceName": "Caffeine",
"dose": 20,
"notes": "",
"creationDate": 1730892511000,
"isDoseAnEstimate": true,
"units": "mg",
"consumerName": null,
"administrationRoute": "INSUFFLATED"
},
{
"estimatedDoseStandardDeviation": null,
"dose": 100,
"units": "mg",
"creationDate": 1730892529000,
"isDoseAnEstimate": false,
"consumerName": null,
"notes": "",
"administrationRoute": "ORAL",
"time": 1730892528000,
"substanceName": "Caffeine",
"customUnitId": null,
"stomachFullness": "EMPTY"
},
{
"substanceName": "Caffeine",
"notes": "",
"customUnitId": null,
"time": 1730892533000,
"isDoseAnEstimate": true,
"administrationRoute": "ORAL",
"units": "mg",
"estimatedDoseStandardDeviation": null,
"dose": 100,
"creationDate": 1730892534000,
"stomachFullness": "EMPTY",
"consumerName": null
},
{
"estimatedDoseStandardDeviation": 20,
"isDoseAnEstimate": true,
"administrationRoute": "ORAL",
"dose": 100,
"time": 1730892549000,
"units": "mg",
"consumerName": null,
"creationDate": 1730892554000,
"notes": "",
"substanceName": "Caffeine",
"stomachFullness": "EMPTY",
"customUnitId": null
},
{
"stomachFullness": "EMPTY",
"notes": "",
"dose": 250,
"isDoseAnEstimate": false,
"time": 1730892600000,
"administrationRoute": "ORAL",
"creationDate": 1730892603000,
"estimatedDoseStandardDeviation": null,
"units": "mg",
"consumerName": null,
"customUnitId": null,
"substanceName": "N-Acetylcysteine"
}
],
"location": null
},
{
"text": "",
"sortDate": 1730892605000,
"isFavorite": false,
"ingestions": [
{
"stomachFullness": "EMPTY",
"dose": 250,
"isDoseAnEstimate": false,
"substanceName": "N-Acetylcysteine",
"notes": "",
"consumerName": null,
"customUnitId": null,
"creationDate": 1730892610000,
"estimatedDoseStandardDeviation": null,
"time": 1730892605000,
"units": "mg",
"administrationRoute": "ORAL"
},
{
"substanceName": "Custom",
"stomachFullness": null,
"customUnitId": null,
"time": 1730892696000,
"isDoseAnEstimate": false,
"units": "mg",
"notes": "",
"consumerName": null,
"estimatedDoseStandardDeviation": null,
"administrationRoute": "SMOKED",
"creationDate": 1730892698000,
"dose": 100
},
{
"dose": null,
"estimatedDoseStandardDeviation": null,
"substanceName": "Custom",
"units": "mg",
"administrationRoute": "SMOKED",
"creationDate": 1730892704000,
"notes": "",
"consumerName": null,
"customUnitId": null,
"time": 1730892702000,
"isDoseAnEstimate": false,
"stomachFullness": null
},
{
"stomachFullness": "EMPTY",
"time": 1730892770000,
"consumerName": null,
"dose": 1,
"isDoseAnEstimate": false,
"customUnitId": 1251361475,
"substanceName": "Caffeine",
"notes": "",
"estimatedDoseStandardDeviation": null,
"administrationRoute": "ORAL",
"units": "mg",
"creationDate": 1730892771000
}
],
"title": "Experience 2",
"creationDate": 1730892610000,
"location": null,
"ratings": [],
"timedNotes": []
}
],
"customSubstances": [
{
"description": "",
"name": "Custom",
"units": "mg"
}
]
}

42
tool/args.jq Normal file
View file

@ -0,0 +1,42 @@
def parseArgBool:
. as $value |
if ($value | type) == "boolean" then
$value
elif
$value == "true" or $value == "on" or $value == "no"
then
true
elif
$value == "false" or $value == "off" or $value == "yes"
then
false
else
error("invalid bool value")
end;
def parseArgs:
. as $args |
reduce $args.positional[] as $arg ({
shortArgs: [],
longArgs: {},
nonArgs: []
}; (
if ($arg | test("^-[\\-a-zA-Z]") | not) then
.nonArgs += [$arg]
else
if $arg | startswith("--") then
$arg[2:] as $arg |
($arg | contains("=")) as $containsValue |
if $containsValue then
($arg | split("=")) as $argSplit |
$argSplit[0] as $arg | $argSplit[1] as $value |
.longArgs[$arg] |= $value
else
.longArgs[$arg] |= null
end
else
.shortArgs += [$arg[1:]]
end
end
));

126
tool/journalUtils.jq Normal file
View file

@ -0,0 +1,126 @@
include "utils";
def formatExperienceTitle:
. as $experience |
"\"\(.title)\": \(.creationDate / 1000 | strftime("%d-%m-%Y"))";
def calculateIngestionDose($customUnits):
. as $ingestion |
if .customUnitId != null then
($customUnits | map(select(.id == $ingestion.customUnitId))[0]) as $customUnit |
.dose * $customUnit.dose | . as $dose |
$dose * 100 | round / 100
else
.dose
end;
def ingestionUnit($customUnits):
. as $ingestion |
if .customUnitId != null then
($customUnits | map(select(.id == $ingestion.customUnitId))[0]) as $customUnit |
$customUnit.originalUnit
else
.units
end;
def formatIngestionDose($customUnits):
. as $ingestion |
. | calculateIngestionDose($customUnits) as $dose |
. | ingestionUnit($customUnits) as $unit |
$customUnits | map(select(.id == $ingestion.customUnitId))[0] as $customUnit |
if $ingestion.dose == null then
"Unknown"
elif $customUnit == null then
"\($dose) \($unit)"
else
"\($dose) \($unit) (\($ingestion.dose) \($unit) * \($customUnit.dose) \($customUnit.unit))"
end;
def formatIngestionTime:
. as $ingestion |
$ingestion.time / 1000 | strftime("%a %I:%M %p");
def formatIngestionROA($customUnits; $substitutions):
. as $ingestion |
$ingestion.administrationRoute as $roa |
(if
$substitutions | has($roa)
then
$substitutions.[$roa]
else
$roa | titleCase
end) as $roaText |
$ingestion.customUnitId as $customUnitId |
if
$customUnitId == null
then
$roaText
else
$customUnits | map(select(.id == $customUnitId))[0] as $customUnit |
"\($roaText) (\($customUnit.name))"
end;
def formatIngestionROA($customUnits): formatIngestionROA($customUnits; {});
def filterIngestions($substanceFilter; $consumerFilter):
. as $ingestions |
$ingestions | if (($substanceFilter // [] | length) > 0) then
(reduce .[] as $ingestion ([];
if
([$substanceFilter[] | . == $ingestion.substanceName] | any)
and
([$consumerFilter[] | . == ifNullDefault($ingestion.consumerName; "default")] | any)
then . += [$ingestion]
else . end)
)
end;
def ingestionsByConsumer:
. as $ingestions |
(reduce $ingestions[] as $ingestion ({};
ifNullDefault($ingestion.consumerName; "default") as $consumerName |
if .[$consumerName] == null then .[$consumerName] |= [] end |
.[$consumerName] += [$ingestion]
));
def ingestionsSubstanceNames:
. as $ingestions |
[$ingestions[].substanceName] | orderedUnique;
def ingestionsConsumerNames:
. as $ingestions |
[$ingestions[] as $ingestion | ifNullDefault($ingestion.consumerName; "default")] | orderedUnique;
def experienceStats($customUnits):
. as $experience |
$experience.ingestions as $ingestions |
(reduce $ingestions[] as $ingestion ({}; . as $stats |
$ingestion |
.substanceName as $name |
.administrationRoute as $administrationRoute |
. | calculateIngestionDose($customUnits) as $dose |
. | ingestionUnit($customUnits) as $unit |
(.consumerName // "default") as $consumerName |
$stats |
.[$consumerName].[$name].[$administrationRoute]|=
($stats.[$consumerName].[$name].[$administrationRoute] // {
unit: "",
# null because null+null = null for ingestions with unknown dose which .dose is null
dose: null
}) |
.[$consumerName].[$name].[$administrationRoute].unit |= $unit |
.[$consumerName].[$name].[$administrationRoute].dose += $dose
));
def calculateCombinedDose($substanceName; $consumerName):
. as $stats |
($stats.[$consumerName].[$substanceName] | [to_entries[] | .value.dose] | add)| . as $combinedDose |
($stats.[$consumerName].[$substanceName] | to_entries[0] | .value.unit) as $combinedDoseUnit |
{dose: $combinedDose, unit: $combinedDoseUnit};
def experienceByTitle($name):
assert((. | type) == "array"; "experienceByTitle takes a array of experiences as input") |
map(select(.title == $name))[0];

391
tool/main.jq Normal file
View file

@ -0,0 +1,391 @@
# run as: jq export.json -f tool.jq -Cr --args --
include "utils";
include "args";
include "journalUtils";
def printExperienceStats($stats; $substanceFilter; $consumerFilter; $withTitle):
. as $experience |
($consumerFilter // ["default"]) as $consumerFilter |
$experience.ingestions | filterIngestions($substanceFilter; $consumerFilter) as $ingestions |
($ingestions | ingestionsByConsumer) as $ingestionsByConsumer |
($ingestionsByConsumer | keys) as $consumerNames |
"" as $experienceStatsText |
$experienceStatsText |
if $withTitle then
. += ($experience | formatExperienceTitle | . + "\n")
end | . as $experienceStatsText |
reduce $consumerNames[] as $consumerName ($experienceStatsText;
. as $experienceStatsText |
$experienceStatsText |
if ($consumerNames != ["default"])
then . += "Consumer: \($consumerName)\n"
end | . as $experienceStatsText |
($stats.[$consumerName] | keys) as $substanceNames |
$experienceStatsText | reduce $substanceNames[] as $substanceName (.;
. as $experienceStatsText |
($stats.[$consumerName].[$substanceName] | keys) as $ingestionMethods |
($experienceStatsText | . += "Substance: \($substanceName)\n") as $experienceStatsText |
reduce ($stats.[$consumerName].[$substanceName] | to_entries)[] as $substanceStats ($experienceStatsText;
. as $experienceStatsText |
$substanceStats |
.key as $ingestionMethod |
ifNullDefault(.value.dose; "Unknown") as $dose |
.value.unit as $unit |
($experienceStatsText | . += "Dose (\($ingestionMethod | titleCase)): \($dose) \($unit)\n") as $experienceStatsText |
$experienceStatsText
) | . as $experienceStatsText |
$experienceStatsText | if ($ingestionMethods | length > 1) then
($stats | calculateCombinedDose($substanceName; $consumerName)) as $combinedDose |
. += "Combined Dose: \(ifNullDefault($combinedDose.dose; "Unknown")) \($combinedDose.unit)\n"
end | . as $experienceStatsText |
$experienceStatsText | . += "\n"
)
) | rtrimstr("\n\n");
def printExperienceLog($customUnits; $substanceFilter; $consumerFilter; $pretty; $withTitle):
. as $experience |
($consumerFilter // ["default"]) as $consumerFilter |
$experience.ingestions | sort_by(.sortDate) | filterIngestions($substanceFilter; $consumerFilter) as $ingestions |
$ingestions | ingestionsConsumerNames as $consumerNames |
if ($consumerNames == ["default"])
then
["Substance", "Dose", "ROA", "Consumer", "Time"]
else
["Substance", "Dose", "ROA", "Time"]
end | . as $columnTitles |
(reduce $ingestions[] as $ingestion ([]; . as $rows |
$ingestion |
.substanceName as $substanceName |
ifNullDefault(.consumerName; "default") as $consumerName |
formatIngestionDose($customUnits) as $doseText |
formatIngestionROA($customUnits) as $roaText |
formatIngestionTime as $timeText |
$rows | . += [
if ($consumerNames != ["default"])
then [$substanceName, $doseText, $roaText, $consumerName, $timeText]
else [$substanceName, $doseText, $roaText, $timeText] end
]
)) as $rows |
if $pretty then
printPrettyTable(
if $withTitle then ($experience | formatExperienceTitle) else null end;
$columnTitles;
$rows
)
else
$rows | map(join(" | ")) | rtrimstr(" ") as $rows |
(if $withTitle then [$experience | formatExperienceTitle] else [] end) + $rows | join("\n")
end | . as $ingestionLog |
$ingestionLog;
def printExperiencesAdvanced($customUnits; $substanceFilter; $consumerFilter; $sortMethod; $sortOptions; $sortFilterString; sortFilter):
. as $experiences |
($consumerFilter // ["default"]) as $consumerFilter |
$sortOptions |
# If filtering results by substances but no sortOptions.substanceName is defined, use the first one as a default
(.substanceName |=
if
$sortOptions.substanceName == null
and
(($substanceFilter | length) >= 1)
then
$substanceFilter[0]
else
null
end
) | . as $sortOptions |
$sortOptions | (.ingestionMethod |= ($sortOptions.ingestionMethod // "ORAL")) | . as $sortOptions |
$sortOptions | (.consumerName |= ($sortOptions.consumerName // "default")) | . as $sortOptions |
def sortFilterExperiences:
def oldToNewSort: .experience.sortDate;
def highestCombinedDoseSort: .stats.[$sortOptions.consumerName].[$sortOptions.substanceName] | [to_entries[] | .value.dose] | add;
def highestMethodDoseSort: .stats.[$sortOptions.consumerName].[$sortOptions.substanceName].[$sortOptions.ingestionMethod // "ORAL"].dose;
def filterBySubstanceFilter:
. as $experiencesData |
(reduce $experiencesData[] as $experienceData ([];
($experienceData.experience.ingestions) as $ingestions |
. +=
if ($experienceData.experience.ingestions |
any(
.substanceName as $substanceName |
$substanceFilter |
any(index($substanceName))
)
) then [$experienceData] else [] end
));
def filterByConsumerFilter:
. as $experiencesData |
(reduce $experiencesData[] as $experienceData ([];
($experienceData.experience.ingestions) as $ingestions |
. +=
if ($experienceData.experience.ingestions |
any(
ifNullDefault(.consumerName; "default") as $consumerName |
$consumerFilter |
any(index($consumerName))
)
) then [$experienceData] else [] end
));
def filterBySubstanceAndConsumer:
. as $experiencesData |
(reduce $experiencesData[] as $experienceData ([];
($experienceData.experience.ingestions) as $ingestions |
. +=
if
($experienceData.experience.ingestions |
any(
.substanceName == $sortOptions.substanceName
and
ifNullDefault(.consumerName; "default") == $sortOptions.consumerName
))
then [$experienceData] else [] end
));
def filterByIngestionMethodForSubstance:
. as $experiencesData | filterBySubstanceAndConsumer as $experiencesData |
(reduce $experiencesData[] as $experienceData ([];
($experienceData.experience.ingestions) as $ingestions |
. += if ($experienceData.experience.ingestions | any(.substanceName == $sortOptions.substanceName and .administrationRoute == $sortOptions.ingestionMethod))
then [$experienceData] else [] end
));
# speeds up by excluding everything not containing substances & consumers not in filters, wouldn't show any data anyway
if $substanceFilter != null then filterBySubstanceFilter end |
filterByConsumerFilter |
if
$sortMethod == "old-to-new" or $sortMethod == null
then
sort_by(oldToNewSort)
elif
$sortMethod == "highest-combined-dose"
then
assert($sortOptions.substanceName != null; "substanceName not provided as sortOption") |
filterBySubstanceAndConsumer |
sort_by(highestCombinedDoseSort, oldToNewSort) |
reverse
elif
$sortMethod == "highest-dose-for-method"
then
assert($sortOptions.substanceName != null; "substanceName not provided as sortOption") |
filterByIngestionMethodForSubstance |
sort_by(highestMethodDoseSort, oldToNewSort) |
reverse
end;
def sortFilterFromString($filterString):
(reduce ($filterString | split("|"))[] as $filter (.;
if
$filter == "reverse" then . | reverse
elif ($filter | startswith("firstN")) then
($filter | ltrimstr("firstN(") | rtrimstr(")")) as $arg |
. | firstN($arg | try fromjson catch error("invalid number passed to firstN"))
end
));
def experiencesWithExtraData($customUnits):
. as $experiences |
(reduce $experiences[] as $experience ([];
. += [{
stats: $experience | experienceStats($customUnits),
$experience
}]
));
$experiences |
experiencesWithExtraData($customUnits) |
sortFilterExperiences |
if ($sortFilterString != null) then . | sortFilterFromString($sortFilterString) end |
if (sortFilter != null) then . | sortFilter end |
.[] as $entry |
$entry.experience as $experience |
$entry.stats as $stats |
($experience | printExperienceLog(
$customUnits;
$substanceFilter;
$consumerFilter;
true;
true
)) +
"\nCumulative Doses:\n" +
($experience | printExperienceStats(
$stats;
$substanceFilter;
$consumerFilter;
false
)) + "\n";
def main($ARGS):
def usage:
[
"psychonaut_journal_stats {printExperience,printExperiencesAdvanced}",
""
] | join("\n") | halt_error(1);
. as $exportData |
($ARGS | parseArgs) as $parsedArgs |
$parsedArgs.nonArgs[0] as $program |
($parsedArgs | .nonArgs |= $parsedArgs.nonArgs[1:]) as $parsedArgs |
if $program == null then
if any($parsedArgs.shortArgs[]; . == "h") then usage end |
if ($parsedArgs.longArgs | has("help")) then usage end |
usage
elif $program == "printExperience" then
def printExperienceUsage($reason):
[
$reason,
"Usage: printExperience [experienceTitle] --pretty=bool --title=bool --stats=bool --substance-filter=[substanceNames,] --consumer-filter=[consumerNames,]",
""
] | map(select(. != null)) | join("\n") | halt_error(1);
if any($parsedArgs.shortArgs[]; . == "h") then printExperienceUsage(null) end |
if ($parsedArgs.longArgs | has("help")) then printExperienceUsage(null) end |
$parsedArgs.nonArgs[0] as $experienceTitle |
if $experienceTitle == null then printExperienceUsage("experienceTitle not provided") end |
{
substanceFilter: null,
consumerFilter: null,
pretty: true,
withTitle: true,
withStats: true,
} as $defaultOptions |
$defaultOptions as $options |
reduce ($parsedArgs.longArgs | to_entries[]) as $longArg ($options; (
$longArg.key as $arg |
$longArg.value as $value |
if $arg == "pretty" then
.pretty |= (ifNullDefault($value; $defaultOptions.pretty) | parseArgBool)
end |
if $arg == "title" then
.withTitle |= (ifNullDefault($value; $defaultOptions.withTitle) | parseArgBool)
end |
if $arg == "stats" then
.withStats |= (ifNullDefault($value; $defaultOptions.withStats) | parseArgBool)
end |
if $arg == "substance-filter" then
.substanceFilter |= ($parsedArgs.longArgs.["substance-filter"] | split(","))
end |
if $arg == "consumer-filter" then
.consumerFilter |= ($parsedArgs.longArgs.["consumer-filter"] | split(","))
end
)) | . as $options |
$exportData.experiences | experienceByTitle($experienceTitle) as $experience |
if $experience == null then error("Experience not found") end |
($experience | printExperienceLog(
$exportData.customUnits;
$options.substanceFilter;
$options.consumerFilter;
$options.pretty;
$options.withTitle
)) as $ingestionLog |
if $options.withStats then
($experience | experienceStats($exportData.customUnits)) as $stats |
$ingestionLog +
"\n\nCumulative Doses:\n" +
($experience | printExperienceStats(
$stats;
$options.substanceFilter;
$options.consumerFilter;
false
))
else
$ingestionLog
end
elif $program == "printExperiencesAdvanced" then
def printExperiencesAdvancedUsage($reason):
[
$reason,
"Usage: printExperiencesAdvanced --substance-filter=[substanceNames,] --consumer-filter=[consumerNames,] --sort-method={old-to-new,highest-combined-dose,highest-dose-for-method} --sort-option-substance-name=[string] --sort-options-ingestion-method=[string] --sort-options-consumer-name=[string] --sort-filter={firstN(x),reverse}",
""
] | map(select(. != null)) | join("\n") | halt_error(1);
if any($parsedArgs.shortArgs[]; . == "h") then printExperiencesAdvancedUsage(null) end |
if ($parsedArgs.longArgs | has("help")) then printExperiencesAdvancedUsage(null) end |
{
substanceFilter: null,
consumerFilter: null,
sortMethod: "old-to-new",
sortOptions: {},
sortFilter: null
} as $defaultOptions |
$defaultOptions as $options |
reduce ($parsedArgs.longArgs | to_entries[]) as $longArg ($options; (
$longArg.key as $arg |
$longArg.value as $value |
if $arg == "substance-filter" then
.substanceFilter |= ($parsedArgs.longArgs.["substance-filter"] | split(","))
end |
if $arg == "consumer-filter" then
.consumerFilter |= ($parsedArgs.longArgs.["consumer-filter"] | split(","))
end |
if $arg == "sort-method" then
.sortMethod |= $parsedArgs.longArgs.["sort-method"]
end |
if $arg == "sort-option-substance-name" then
.sortOptions.substanceName |= $parsedArgs.longArgs.["sort-option-substance-name"]
end |
if $arg == "sort-option-ingestion-method" then
.sortOptions.ingestionMethod |= $parsedArgs.longArgs.["sort-option-ingestion-method"]
end |
if $arg == "sort-option-consumer-name" then
.sortOptions.consumerName |= $parsedArgs.longArgs.["sort-option-consumer-name"]
end |
if $arg == "sort-filter" then
.sortFilter |= $parsedArgs.longArgs.["sort-filter"]
end
)) | . as $options |
$exportData.experiences |
printExperiencesAdvanced(
$exportData.customUnits;
$options.substanceFilter;
$options.consumerFilter;
$options.sortMethod;
$options.sortOptions;
$options.sortFilter; null
)
else
usage
end;
main($ARGS)

127
tool/testLib.jq Normal file
View file

@ -0,0 +1,127 @@
def printTestResult:
. as $result | $result |
if .passed then
"Test \"\(.name)\" Passed"
else
(if (.loc != null) then " at \(.loc.file):\(.loc.line)" else "" end) as $fileLocation |
"Test \"\(.name)\"\($fileLocation) Failed\nReason: \(.reason)\nOutput: \(.output | tojson)"
end;
def runTest($loc; $name; testExpr; checkResult; $expectError):
try (
(null | testExpr) as $output |
if $expectError then
{
$name,
passed: false,
reason: "Expected error but no error was raised",
$output,
$loc
}
elif ($output | checkResult) then
{
$name,
passed: true,
$output,
$loc
}
else
{
$name,
passed: false,
reason: "Result was different from expected",
$output,
$loc
}
end
) catch (
. as $error |
if ($expectError | not) then
{
$name,
passed: false,
reason: "Error caught when no error was expected",
output: $error,
$loc
}
elif ($expectError and ($error | checkResult)) then
{
$name,
passed: true,
output: $error,
$loc
}
elif ($expectError and ($error | checkResult | not)) then
{
$name,
passed: false,
reason: "Expected error but received different error",
output: $error,
$loc
}
else
error("unknown error")
end
);
def expectPassed:
if (.passed | not) then
error(. | printTestResult)
end;
def expectPassed($result): $result | expectPassed;
def expectFailed:
if (.passed) then
error(. | printTestResult)
end;
def expectFailed($result): $result | expectFailed;
def testTests:
expectPassed(runTest(
$__loc__;
"passing test";
true;
. == true;
false
)) |
expectPassed(runTest(
$__loc__;
"error expected and equal";
error("error");
. == "error";
true
)) |
expectFailed(runTest(
$__loc__;
"failing test";
true;
. == false;
false
)) |
expectFailed(runTest(
$__loc__;
"error expected but no error";
null;
. == null;
true
)) |
expectFailed(runTest(
$__loc__;
"error not expected";
error("error");
. == null;
false
)) |
expectFailed(runTest(
$__loc__;
"error different";
error("error");
. == "different error";
true
));
def testLibMain:
testTests |
empty |
halt_error(0);

17
tool/tests.jq Normal file
View file

@ -0,0 +1,17 @@
#import "./testdata/tests_export" as $exportData;
include "journalUtils";
include "testLib";
def testsMain:
$ARGS.named["file:testdata/tests_export.json"][0] as $exportData |
expectPassed(runTest(
$__loc__;
"invalid input to experienceByTitle";
(
$exportData.experiences |
experienceByTitle("Test")
);
. == "error";
false
));

121
tool/utils.jq Normal file
View file

@ -0,0 +1,121 @@
def debugLog($target; $value):
if
($ENV["JQ_DEBUG"] == $target)
or
($target == "*")
or
($ENV["JQ_DEBUG"] | split(",") | any(. as $debugTarget | $target | startswith($debugTarget | split("*")[0])))
then
debug({$target, $value})
end;
def assert(cond; $msg): if cond then . else error($msg) end;
def assert($loc; cond; $msg): assert(cond; "\($loc.file):\($loc.line): " + $msg);
def titleCase:
[splits("\\b") | select(length>0)]
| map((.[:1]|ascii_upcase) + (.[1:] |ascii_downcase))
| join("");
def lpad(string;len;fill):
if len == 0 then string else (fill * len)[0:len] + string end;
def rpad(string;len;fill):
if len == 0 then string else string + (fill * len)[0:len] end;
def firstN($n): .[:$n];
# only use when // does not suffice
def ifNullDefault($value; $default):
if $value != null then
$value
else
$default
end;
def orderedUnique:
(reduce .[] as $value ([];
if
(. | any(index($value)) | not)
then . += [$value]
else . end
));
def printPrettyTable($title; $columnTitles; $rows):
if [$rows[] | length] | unique | length > 1 then error("non-even number of columns") end |
$title | if . == null then "" end | . as $title |
" | " as $columnSeperator |
([$rows[] | length] | unique[0] // 0) as $columns |
[$rows[] as $columns | [$columns[] | length]] as $columnLengths |
(reduce $columnLengths[] as $lengths ([range($columns) | 0];
[range($columns) as $columnNumber | [.[$columnNumber], $lengths[$columnNumber]] | max]
)) as $maxColumnLengths |
(reduce $columnLengths[] as $lengths ($maxColumnLengths;
[range($columns) as $columnNumber | [.[$columnNumber], ($columnTitles[$columnNumber] | length)] | max]
)) as $maxColumnLengths |
(reduce range($columns) as $columnNumber ([];
$columnTitles[$columnNumber] as $title |
. += [
if $columnNumber == 0 then
lpad($title; $maxColumnLengths[$columnNumber] - ($title | length) ; " ")
else
rpad($title; $maxColumnLengths[$columnNumber] - ($title | length) ; " ")
end
]
)) | join($columnSeperator) as $columnTitleRow |
(reduce $rows[] as $row ([];
. += [(reduce range($columns) as $columnNumber ([]; . += [
$row[$columnNumber] as $column |
if $columnNumber == 0 then
lpad($column; $maxColumnLengths[$columnNumber] - ($column | length) ; " ")
else
rpad($column; $maxColumnLengths[$columnNumber] - ($column | length) ; " ")
end
]))]
)) as $rows |
$title | length as $titleLength |
((($columns - 1) * ($columnSeperator | length)) + ($maxColumnLengths | add)) as $maxRowLength |
[
$maxRowLength,
$titleLength
] | max as $maxLength |
# Pad title to be centered to maximum row length
$title | if $titleLength < $maxRowLength then
lpad($title; ($maxLength - $titleLength) / 2; " ")
end | . as $title |
# if title is longer than maximum row length, pad the first row the difference between them
$rows | if $titleLength > $maxRowLength then
(reduce .[] as $row ([];
. += [
[
lpad($row[0]; $titleLength - $maxRowLength; " ")
] + $row[1:]
]
))
end | . as $rows |
($rows | map(join($columnSeperator))) as $rows |
if $title != null then
[
$title,
([range($maxLength) | "-"] | join("")),
$columnTitleRow,
([range($maxLength) | "-"] | join("")),
$rows
] | flatten | join("\n") + "\n"
else
[
$columnTitleRow,
$rows
] | flatten | join("\n") + "\n"
end;