# kv module # # use std-rfc/kv * # # Easily store and retrieve key-value pairs # in a pipeline. # # A common request is to be able to assign a # pipeline result to a variable. While it's # not currently possible to use a "let" statement # within a pipeline, this module provides an # alternative. Think of each key as a variable # that can be set and retrieved. # Stores the pipeline value for later use # # If the key already exists, it is updated to the new value provided. @example "Store the list of files in the home directory" { ls ~ | kv set "home snapshot" } @example "Store a number" { kv set foo 5 } @example "Update a number" { kv set foo { $in + 1 } } export def "kv set" [ key: string value_or_closure?: any --return (-r): string # Whether and what to return to the pipeline output --universal (-u) # Store the key-value pair in a universal database ] { # Pipeline input is preferred, but prioritize # parameter if present. This allows $in to be # used in the parameter if needed. let input = $in # If passed a closure, execute it let arg_type = ($value_or_closure | describe) let value = match $arg_type { closure => { $input | do $value_or_closure } _ => ($value_or_closure | default $input) } # Store values as nuons for type-integrity let kv_pair = { session: '' # Placeholder key: $key value: ($value | to nuon) } let db_open = (db_setup --universal=$universal) try { # Delete the existing key if it does exist do $db_open | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $key } } match $universal { true => { $kv_pair | into sqlite (universal_db_path) -t std_kv_store } false => { $kv_pair | stor insert -t std_kv_store } } # The value that should be returned from `kv set` # By default, this is the input to `kv set`, even if # overridden by a positional parameter. # This can also be: # input: (Default) The pipeline input to `kv set`, even if # overridden by a positional parameter. `null` if no # pipeline input was used. # --- # value: If a positional parameter was used for the value, then # return it, otherwise return the input (whatever was set). # If the positional was a closure, return the result of the # closure on the pipeline input. # --- # all: The entire contents of the existing kv table are returned match ($return | default 'input') { 'all' => (kv list --universal=$universal) 'a' => (kv list --universal=$universal) 'value' => $value 'v' => $value 'input' => $input 'in' => $input 'i' => $input _ => { error make { msg: "Invalid --return option" label: { text: "Must be 'all'/'a', 'value'/'v', or 'input'/'in'/'i'" span: (metadata $return).span } } } } } # Retrieves a stored value by key # # Counterpart of "kv set". Returns null if the key is not found. @example "Retrieve a stored value" { kv get foo } export def "kv get" [ key: string # Key of the kv-pair to retrieve --universal (-u) # Whether to use the universal db ] { let db_open = (db_setup --universal=$universal) do $db_open # Hack to turn a SQLiteDatabase into a table | $in.std_kv_store | wrap temp | get temp | where key == $key # Should only be one occurrence of each key in the stor | get -i value.0 | match $in { # Key not found null => null # Key found _ => { from nuon } } } # List the currently stored key-value pairs # # Returns results as the Nushell value rather than the stored nuon. export def "kv list" [ --universal (-u) # Whether to use the universal db ] { let db_open = (db_setup --universal=$universal) do $db_open | $in.std_kv_store? | each {|kv_pair| { key: $kv_pair.key value: ($kv_pair.value | from nuon ) } } } # Returns and removes a key-value pair export def --env "kv drop" [ key: string # Key of the kv-pair to drop --universal (-u) # Whether to use the universal db ] { let db_open = (db_setup --universal=$universal) let value = (kv get --universal=$universal $key) try { do $db_open # Hack to turn a SQLiteDatabase into a table | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $key } } if $universal and ($env.NU_KV_UNIVERSALS? | default false) { hide-env $key } $value } def universal_db_path [] { $env.NU_UNIVERSAL_KV_PATH? | default ( $nu.data-dir | path join "std_kv_variables.sqlite3" ) } def db_setup [ --universal # Whether to use the universal db ] : nothing -> closure { try { match $universal { true => { # Ensure universal sqlite db and table exists let uuid = (random uuid) let dummy_record = { session: '' key: $uuid value: '' } $dummy_record | into sqlite (universal_db_path) -t std_kv_store open (universal_db_path) | query db "DELETE FROM std_kv_store WHERE key = :key" --params { key: $uuid } } false => { # Create the stor table if it doesn't exist stor create -t std_kv_store -c {session: str, key: str, value: str} | ignore } } } # Return the correct closure for opening on-disk vs. in-memory match $universal { true => {{|| open (universal_db_path)}} false => {{|| stor open}} } } # This hook can be added to $env.config.hooks.pre_execution to enable # "universal variables" similar to the Fish shell. Adding, changing, or # removing a universal variable will immediately update the corresponding # environment variable in all running Nushell sessions. export def "kv universal-variable-hook" [] { {|| kv list --universal | transpose -dr | load-env $env.NU_KV_UNIVERSALS = true } }