Developing a feature rich Natural Language Processor
- Using just jq, Nix & Bash
I've always been drawn to voice commands. Maybe it's because of the extended use cases that come with being blind - you can imagine fumbling around for the TV remote when not being able to see? That sucks! Now I can simply tell the TV to start. Or if I prefer, simply ask where the remote is and the Nvidia Shield Remote plays a sound. Maybe it's just the appeal of being able to multitask with a third hand that doesn't exist. Either way, the idea of speaking to my computer and having it do things has always fascinated me. Voice as an interface feels natural, powerful... and deeply underutilized.
# π¦ says βΆ I use 20x magnification when I code and debug. I use emoji to simplify logs for myself. If you can't handle my code style you can disable most of it on this website by toggling the button in the navbar. Shall duck continue?
Let's rewind a bit. I've always had a soft spot for doing things the stupid way. Not "stupid" as in broken, but "stupid" as in... unorthodox, unnecessary, and definitely not optimized for scale. The kind of stupid that makes you ask: "Wait, you did what in Bash?!"
The Genesis: Bash Meets Nix
Early in this project, no one - not even I - could give a good reason for doing it in Bash. There were no benchmarks to beat, no ecosystem gaps to fill, and definitely no best practices to follow. Maybe it's still a stupid idea. But I kept coming back to one thing: Bash has flexibility.
You can bend it in ways most people can't imagine.
# π¦ says βΆ There is no spoon...
Meanwhile, tools like Home Assistant's Assist were starting to bore me. They worked, sure - but I constantly felt locked in, boxed by high-level abstractions and limited customization. I didn't want a voice assistant that just reacts - I wanted one that obeys.
While being an active contributor within the community, I multiple times got or noticed people getting responses from the dev's "That's not possible." and "No, you can't do that". Which was my breaking point to leave and go my own way.
That's where Nix came in. Going the Nix way - what could be more fun?
Using Nix's declarative configuration model, I realized I could define a CLI interface that held all my custom scripts and logic - the glue that tied intent to action. I could describe sentences declaratively and map them directly to Bash scripts.
No overhead. No dependencies. No noise. Just me, a microphone, and a shell.
I Did Not Mean for It to Get This Complex
What started as a small utility to let me run shell commands by typing natural language sentences quickly spiraled into a full-blown NLP system. With Bash. And Nix. And thousands of dynamically generated regular expressions.
This blog post walks you through the actual code behind a voice-command-esque CLI script framework I built using Nix to organize, parse, cache, and test structured sentence intents to build and execute Shell commands.
π¦π HOME via π v3.12.10
01:41:26 β― yo do "please stop test auto 5 yes wild card testing here"
ββ(yo-demo)
βπ¦
βββΆ --action stop
βββΆ --target test
βββΆ --mode auto
βββΆ --duration 5
βββΆ --confirm yes
βββΆ --wild wild card testing here
[π¦π] [01:41:31] βοΈDEBUGβοΈ βΆ +0.007 s Script Started!
[π¦π] [01:41:31] βοΈDEBUGβοΈ βΆ +0.013 s SCript Executed!
[π¦π] [01:41:31] βοΈDEBUGβοΈ βΆ +0.020 s Script finished!
Nix Configuration: Declaring Intents
yo = {
scripts = {
demo = {
parameters = [
{ name = "action"; description = "Action to perform"; default = "run"; }
{ name = "target"; description = "Target of the action"; }
{ name = "intensity"; description = "Level of intensity"; type = "int"; optional = true; }
{ name = "duration"; description = "Duration in seconds"; type = "int"; optional = true; }
{ name = "mode"; description = "Mode of operation"; optional = true; }
{ name = "confirm"; description = "Whether to confirm the action"; optional = true; }
{ name = "wild"; description = "Wildcard testing"; optional = true; }
];
code = ''
echo "Script executed!"
'';
};
};
do = {
intents = {
demo = {
data = [{
sentences = [
"please {action} {target} {mode} {duration} {confirm} {wild}"
];
lists = {
action.values = [
{ "in" = "[run|execute|start]"; out = "RUN"; }
{ "in" = "[stop|terminate|halt]"; out = "STOP"; }
];
target.values = [
{ "in" = "[process|program|sequence]"; out = "PROCESS"; }
{ "in" = "[test|check|validation]"; out = "TEST"; }
];
mode.values = [
{ "in" = "[auto|automatic]"; out = "AUTO"; }
{ "in" = "[manual|manually]"; out = "MANUAL"; }
];
confirm.values = [
{ "in" = "[yes|confirm|sure]"; out = "YES"; }
{ "in" = "[no|cancel|decline]"; out = "NO"; }
];
intensity.values = builtins.genList (i: {
"in" = toString (i + 1);
out = toString (i + 1);
}) 10;
duration.values = builtins.genList (i: {
"in" = toString ((i + 1) * 10);
out = toString ((i + 1) * 10);
}) 6;
wild.wildcard = true;
};
}];
};
};
};
};
};
Early on, I realized Nix could give me a ton of structure and organization. Since I have been running NixOS system wide for some years now, I have grown comfortable writing Nix expressions. Nix will let user define software configurations declaratively. I'm abusing it for voice logic. I wanted to declaratively define scripts and bind natural language intents to each of them - and Nix is great at producing structured derivations.
Each script gets a name, description, category, and as many parameter options as prefered., Add a set of intent sentences to that.
These are eventually transformed into regular expressions, cached, and tested. Lists can optionally be defined mapping in words to out words. Or set the list value as a wildcard if input for the parameter should be a wildcard and accept any input.
From Sentences to Shell Scripts
The core idea was: take a sentence from the user, figure out what script it maps to, and run it with the correct arguments. Sounds simple? I wouldn't really call it easy, but okay.
# π¦ says βΆ Logical first steps
let scripts = config.yo.scripts; # π¦ says βΆ import all dem scripts scriptNames = builtins.attrNames scripts; # π¦ says βΆ names can be useful too scriptNamesWithIntents = builtins.filter (scriptName: builtins.hasAttr scriptName config.yo.do.intents ) scriptNames; # π¦ says βΆ scripts with no sentences - skippin' dem yo
Intent Parsing + Entity Resolution
To match user input, I needed to preprocess the input sentence, resolve any custom entities, and match it against a giant list of potential phrases. Oh yeah, and some of the words in those phrases were dynamic parameters.
# π¦ says βΆ where da magic dynamic regex iz at makePatternMatcher = scriptName: let dataList = config.yo.do.intents.${scriptName}.data; in '' # π¦ says βΆ diz iz how i pick da script u want match_${scriptName}() { # π¦ says βΆ shushin' da caps β lowercase life 4 cleaner dyn regex zen β¨ local input="$(echo "$1" | tr '[:upper:]' '[:lower:]')" # π¦ says βΆ always show input in debug mode # π¦ says βΆ watch the fancy stuff live in action dt_debug "Trying to match for script: ${scriptName}" >&2 dt_debug "Input: $input" >&2 # π¦ says βΆ duck presentin' - da madnezz ${lib.concatMapStrings (data: lib.concatMapStrings (sentence: lib.concatMapStrings (sentenceText: let # π¦ says βΆ now sentenceText is one of the expanded variants! parts = lib.splitString "{" sentenceText; # π¦ says βΆ diggin' out da goodies from curly nests! Gimme dem {param} nuggets! firstPart = lib.escapeRegex (lib.elemAt parts 0); # π¦ says βΆ gotta escape them weird chars restParts = lib.drop 1 parts; # π¦ says βΆ now we in the variable zone quack? # π¦ says βΆ process each part to build regex and params regexParts = lib.imap (i: part: let split = lib.splitString "}" part; # π¦ says βΆ yeah yeah curly close that syntax shell param = lib.elemAt split 0; # π¦ says βΆ name of the param in da curly β ex: {user} after = lib.concatStrings (lib.tail split); # π¦ says βΆ anything after the param in this chunk # π¦ says βΆ Wildcard mode! anything goes - duck catches ALL the worms! (.*) isWildcard = data.lists.${param}.wildcard or false; regexGroup = if isWildcard then "(.*)" else "\\b([^ ]+)\\b"; # π¦ says βΆ ^ da regex that gon match actual input text in { regex = regexGroup + lib.escapeRegex after; param = param; } ) restParts; fullRegex = let clean = lib.strings.trim (firstPart + lib.concatStrings (map (v: v.regex) regexParts)); in "^${clean}$"; # π¦ says βΆ mash all regex bits 2gether paramList = map (v: v.param) regexParts; # π¦ says βΆ the squad of parameters in '' local regex='^${fullRegex}$' dt_debug "REGEX: $regex" if [[ "$input" =~ $regex ]]; then # π¦ says βΆ DANG DANG β regex match engaged ${lib.concatImapStrings (i: paramName: '' # π¦ says βΆ extract match group #i+1 β param value, come here plz param_value="''${BASH_REMATCH[${toString (i+1)}]}" # π¦ says βΆ if param got synonym, apply the duckfilter if [[ -n "''${param_value:-}" && -v substitutions["$param_value"] ]]; then subbed="''${substitutions["$param_value"]}" if [[ -n "$subbed" ]]; then param_value="$subbed" fi fi ${lib.optionalString ( data.lists ? ${paramName} && !(data.lists.${paramName}.wildcard or false) ) '' # π¦ says βΆ apply substitutions before case matchin' if [[ -v substitutions["$param_value"] ]]; then param_value="''${substitutions["$param_value"]}" fi case "$param_value" in ${makeEntityResolver data paramName} *) ;; esac ''} # π¦ says βΆ declare global param β duck want it everywhere! (for bash access) declare -g "_param_${paramName}"="$param_value" declare -A params=() params["${paramName}"]="$param_value" matched_params+=("$paramName") '') paramList} # π¦ says βΆ set dat param as a GLOBAL VAR yo! every duck gotta know # π¦ says βΆ build cmd args: --param valu cmd_args=() ${lib.concatImapStrings (i: paramName: '' value="''${BASH_REMATCH[${toString i}]}" cmd_args+=(--${paramName} "$value") '') paramList} dt_debug "REMATCH 1: ''${BASH_REMATCH[1]}" dt_debug "REMATCH 2: ''${BASH_REMATCH[2]}" dt_debug "MATCHED SCRIPT: ${scriptName}" dt_debug "ARGS: ''${cmd_args[@]}" return 0 fi '') (expandOptionalWords sentence) ) data.sentences ) dataList} return 1 } ''; # π¦ says βΆ dat was fun! let'z do it again some time
# π¦ says βΆ Funny side note, before this project, regular expressions was diz duck's absolute worst nightmare. These days diz duck quacktually don't mind it dat much.
The Performance Bomb
As I implemented more features, performance became explosive. I wanted my sentence definitions to have [optional|words] and (required|one|of|these|words) patterns. This led to combinatorial explosion.
cartesianProductOfLists = lists: # π¦ says βΆ if da listz iz empty .. if lists == [] then [ [] ] # π¦ says βΆ .. i gib u empty listz of listz yo got it? else # π¦ says βΆ ELSE WAT?! let # π¦ says βΆ sorry.. i gib u first list here u go yo head = builtins.head lists; # π¦ says βΆ remaining listz for u here u go bro! tail = builtins.tail lists; # π¦ says βΆ calculate combinations for my tail - yo calc wher u at?! tailProduct = cartesianProductOfLists tail; in # π¦ says βΆ for everyy x in da listz .. lib.concatMap (x: # π¦ says βΆ .. letz combinez wit every tail combinationz .. map (y: [x] ++ y) tailProduct ) head; # π¦ says βΆ dang! datz a DUCK COMBO alright! # π¦ says βΆ here i duckie help yo out! makin' yo life eazy sleazy' wen declarative sentence yo typin' expandOptionalWords = sentence: # π¦ says βΆ qucik & simple sentences we quacky & hacky expandin' let # π¦ says βΆ CHOP CHOP! Rest in lil' Pieceez bigg sentence!!1 tokens = lib.splitString " " sentence; # π¦ says βΆ definin' dem wordz in da (braces) taggin' dem' wordz az (ALTERNATIVES) lettin' u choose one of dem wen triggerin' isRequiredGroup = t: lib.hasPrefix "(" t && lib.hasSuffix ")" t; # π¦ says βΆ puttin' sentence wordz in da [bracket] makin' em' [OPTIONAL] when doin' u don't have to be pickin' woooho isOptionalGroup = t: lib.hasPrefix "[" t && lib.hasSuffix "]" t; expandToken = token: # π¦ says βΆ dis gets all da real wordz out of one token (yo!) if isRequiredGroup token then let # π¦ says βΆ thnx 4 lettin' ducklin' be cleanin' - i'll be removin' dem "()" clean = lib.removePrefix "(" (lib.removeSuffix ")" token); alternatives = lib.splitString "|" clean; # π¦ says βΆ use "|" to split (alternative|wordz) yo in # π¦ says βΆ dat's dat 4 dem alternativez alternatives else if isOptionalGroup token then let # π¦ says βΆ here we be goin' again - u dirty and i'll be cleanin' dem "[]" clean = lib.removePrefix "[" (lib.removeSuffix "]" token); alternatives = lib.splitString "|" clean; # π¦ says βΆ i'll be stealin' dat "|" from u in # π¦ says βΆ u know wat? optional means we include blank too! alternatives ++ [ "" ] else # π¦ says βΆ else i be returnin' raw token for yo [ token ]; # π¦ says βΆ now i gib u generatin' all dem combinationz yo expanded = cartesianProductOfLists (map expandToken tokens); # π¦ says βΆ clean up if too much space, smush back into stringz for ya trimmedVariants = map (tokenList: let # π¦ says βΆ join with spaces then trim them suckers raw = lib.concatStringsSep " " tokenList; # π¦ says βΆ remove ALL extra spaces cleaned = lib.replaceStrings [" "] [" "] (lib.strings.trim raw); in # π¦ says βΆ wow now they be shinin' cleaned ) expanded; # π¦ says βΆ and they be multiplyyin'! # π¦ says βΆ throwin' out da empty and cursed ones yo nonEmpty = lib.filter (s: s != "") trimmedVariants; hasFixedText = v: builtins.match ".*[^\\{].*" v != null; # π¦ says βΆ no no no, no nullin' validVariants = lib.filter hasFixedText nonEmpty; in # π¦ says βΆ returnin' all unique variantz of da sentences β holy duck dat'z fresh lib.unique validVariants;
This is where the bomb went off. The module went from being instant to extremely slow.
Even with a very detailed debugging system in place, when you are generating code with this extensiveness, finding the problematic line can take time, later i found that it was caused by a single intent definition, my timer intent....
I was defining this intent in the most genius way, something similar to:
sentences = [ "(create|set|start|launch) [a] timer [for] {hours} (hour|hours) {minutes} (minute|minutes) {seconds} (second|seconds)" "(create|set|start|launch) [a] timer [for] {minutes} (minute|minutes) [and] {seconds} (second|seconds)" "(create|set|start|launch) [a] timer [for] {minutes} (minute|minutes)" "(create|set|start|launch) [a] timer [for] {seconds} seconds" ]; lists = { hours.values = lib.genList (n: { "in" = lib.concatStringsSep "|" [ (toString n) ("kl " + toString n) (toString n + "h") (builtins.elemAt numberWords n) ]; out = toString n; }) 24; minutes.values = lib.genList (n: { "in" = lib.concatStringsSep "|" [ (toString n) (toString n + "m") ("minut " + toString n) (builtins.elemAt numberWords n) ]; out = toString n; }) 60; seconds.values = lib.genList (n: { "in" = lib.concatStringsSep "|" [ (toString n) (toString n + "s") ("sekund " + toString n) (builtins.elemAt numberWords n) ]; out = toString n; }) 60;
To better paint the picture of why this was a bad idea, I'll do the math
Combinational explosion:
- Sentence 4:
Fixed Variants: 16. Param combos: 240. Total variants: 16 Γ 240 = 3,840 - Sentence 3:
Fixed variants: 32. Param combos: 240. Total variants: 32 Γ 240 = 7,680 - Sentence 2:
Fixed variants: 128. Param combos: 240 Γ 240 = 57,600. Total variants: 128 Γ 57,600 = 7,372,800 - Sentence 1:
Fixed variants: 128. Param combos: 96 Γ 240 Γ 240 = 5,529,600. Total variants: 128 Γ 5,529,600 = 707,788,800 (!) - Total number of unique sentence variations:
715,173,120
Yes β over 715 million possible combinations.
Insane? Definitely.
Useful? Ehh.. I don't think so?
But I still think this example shows how powerful this can be if you'd decide to go that crazy route.
This is where I decided to define with caution, and also where I implemented the next performance feature, the priority system.
Basic functionallity that let user define order of which the intents are processed for the regex patterns.
# π¦ says βΆ priority system 4 runtime optimization scriptRecordsWithIntents = let # π¦ says βΆ calculate priority calculatePriority = scriptName: config.yo.do.intents.${scriptName}.priority or 3; # π¦ says βΆ default medium # π¦ says βΆ create script records metadata makeRecord = scriptName: rec { name = scriptName; priority = calculatePriority scriptName; hasComplexPatterns = let intent = config.yo.do.intents.${scriptName}; patterns = lib.concatMap (d: d.sentences) intent.data; in builtins.any (p: lib.hasInfix "{" p || lib.hasInfix "[" p) patterns; }; in lib.sort (a: b: # π¦ says βΆ primary sort: lower number = higher priority a.priority < b.priority # π¦ says βΆ secondary sort: simple patterns before complex ones || (a.priority == b.priority && !a.hasComplexPatterns && b.hasComplexPatterns) # π¦ says βΆ third sort: alphabetical for determinism || (a.priority == b.priority && a.hasComplexPatterns == b.hasComplexPatterns && a.name < b.name) ) (map makeRecord scriptNamesWithIntents); # π¦ says βΆ generate optimized processing order processingOrder = map (r: r.name) scriptRecordsWithIntents;
Fuzzy Matching
Exact regex matches were cool... until they weren't.
Since I had been getting longer and longer runtime execution, I really wanted something more than just fuzz here.
I decided to go with a trigrams cache solution, with a falling back to Levenshtein distance algoritm.
I think this quickly turned out in my favor both performance and speed wise.
To be honest, what was tricky about this part was, in what way I would use the functions.
I started with the obvious exact matching falling back to fuzzy matching. Which doubled the runtime if a command had no exact match. (At this point about 50 seconds total), which is way to high for my use case.
I wanted to run the fuzzy matching logic async with the exact matching, holding off with the execution of the potential command until exact matching had failed.
Running multiple jobs in the background in Bash is fine, but it pretty much makes you lose control of the actual process and makes you wonder who is actually steering this boat?
trigram_similarity() { local str1="$1" local str2="$2" declare -a tri1 tri2 # π¦ says βΆ creates 3 char substring from str1 for ((i=0; i<''${#str1}-2; i++)); do tri1+=( "''${str1:i:3}" ) done # π¦ says βΆ creates 3 char substring from str2 for ((i=0; i<''${#str2}-2; i++)); do tri2+=( "''${str2:i:3}" ) done local matches=0 # π¦ says βΆ count how many trigrams from str1 appear in str2 for t in "''${tri1[@]}"; do [[ " ''${tri2[*]} " == *" $t "* ]] && ((matches++)) done # π¦ says βΆ calculate total number of trigrams local total=$(( ''${#tri1[@]} + ''${#tri2[@]} )) # π¦ says βΆ no trigrams? (( total == 0 )) && echo 0 && return # π¦ says βΆ return diceβs coefficient similarity Γ 100 echo $(( 100 * 2 * matches / total )) # π¦ says βΆ 0-100 scale } levenshtein_similarity() { local a="$1" b="$2" local len_a=''${#a} len_b=''${#b} local max_len=$(( len_a > len_b ? len_a : len_b )) (( max_len == 0 )) && echo 100 && return local dist=$(levenshtein "$a" "$b") local score=$(( 100 - (dist * 100 / max_len) )) # π¦ says βΆ boostz da score for same startin' charizard yo [[ "''${a:0:1}" == "''${b:0:1}" ]] && score=$(( score + 10 )) echo $(( score > 100 ? 100 : score )) }
Bash script workflow
Takes --input "some natural language command" from the user.
Load intent data from a JSON file (includes regex patterns, substitutions, etc.).
Load fuzzy index for fallback fuzzy matching.
For each defined script: Load regex substitutions (entities) and store them in an associative array.
Exact Match Phase (Runs in Background):
Iterate through all defined scripts (in order of priority).
Apply regex substitutions to input text.
Try to match input via corresponding match_script functions.
If matched:
Apply substitutions.
Prepare arguments.
Execute yo-script with those arguments.
Signal fuzzy handler to stop
Fuzzy Match Phase (Runs Concurrently):
Normalize input text.
Compute trigram + Levenshtein similarity scores against defined sentences.
Select best match above the threshold.
Apply the same substitution logic.
Match using match_fuzzy_script function.
If matched:
Wait for exact matcher to finish.
If exact didnβt match, execute yo-script with resolved args.
βΆ View the Bash script for the NLP module
in { # π¦ says βΆ YOOOOOOOOOOOOOOOOOO yo.scripts = { # π¦ says βΆ quack quack quack quack quack.... qwack do = { description = "Natural language to Shell script translator with dynamic regex matching and automatic parameter resolutiion"; aliases = ["b"]; category = "βοΈ Configuration"; # π¦ says βΆ duckgorize iz zmart wen u hab many scriptz i'd say! logLevel = "WARNING"; autoStart = false; parameters = [ { name = "input"; description = "Text to parse into a yo command"; optional = false; } { name = "fuzzyThreshold"; description = "Minimum procentage for considering fuzzy matching sucessful. (1-100)"; default = "15"; } ]; # π¦ says βΆ run yo do --help to display all defined voice commands helpFooter = '' WIDTH=$(tput cols) # π¦ duck say βΆ Auto detect width cat <0' "$intent_data_file" 2>/dev/null || echo false) if [[ "$has_lists" != "true" ]]; then echo -n "$text" echo "|declare -A substitutions=()" # π¦ says βΆ empty substitutions return fi # π¦ says βΆ dis is our quacktionary yo replacements=$(jq -r '.["'"$script"'"].substitutions[] | "\(.pattern)|\(.value)"' "$intent_data_file") while IFS="|" read -r pattern out; do if [[ -n "$pattern" && "$text" =~ $pattern ]]; then original="''${BASH_REMATCH[0]}" [[ -z "''$original" ]] && continue # π¦ says βΆ duck no like empty string substitutions["''$original"]="$out" substitution_applied=true # π¦ says βΆ rack if any substitution was applied text=$(echo "$text" | sed -E "s/\\b$pattern\\b/$out/g") # π¦ says βΆ swap the word, flip the script fi done <<< "$replacements" echo -n "$text" echo "|$(declare -p substitutions)" # π¦ says βΆ returning da remixed sentence + da whole } for f in "$MATCHER_DIR"/*.sh; do [[ -f "$f" ]] && source "$f"; done scripts_ordered_by_priority=( ${lib.concatMapStringsSep "\n" (name: " \"${name}\"") processingOrder} ) dt_info "$scripts_ordered_by_priority" find_best_fuzzy_match() { local input="$1" local best_score=0 local best_match="" local normalized=$(echo "$input" | tr '[:upper:]' '[:lower:]' | tr -d '[:punct:]') local candidates mapfile -t candidates < <(jq -r '.[] | .[] | "\(.script):\(.sentence)"' "$YO_FUZZY_INDEX") dt_debug "Found ''${#candidates[@]} candidates for fuzzy matching" for candidate in "''${candidates[@]}"; do IFS=':' read -r script sentence <<< "$candidate" local norm_sentence=$(echo "$sentence" | tr '[:upper:]' '[:lower:]' | tr -d '[:punct:]') local tri_score=$(trigram_similarity "$normalized" "$norm_sentence") (( tri_score < 30 )) && continue local score=$(levenshtein_similarity "$normalized" "$norm_sentence") if (( score > best_score )); then best_score=$score best_match="$script:$sentence" dt_info "New best match: $best_match ($score%)" fi done if [[ -n "$best_match" ]]; then echo "$best_match|$best_score" else echo "" fi } # π¦ says βΆ insert matchers, build da regex empire. yo ${lib.concatMapStrings (name: makePatternMatcher name) scriptNamesWithIntents} # π¦ says βΆ for dem scripts u defined intents for .. exact_match_handler() { for script in "''${scripts_ordered_by_priority[@]}"; do # π¦ says βΆ .. we insert wat YOU sayz & resolve entities wit dat yo resolved_output=$(resolve_entities "$script" "$text") resolved_text=$(echo "$resolved_output" | cut -d'|' -f1) dt_debug "Tried: match_''${script} '$resolved_text'" # π¦ says βΆ we declare som substitutionz from listz we have - duckz knowz why subs_decl=$(echo "$resolved_output" | cut -d'|' -f2-) declare -gA substitutions || true eval "$subs_decl" >/dev/null 2>&1 || true # π¦ says βΆ we hab a match quacky quacky diz sure iz hacky! if match_$script "$resolved_text"; then if [[ "$(declare -p substitutions 2>/dev/null)" =~ "declare -A" ]]; then for original in "''${!substitutions[@]}"; do dt_debug "Substitution: $original >''${substitutions[$original]}"; [[ -n "$original" ]] && dt_info "$original > ''${substitutions[$original]}" # π¦ says βΆ see wat duck did there? done # π¦ says βΆ i hop duck pick dem right - right? fi args=() # π¦ says βΆ duck gettin' ready 2 build argumentz 4 u script for arg in "''${cmd_args[@]}"; do dt_debug "ADDING PARAMETER: $arg" args+=("$arg") # π¦ says βΆ collecting them shell spell ingredients done # π¦ says βΆ final product - hope u like say duck! paramz="''${args[@]}" && echo echo "exact" > "$match_result_flag" # π¦ says βΆ tellz fuzzy handler we done dt_debug "Executing: yo $script $paramz" # π¦ says βΆ EXECUTEEEEEEEAAA β HERE WE QUAAAAACKAAAOAA exec "yo-$script" "''${args[@]}" return 0 fi done } ${lib.concatMapStrings (name: makeFuzzyPatternMatcher name) scriptNamesWithIntents} # π¦ SCREAMS βΆ FUZZY WOOOO TO THE MOON fuzzy_match_handler() { resolved_output=$(resolve_entities "dummy" "$text") # We'll resolve properly after matching resolved_text=$(echo "$resolved_output" | cut -d'|' -f1) fuzzy_result=$(find_best_fuzzy_match "$resolved_text") [[ -z "$fuzzy_result" ]] && return 1 IFS='|' read -r combined match_score <<< "$fuzzy_result" IFS=':' read -r matched_script matched_sentence <<< "$combined" dt_debug "Best fuzzy script: $matched_script" >&2 # π¦ says βΆ resolve entities agein, diz time for matched script yo resolved_output=$(resolve_entities "$matched_script" "$text") resolved_text=$(echo "$resolved_output" | cut -d'|' -f1) subs_decl=$(echo "$resolved_output" | cut -d'|' -f2-) declare -gA substitutions || true eval "$subs_decl" >/dev/null 2>&1 || true # if (( best_score >= $FUZZY_THHRESHOLD )); then # π¦ says βΆ we hab a match quacky quacky diz sure iz hacky! if match_fuzzy_$matched_script "$resolved_text" "$matched_sentence"; then if [[ "$(declare -p substitutions 2>/dev/null)" =~ "declare -A" ]]; then for original in "''${!substitutions[@]}"; do dt_debug "Substitution: $original >''${substitutions[$original]}"; [[ -n "$original" ]] && dt_info "$original > ''${substitutions[$original]}" # π¦ says βΆ see wat duck did there? done # π¦ says βΆ i hop duck pick dem right - right? fi args=() # π¦ says βΆ duck gettin' ready 2 build argumentz 4 u script for arg in "''${cmd_args[@]}"; do dt_debug "ADDING PARAMETER: $arg" args+=("$arg") # π¦ says βΆ collecting them shell spell ingredients done # π¦ says βΆ wait for exact match to finish # while kill -0 "$pid1" 2>/dev/null; do while [[ ! -f "$match_result_flag" || $(cat "$match_result_flag") != "exact_finished" ]]; do sleep 0.05 done # π¦ says βΆ checkz if exact match succeeded yo if [[ $(cat "$match_result_flag") == "exact" ]]; then dt_debug "Exact match already handled execution. Fuzzy exiting." exit 0 fi # π¦ says βΆ final product - hope u like say duck! paramz="''${args[@]}" && echo dt_info "Executing: yo $matched_script $paramz" # π¦ says βΆ EXECUTEEEEEEEAAA β HERE WE QUAAAAACKAAAOAA exec "yo-$matched_script" "''${args[@]}" return 0 fi } # π¦ says βΆ if exact match winz, no need for fuzz! but fuzz ready to quack when regex chokes exact_match_handler & pid1=$! fuzzy_match_handler exit ''; };
Comments on this blog post