From 6c344e061f1ebf9f58127c41ce26bba6a63acfc9 Mon Sep 17 00:00:00 2001 From: Sage Date: Tue, 10 Mar 2026 10:13:21 +0000 Subject: [PATCH] Fix hr call --- modules/home/terminal/hr/hr.nu | 184 ++++++++++++++++++++++++++++----- 1 file changed, 157 insertions(+), 27 deletions(-) diff --git a/modules/home/terminal/hr/hr.nu b/modules/home/terminal/hr/hr.nu index b0c3e9b..e8ec346 100644 --- a/modules/home/terminal/hr/hr.nu +++ b/modules/home/terminal/hr/hr.nu @@ -1,4 +1,18 @@ # HR - Hefring work tooling for nushell +# +# This module provides commands for working with Hefring Cloud Run services and Cloud Functions. +# +# Key features: +# - Shell-style argument passing: -key value supports dot notation for nested structures +# Example: hr call service -items.0.type "trip" -items.0.id "123" +# +# - Nushell record syntax with --data flag for complex payloads: +# Example: hr call service --data {company_id: "abc", items: [{type: "trip"}]} +# +# - Automatic type detection: numbers, booleans, and JSON are parsed automatically +# Example: -count "42" → number, -active "true" → boolean +# +# - Pretty display: Payloads are shown as Nushell tables before sending def _hr_usage [] { print "Usage: hr " @@ -181,29 +195,89 @@ def _hr_init_base [name: string, write_files: closure, ignores: list] { direnv allow } +def _hr_setpath [data: any, path: list, value: any] { + # Recursively build nested structure, similar to jq's setpath + if ($path | is-empty) { + $value + } else if ($path | length) == 1 { + $data | upsert ($path | first) $value + } else { + let key = ($path | first) + let rest = ($path | skip 1) + let next_key = ($rest | first) + + # Check if key exists in data and get intermediate value + let key_type = ($key | describe) + let data_type = ($data | describe) + let is_list = ($data_type | str starts-with "list") or ($data_type | str starts-with "table") + + # Determine if key exists and get intermediate structure + let intermediate = if $key_type == "int" and $is_list { + # Array index + if $key < ($data | length) { + $data | get $key + } else { + # Index doesn't exist, create structure based on next key + if ($next_key | describe) == "int" { [] } else { {} } + } + } else if $key_type != "int" { + # Object key + if $key in $data { + $data | get $key + } else { + # Key doesn't exist, create structure based on next key + if ($next_key | describe) == "int" { [] } else { {} } + } + } else { + # Data is not a list but key is int - create array + if ($next_key | describe) == "int" { [] } else { {} } + } + + # Recursively set the rest of the path + let updated_intermediate = (_hr_setpath $intermediate $rest $value) + + # Update data with the new intermediate value + $data | upsert $key $updated_intermediate + } +} + def _hr_add_json_field [json: string, key: string, value: string] { # Determine if value should be parsed as raw JSON or treated as a string let is_bool = ($value == "true" or $value == "false") let is_number = ($value =~ '^-?(0|[1-9][0-9]*)(\.[0-9]+)?$') let is_json_container = ($value =~ '^\[' or $value =~ '^\{') - if $is_bool or $is_number { - $json | from json | upsert $key ($value | from json) | to json + # Parse the value into the appropriate type + let parsed_value = if $is_bool or $is_number { + $value | from json } else if $is_json_container { let parsed = (do { $value | from json } | complete) if $parsed.exit_code == 0 { - $json | from json | upsert $key ($value | from json) | to json + $value | from json } else { print $"Warning: Value for '($key)' looks like JSON but is invalid. Treating as string." --stderr - $json | from json | upsert $key $value | to json + $value } } else { - $json | from json | upsert $key $value | to json + $value } + + # Convert key path (e.g., "items.0.type") to a list of path segments + # Numbers are converted to integers for array indexing + let path_segments = ($key | split row "." | each { |segment| + if ($segment =~ '^[0-9]+$') { + $segment | into int + } else { + $segment + } + }) + + let data = ($json | from json) + _hr_setpath $data $path_segments $parsed_value | to json } def _hr_parse_flags [args: list] { - mut payload = {} + mut json_str = "{}" mut i = 0 while $i < ($args | length) { let arg = ($args | get $i) @@ -217,16 +291,28 @@ def _hr_parse_flags [args: list] { if ($next | str starts-with "-") { error make { msg: $"Error: Missing value for option ($key)" } } - $payload = ($payload | upsert $key $next) + $json_str = (_hr_add_json_field $json_str $key $next) $i = $i + 2 } else { error make { msg: $"Error: Unexpected argument '($arg)'" } } } - $payload + $json_str | from json } -def _hr_call [route_arg: string, ...rest: string] { +def _hr_call [args: list] { + if ($args | is-empty) { + print "Usage: hr call [/path] [OPTIONS]" + print "" + print "Options:" + print " -key value Set JSON field (supports dot notation, e.g., -items.0.type \"trip\")" + print " --data Pass structured data as a Nushell record" + return + } + + let route_arg = ($args | first) + let rest = ($args | skip 1) + let parts = if ($route_arg | str contains "/") { let service = ($route_arg | split row "/" | first) let path = "/" + ($route_arg | split row "/" | skip 1 | str join "/") @@ -241,29 +327,81 @@ def _hr_call [route_arg: string, ...rest: string] { "322048751601" } - let payload = (_hr_parse_flags $rest | to json) + # Check if --data flag is present + let data_idx = ($rest | enumerate | where item == "--data" | get index.0? | default (-1)) + + let payload_record = if $data_idx >= 0 { + # Use --data record + if ($data_idx + 1) >= ($rest | length) { + error make { msg: "Error: --data requires a record argument" } + } + let data_str = ($rest | get ($data_idx + 1)) + # Try to parse as Nushell code to get a record + try { + nu -c $data_str + } catch { + error make { msg: $"Error: --data argument must be a valid Nushell record, got: ($data_str)" } + } + } else { + # Use -key value pairs + _hr_parse_flags $rest + } + + let payload = ($payload_record | to json -r) let url = $"https://($parts.service)-($project_number).europe-west1.run.app($parts.path)" print $"Calling ($url)..." - print $payload + print ($payload | from json) let token = (gcloud auth print-identity-token) - http post $url $payload --content-type "application/json" --headers { Authorization: $"bearer ($token)" } + ^curl -s -S -L $url -H $"Authorization: Bearer ($token)" -H "Content-Type: application/json" -d $payload } -def _hr_cf [function_name: string, ...rest: string] { - let payload = (_hr_parse_flags $rest | to json) +def _hr_cf [args: list] { + if ($args | is-empty) { + print "Usage: hr cf [OPTIONS]" + print "" + print "Options:" + print " -key value Set JSON field (supports dot notation, e.g., -items.0.type \"trip\")" + print " --data Pass structured data as a Nushell record" + return + } + + let function_name = ($args | first) + let rest = ($args | skip 1) + + # Check if --data flag is present + let data_idx = ($rest | enumerate | where item == "--data" | get index.0? | default (-1)) + + let payload_record = if $data_idx >= 0 { + # Use --data record + if ($data_idx + 1) >= ($rest | length) { + error make { msg: "Error: --data requires a record argument" } + } + let data_str = ($rest | get ($data_idx + 1)) + # Try to parse as Nushell code to get a record + try { + nu -c $data_str + } catch { + error make { msg: $"Error: --data argument must be a valid Nushell record, got: ($data_str)" } + } + } else { + # Use -key value pairs + _hr_parse_flags $rest + } + + let payload = ($payload_record | to json -r) let url = $"https://europe-west1-($env.PROJECT_ID).cloudfunctions.net/($function_name)" print $"Calling ($url)..." - print $payload + print ($payload | from json) let token = (gcloud auth print-identity-token) - http post $url $payload --content-type "application/json" --headers { Authorization: $"bearer ($token)" } + ^curl -s -S -L $url -H $"Authorization: Bearer ($token)" -H "Content-Type: application/json" -d $payload } # HR - Hefring work tooling -export def --env hr [...args: string] { +export def --env --wrapped hr [...args: string] { if ($args | is-empty) { _hr_usage return @@ -326,18 +464,10 @@ export def --env hr [...args: string] { uv pip freeze --exclude-editable | save --append requirements.txt } "call" => { - if ($rest | is-empty) { - print "Usage: hr call [/path] " - return - } - _hr_call ($rest | first) ...($rest | skip 1) + _hr_call $rest } "cf" => { - if ($rest | is-empty) { - print "Usage: hr cf " - return - } - _hr_cf ($rest | first) ...($rest | skip 1) + _hr_cf $rest } _ => { _hr_usage } }