nu-std 0.114.1

The standard library of Nushell
Documentation
def cell-path-join []: list<cell-path> -> cell-path {
    each --flatten {|e|
        try { split cell-path } catch { [$e] }
    }
    | into cell-path
}

def add-parent [parent: cell-path]: table<path: cell-path> -> table<path: cell-path> {
    update path { [$parent, $in] | cell-path-join }
}

def get-children []: [any -> table<path: cell-path, item: any>] {
    match $in {
        {} => { transpose path item }
        [..] => { enumerate | rename path item }
        _ => { return [] }
    }
}

def get-children-at [path: cell-path ...paths: cell-path]: [any -> table<path: cell-path, item: any>] {
    match $in {
        {} => {
            get -o $path ...$paths $.""
            | zip [$path ...$paths]
            | each --flatten {|e|
                match $e.0 {
                    null | [] => []
                    [..] => {
                        $e.0 | get-children | add-parent $e.1
                    }
                    $x => [{path: $e.1, item: $x}]
                }
            }
        }
        _ => { get-children }
    }
}

def make-descend-fn [arg, rest] {
    let arg_type = match ($arg.item | describe --detailed).type {
        "string" | "int" => "cell-path",
        $x => $x,
    }
    match [$arg_type, $rest.item] {
        ["nothing", []] => {
            {|| get-children }
        }
        ["cell-path", []] => {
            {|| get-children-at $arg.item }
        }
        ["cell-path", _] => {
            let labels = $rest.item | each {|e|
                let md = metadata
                match ($e | describe) {
                    "cell-path" | "string" | "int" => { }
                    $type => {
                        {
                            span: $md.span
                            text: $'expected cell-path, got ($type)'
                        }
                    }
                }
            }
            if $labels != [] {
                error make {
                    msg: "Type mismatch"
                    labels: $labels
                }
            }
            {|| get-children-at $arg.item ...$rest.item }
        }
        ["closure", []] => {
            {|parent|
                let output = try {
                    $parent | do $arg.item $parent
                } catch {
                    return []
                }
                | append []

                let has_item = try { $output | get item; true } catch { false }

                $output
                | if not $has_item { wrap item } else { }
                | default "<closure>" path
            }
        }
        ["closure", _] => {
            error make {
                msg: "Closure accessor can't be combined with other accessors."
                labels: [
                    {
                        text: "closure",
                        span: $arg.span
                    }
                    {
                        text: "additional accessors not allowed",
                        span: $rest.span
                    }
                ]
                help: "Remove the additional accessors, or modify the closure to access the same values."
            }
        }
        [$type, _] => {
            error make {
                msg: "Type mismatch."
                labels: [
                    {
                        text: $"Cannot get child values using a ($type)"
                        span: $arg.span
                    }
                ]
                help: "Try using a cell-path or a closure."
            }
        }
    }
}

def make-depth-first-fn [descend: closure]: nothing -> closure {
    {|stack| match $stack {
        [] => { {} }
        [$head, ..$tail] => {
            let children = $head.item | do $descend $head.item | add-parent $head.path
            {
                out: $head,
                next: ($children ++ $tail),
            }
        }
    }}
}

def make-breadth-first-fn [descend: closure]: nothing -> closure {
    {|queue| match $queue {
        [] => { {} }
        [$head, ..$tail] => {
            let children = $head.item | do $descend $head.item | add-parent $head.path
            {
                out: $head,
                next: ($tail ++ $children),
            }
        }
    }}
}

# Recursively descend a nested value, returning each value along with its path.
#
# Recursively descends its input, producing all values as a stream, along with
# the cell-paths to access those values.
#
# If a cell-path is provided as argument, rather than traversing all children,
# only the given cell-path is followed. The cell-path is evaluated at each level,
# relative to the parent element.
#
# If a closure is provided, it will be used to get children from parent values.
# The closure can have a variety of return types, each one in the list being
# coerced to the next type:
#  - list<any>
#  - table<item: any>
#  - table<item: any, path: any>
# `path` is used to construct the full path of an item, being concatenated to
# the parent item's path. If a child item does not have a `path` field, its
# path defaults to `<closure>`
@example "Access each possible path in a value" {
    {
        "foo": {
            "egg": "X"
            "spam": "Y"
        }
        "bar": {
            "quox": ["A" "B"]
        }
    }
    | recurse
    | update item { to nuon }
} --result [
    [path, item];
    [ ($.),           r#'{foo: {egg: X, spam: Y}, bar: {quox: [A, B]}}'# ],
    [ ($.foo),        r#'{egg: X, spam: Y}'# ],
    [ ($.bar),        r#'{quox: [A, B]}'# ],
    [ ($.foo.egg),    r#'X'# ],
    [ ($.foo.spam),   r#'Y'# ],
    [ ($.bar.quox),   r#'[A, B]'# ],
    [ ($.bar.quox.0), r#'A'# ],
    [ ($.bar.quox.1), r#'B'# ]
]
@example "Recurse example from `jq`'s manpage" {
    {"name": "/", "children": [
        {"name": "/bin", "children": [
            {"name": "/bin/ls", "children": []},
            {"name": "/bin/sh", "children": []}]},
        {"name": "/home", "children": [
            {"name": "/home/stephen", "children": [
                {"name": "/home/stephen/jq", "children": []}]}]}]}
    | recurse children
    | get item.name
} --result [/, /bin, /home, /bin/ls, /bin/sh, /home/stephen, /home/stephen/jq]
@example "Recurse example from `jq`'s manpage, using depth-first traversal like `jq`" {
    {"name": "/", "children": [
        {"name": "/bin", "children": [
            {"name": "/bin/ls", "children": []},
            {"name": "/bin/sh", "children": []}]},
        {"name": "/home", "children": [
            {"name": "/home/stephen", "children": [
                {"name": "/home/stephen/jq", "children": []}]}]}]}
    | recurse children --depth-first
    | get item.name
} --result [/, /bin, /bin/ls, /bin/sh, /home, /home/stephen, /home/stephen/jq]
@example '"Recurse" using a closure' {
    2
    | recurse { ({path: square item: ($in * $in)}) }
    | take while { $in.item < 100 }
} --result [
    [path, item];
    [$., 2],
    [$.square, 4],
    [$.square.square, 16]
]
@search-terms jq ".." nested
export def recurse [
    accessor?: oneof<cell-path, closure> # Specify how to get children from parent value.
    ...rest: cell-path # additional cell-paths (can't be combined with closure `accessor`)
    --depth-first # Descend depth-first rather than breadth first
]: [any -> list<any>] {
    let root = $in

    let spanned_accessor = {
        item: $accessor
        span: (metadata $accessor).span
    }
    let spanned_rest = {
        item: $rest
        span: (metadata $rest).span
    }
    let descend = make-descend-fn $spanned_accessor $spanned_rest

    let fn = if $depth_first {
        make-depth-first-fn $descend
    } else {
        make-breadth-first-fn $descend
    }

    generate $fn [{path: ($.), item: $root }]
}

# Helper for `only` errors
def only-error [msg: string, meta: record, label: string]: nothing -> error {
  error make {
    msg: $msg,
    label: {
      text: $label,
      span: $meta.span,
    }
  }
}

# Get the only element of a list or table, ensuring it exists and there are no extra elements.
#
# Similar to `first` with no arguments, but errors if there are additional
# items when there should only be one item. This can help avoid issues when more
# than one row than expected matches some criteria.
#
# This command is useful when chained with `where` to ensure that only one row
# meets the given condition.
#
# If a cell path is provided as an argument, it will be accessed after the first
# element. For example, `only foo` is roughly equivalent to `get 0.foo`, with
# the guarantee that there are no additional elements.
#
# Note that this command currently collects streams.
@search-terms first single
@category filters
@example "Get the only item in a list, ensuring it exists and there's no additional items" --result 5 {
    [5] | only
}
@example "Get the item (if present) from a list that has no more than one item" --result null {
    [] | only
}
@example "Get the `name` column of the only row in a table" --result "foo" {
    [{name: foo, id: 5}] | only name
}
@example "Get the modification time of the file named foo.txt" {
    ls | where name == "foo.txt" | only modified
}
export def only [
    --optional # Return `null` if there are no elements (does not affect behavior of the `cell_path` argument)
    cell_path?: cell-path # The cell path to access within the only element.
]: [table -> any, list -> any] {
    peek 2 | metadata access {|meta|
        match $meta.peek.value? {
            [] => {
                # discard pipeline input
                null;
                # had to move it here from the match guard. while the closure
                # itself has access to `$optional`, for some reason it was not
                # available to the match guard at runtime
                if not $optional {
                    only-error "expected non-empty table/list" $meta "empty"
                }
            }
            [$one] => ($one | if $cell_path != null { get $cell_path } else { })
            _ => (only-error "expected only one element in table/list" $meta "has more than one element")
        }
    }
}

def prod-error-helper [] {
    items {|k v|
        match $v {
            [..] => null
            _ => {
                let ty = $v | describe
                {
                    span: (metadata $v).span
                    text: $'expected list, got ($ty)'
                }
            }
        }
    }
    | compact -e
    | let labels

    if $labels != [] {
        error make {
            msg: "Type mismatch. All values must be lists."
            code: "nu::shell::type_mismatch"
            labels: $labels
        }
    }
}

def prod-mk-fn [src: record]: nothing -> closure {
    $src | prod-error-helper
    $src
    | transpose name items
    | reduce --fold ({|| }) {|source prev_fn|
        {||
            do $prev_fn
            | each --flatten {|l|
                $source.items | each {|r|
                    $l | merge {($source.name): $r}
                }
            }
        }
    }
}

# Cartesian product of multiple lists as a table.
@search-terms cartesian product combination
@category generators
@example "Product of two lists" {
    [1, 2] | prod { rhs: [a, b] }
} --result [
    [in, rhs];
    [1, a]
    [1, b]
    [2, a]
    [2, b]
]
@example "All element combinations of multiple lists" {
    prod {
        size: [little, big]
        color: [red, blue]
        shape: [circle, box]
    }
} --result [
    [size, color, shape];
    [little, red, circle],
    [little, red, box],
    [little, blue, circle],
    [little, blue, box],
    [big, red, circle],
    [big, red, box],
    [big, blue, circle],
    [big, blue, box]
]
export def prod [
    src: record # Lists to combine. All values must be lists.
]: [
    nothing -> table
    list -> table
    range -> table
] {
    peek | metadata access {|md|
        match $md.peek {
            {type: "list"} => { wrap in }
            {type: "empty"} => { [{}] }
        }
    }
    | do (prod-mk-fn $src)
}