962 lines
20 KiB
Markdown
962 lines
20 KiB
Markdown
|
|
# Nushell 0.109+ Guidelines for Syntaxis Project
|
|||
|
|
|
|||
|
|
## Overview
|
|||
|
|
|
|||
|
|
This document provides comprehensive guidelines for writing and maintaining Nushell scripts compatible with Nushell 0.109 and later versions. All scripts in the syntaxis project must follow these standards.
|
|||
|
|
|
|||
|
|
## ⚠️ BREAKING CHANGES in Nushell 0.109+
|
|||
|
|
|
|||
|
|
### Critical: Return Type Annotation Syntax Changed
|
|||
|
|
|
|||
|
|
**OLD SYNTAX (No longer works):**
|
|||
|
|
```nushell
|
|||
|
|
def function_name [param: string] -> string {
|
|||
|
|
$param
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**NEW SYNTAX (Required in 0.109+):**
|
|||
|
|
```nushell
|
|||
|
|
def function_name [param: string]: nothing -> string {
|
|||
|
|
$param
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Key Changes:**
|
|||
|
|
- Return type annotations now require BOTH input type and output type
|
|||
|
|
- Format: `]: input_type -> output_type`
|
|||
|
|
- Input type specifies what the function accepts from pipeline:
|
|||
|
|
- `nothing` - No pipeline input accepted
|
|||
|
|
- `any` - Accepts any pipeline input (stored in `$in`)
|
|||
|
|
- `string`, `list`, etc. - Specific type required from pipeline
|
|||
|
|
|
|||
|
|
**Migration Required:**
|
|||
|
|
All existing scripts using `-> type` syntax MUST be updated to `]: nothing -> type` or the appropriate input type.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Table of Contents
|
|||
|
|
|
|||
|
|
1. [General Principles](#general-principles)
|
|||
|
|
2. [Script Header & Shebang](#script-header--shebang)
|
|||
|
|
3. [Function Definitions](#function-definitions)
|
|||
|
|
4. [Type Annotations](#type-annotations)
|
|||
|
|
5. [Error Handling](#error-handling)
|
|||
|
|
6. [Closures and Blocks](#closures-and-blocks)
|
|||
|
|
7. [Pipeline and Data Flow](#pipeline-and-data-flow)
|
|||
|
|
8. [String Interpolation](#string-interpolation)
|
|||
|
|
9. [Conditionals](#conditionals)
|
|||
|
|
10. [Loops and Iteration](#loops-and-iteration)
|
|||
|
|
11. [Record and List Operations](#record-and-list-operations)
|
|||
|
|
12. [File System Operations](#file-system-operations)
|
|||
|
|
13. [External Commands](#external-commands)
|
|||
|
|
14. [Module System](#module-system)
|
|||
|
|
15. [Testing](#testing)
|
|||
|
|
16. [Performance Considerations](#performance-considerations)
|
|||
|
|
17. [Common Pitfalls](#common-pitfalls)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## General Principles
|
|||
|
|
|
|||
|
|
### ✅ Do's
|
|||
|
|
- **Use type annotations** for function parameters and return types
|
|||
|
|
- **Export public functions** explicitly with `export def`
|
|||
|
|
- **Use descriptive variable names** (snake_case)
|
|||
|
|
- **Leverage the pipeline** for data transformations
|
|||
|
|
- **Handle errors gracefully** with `try-catch`
|
|||
|
|
- **Document complex logic** with comments
|
|||
|
|
- **Use immutable variables** by default; only use `mut` when necessary
|
|||
|
|
|
|||
|
|
### ❌ Don'ts
|
|||
|
|
- **Avoid global mutable state**
|
|||
|
|
- **Don't use deprecated commands** (check Nushell changelog)
|
|||
|
|
- **Don't mix shell commands with Nushell builtins** unnecessarily
|
|||
|
|
- **Avoid deeply nested closures** (prefer helper functions)
|
|||
|
|
- **Don't ignore error cases** in production scripts
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Script Header & Shebang
|
|||
|
|
|
|||
|
|
### Standard Header
|
|||
|
|
```nushell
|
|||
|
|
#!/usr/bin/env nu
|
|||
|
|
# script-name.nu - Brief description of script purpose
|
|||
|
|
# Usage: nu script-name.nu [OPTIONS]
|
|||
|
|
#
|
|||
|
|
# Detailed description if needed
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### With Version Requirement
|
|||
|
|
```nushell
|
|||
|
|
#!/usr/bin/env nu
|
|||
|
|
# Requires Nushell 0.109+
|
|||
|
|
|
|||
|
|
def main [] {
|
|||
|
|
# Verify version
|
|||
|
|
let version = ($nu.version | get major)
|
|||
|
|
if $version < 109 {
|
|||
|
|
error make { msg: "Requires Nushell 0.109+" }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Function Definitions
|
|||
|
|
|
|||
|
|
### Basic Function Syntax
|
|||
|
|
```nushell
|
|||
|
|
# Private function (module scope)
|
|||
|
|
def helper_function [arg: string] -> string {
|
|||
|
|
$arg | str upcase
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Public exported function
|
|||
|
|
export def main_function [
|
|||
|
|
arg1: string # Required argument
|
|||
|
|
arg2?: int # Optional argument (note the ?)
|
|||
|
|
--flag: bool = false # Flag with default
|
|||
|
|
--value: string # Named parameter
|
|||
|
|
] -> list<string> {
|
|||
|
|
# Function body
|
|||
|
|
[$arg1, $arg2]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Main Entry Point
|
|||
|
|
```nushell
|
|||
|
|
def main [
|
|||
|
|
--verbose: bool = false
|
|||
|
|
...args: string # Rest parameters
|
|||
|
|
] {
|
|||
|
|
if $verbose {
|
|||
|
|
print "Verbose mode enabled"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for arg in $args {
|
|||
|
|
print $arg
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Type Annotations
|
|||
|
|
|
|||
|
|
### Supported Types
|
|||
|
|
```nushell
|
|||
|
|
# Scalar types
|
|||
|
|
def example [
|
|||
|
|
text: string
|
|||
|
|
number: int
|
|||
|
|
decimal: float
|
|||
|
|
flag: bool
|
|||
|
|
date: datetime
|
|||
|
|
duration: duration
|
|||
|
|
filesize: filesize
|
|||
|
|
] {}
|
|||
|
|
|
|||
|
|
# Collection types
|
|||
|
|
def collections [
|
|||
|
|
items: list<string> # Homogeneous list
|
|||
|
|
config: record # Record (struct)
|
|||
|
|
table: table # Table type
|
|||
|
|
any_list: list # List of any type
|
|||
|
|
] {}
|
|||
|
|
|
|||
|
|
# Optional and nullable
|
|||
|
|
def optional [
|
|||
|
|
maybe_text?: string # Optional parameter
|
|||
|
|
] {}
|
|||
|
|
|
|||
|
|
# Return type annotations (NEW SYNTAX in 0.109+)
|
|||
|
|
# Format: def name [params]: input_type -> output_type { body }
|
|||
|
|
def get_items []: nothing -> list<string> {
|
|||
|
|
["item1", "item2"]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def get_config []: nothing -> record {
|
|||
|
|
{name: "test", version: "1.0"}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Function that accepts pipeline input
|
|||
|
|
def process_items []: list<string> -> list<string> {
|
|||
|
|
$in | each {|item| $item | str upcase}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Error Handling
|
|||
|
|
|
|||
|
|
### Try-Catch Pattern
|
|||
|
|
```nushell
|
|||
|
|
# Basic try-catch
|
|||
|
|
def safe_operation [] {
|
|||
|
|
try {
|
|||
|
|
# Risky operation
|
|||
|
|
open file.txt
|
|||
|
|
} catch { |err|
|
|||
|
|
# Handle error
|
|||
|
|
print $"Error: ($err.msg)"
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Try with default value
|
|||
|
|
def get_value_or_default [] -> string {
|
|||
|
|
try {
|
|||
|
|
open config.toml | get key
|
|||
|
|
} catch {
|
|||
|
|
"default_value"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Error Creation
|
|||
|
|
```nushell
|
|||
|
|
def validate_input [value: int] {
|
|||
|
|
if $value < 0 {
|
|||
|
|
error make {
|
|||
|
|
msg: "Value must be non-negative"
|
|||
|
|
label: {
|
|||
|
|
text: "invalid value"
|
|||
|
|
span: (metadata $value).span
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Null Handling
|
|||
|
|
```nushell
|
|||
|
|
# Use null-coalescing with default operator
|
|||
|
|
def get_env_or_default [key: string, default: string] -> string {
|
|||
|
|
$env | get -i $key | default $default
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Safe navigation with optional chaining
|
|||
|
|
def get_nested_value [data: record] -> any {
|
|||
|
|
$data | get -i outer.inner.value | default null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Closures and Blocks
|
|||
|
|
|
|||
|
|
### Closure Syntax (Nushell 0.109+)
|
|||
|
|
```nushell
|
|||
|
|
# Simple closure
|
|||
|
|
let double = {|x| $x * 2}
|
|||
|
|
[1, 2, 3] | each $double
|
|||
|
|
|
|||
|
|
# Multi-line closure
|
|||
|
|
let process = {|item|
|
|||
|
|
let result = $item | str upcase
|
|||
|
|
$result + "!"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Closure with pipeline input ($in)
|
|||
|
|
let items = [1, 2, 3] | where {|it| $it > 1}
|
|||
|
|
|
|||
|
|
# IMPORTANT: Use {|it| ...} not { $in | ... } for newer versions
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Block vs Closure
|
|||
|
|
```nushell
|
|||
|
|
# Block (no parameters, uses $in)
|
|||
|
|
def process_with_block [] {
|
|||
|
|
[1, 2, 3] | each { $in * 2 } # Uses $in implicitly
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Closure (explicit parameters preferred)
|
|||
|
|
def process_with_closure [] {
|
|||
|
|
[1, 2, 3] | each {|x| $x * 2} # Explicit parameter
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Common Patterns
|
|||
|
|
```nushell
|
|||
|
|
# Filter with closure
|
|||
|
|
let adults = $people | where {|person| $person.age >= 18}
|
|||
|
|
|
|||
|
|
# Map transformation
|
|||
|
|
let names = $users | each {|user| $user.name}
|
|||
|
|
|
|||
|
|
# Reduce/fold
|
|||
|
|
let sum = [1, 2, 3, 4] | reduce {|acc, val| $acc + $val}
|
|||
|
|
|
|||
|
|
# Any/all predicates
|
|||
|
|
let has_errors = $items | any {|item| $item.status == "error"}
|
|||
|
|
let all_valid = $items | all {|item| $item.valid}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Pipeline and Data Flow
|
|||
|
|
|
|||
|
|
### Pipeline Best Practices
|
|||
|
|
```nushell
|
|||
|
|
# ✅ Good: Clear pipeline flow
|
|||
|
|
def process_data [] {
|
|||
|
|
open data.json
|
|||
|
|
| get items
|
|||
|
|
| where {|item| $item.active}
|
|||
|
|
| each {|item| {name: $item.name, count: $item.count}}
|
|||
|
|
| sort-by count
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ❌ Avoid: Unnecessary intermediate variables
|
|||
|
|
def process_data_bad [] {
|
|||
|
|
let data = open data.json
|
|||
|
|
let items = $data | get items
|
|||
|
|
let filtered = $items | where {|item| $item.active}
|
|||
|
|
# ... prefer pipeline instead
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pipeline Input Variable
|
|||
|
|
```nushell
|
|||
|
|
# $in represents pipeline input
|
|||
|
|
def uppercase_all [] {
|
|||
|
|
["hello", "world"] | each { $in | str upcase }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Prefer explicit parameters for clarity
|
|||
|
|
def uppercase_all_better [] {
|
|||
|
|
["hello", "world"] | each {|text| $text | str upcase}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## String Interpolation
|
|||
|
|
|
|||
|
|
### String Formatting
|
|||
|
|
```nushell
|
|||
|
|
# Basic interpolation
|
|||
|
|
let name = "World"
|
|||
|
|
print $"Hello, ($name)!"
|
|||
|
|
|
|||
|
|
# Expression interpolation
|
|||
|
|
let count = 5
|
|||
|
|
print $"You have ($count * 2) items"
|
|||
|
|
|
|||
|
|
# Nested interpolation
|
|||
|
|
let user = {name: "Alice", age: 30}
|
|||
|
|
print $"User: ($user.name), Age: ($user.age)"
|
|||
|
|
|
|||
|
|
# Multi-line strings
|
|||
|
|
let message = $"
|
|||
|
|
Welcome to syntaxis
|
|||
|
|
Version: (open Cargo.toml | get package.version)
|
|||
|
|
Date: (date now | format date "%Y-%m-%d")
|
|||
|
|
"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### ANSI Colors
|
|||
|
|
```nushell
|
|||
|
|
# Using ansi command
|
|||
|
|
print $"(ansi cyan)INFO:(ansi reset) Processing..."
|
|||
|
|
print $"(ansi red)ERROR:(ansi reset) Failed"
|
|||
|
|
print $"(ansi green)✓(ansi reset) Success"
|
|||
|
|
|
|||
|
|
# Common patterns
|
|||
|
|
def print_success [msg: string] {
|
|||
|
|
print $"(ansi green)✓(ansi reset) ($msg)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def print_error [msg: string] {
|
|||
|
|
print $"(ansi red)✗(ansi reset) ($msg)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def print_info [msg: string] {
|
|||
|
|
print $"(ansi cyan)ℹ(ansi reset) ($msg)"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Conditionals
|
|||
|
|
|
|||
|
|
### If-Else Statements
|
|||
|
|
```nushell
|
|||
|
|
# Basic if-else
|
|||
|
|
def check_value [x: int] -> string {
|
|||
|
|
if $x > 0 {
|
|||
|
|
"positive"
|
|||
|
|
} else if $x < 0 {
|
|||
|
|
"negative"
|
|||
|
|
} else {
|
|||
|
|
"zero"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Inline if (expression form)
|
|||
|
|
def abs [x: int] -> int {
|
|||
|
|
if $x >= 0 { $x } else { -$x }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Match Expressions
|
|||
|
|
```nushell
|
|||
|
|
# Pattern matching
|
|||
|
|
def process_status [status: string] -> string {
|
|||
|
|
match $status {
|
|||
|
|
"pending" => "⏳ Waiting"
|
|||
|
|
"running" => "▶️ In Progress"
|
|||
|
|
"completed" => "✅ Done"
|
|||
|
|
"failed" => "❌ Error"
|
|||
|
|
_ => "❓ Unknown"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Match with guards
|
|||
|
|
def categorize [num: int] -> string {
|
|||
|
|
match $num {
|
|||
|
|
$x if $x < 0 => "negative"
|
|||
|
|
0 => "zero"
|
|||
|
|
$x if $x < 10 => "small"
|
|||
|
|
$x if $x < 100 => "medium"
|
|||
|
|
_ => "large"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Loops and Iteration
|
|||
|
|
|
|||
|
|
### For Loops
|
|||
|
|
```nushell
|
|||
|
|
# Basic for loop
|
|||
|
|
def process_items [] {
|
|||
|
|
for item in [1, 2, 3] {
|
|||
|
|
print $"Processing: ($item)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# With index using enumerate
|
|||
|
|
def indexed_loop [] {
|
|||
|
|
for (idx, val) in ([1, 2, 3] | enumerate) {
|
|||
|
|
print $"Index ($idx): ($val)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### While Loops
|
|||
|
|
```nushell
|
|||
|
|
# While loop
|
|||
|
|
def countdown [mut n: int] {
|
|||
|
|
while $n > 0 {
|
|||
|
|
print $n
|
|||
|
|
$n = $n - 1
|
|||
|
|
}
|
|||
|
|
print "Done!"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Preferred: Functional Iteration
|
|||
|
|
```nushell
|
|||
|
|
# Use each instead of for when transforming
|
|||
|
|
def double_all [items: list<int>] -> list<int> {
|
|||
|
|
$items | each {|x| $x * 2}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Use where for filtering
|
|||
|
|
def get_active [items: list] -> list {
|
|||
|
|
$items | where {|item| $item.active}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Use reduce for aggregation
|
|||
|
|
def sum [numbers: list<int>] -> int {
|
|||
|
|
$numbers | reduce {|acc, val| $acc + $val}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Record and List Operations
|
|||
|
|
|
|||
|
|
### Record Operations
|
|||
|
|
```nushell
|
|||
|
|
# Create record
|
|||
|
|
let config = {
|
|||
|
|
name: "syntaxis"
|
|||
|
|
version: "0.1.0"
|
|||
|
|
features: ["cli", "tui", "api"]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Access fields
|
|||
|
|
let name = $config.name
|
|||
|
|
let version = $config | get version
|
|||
|
|
|
|||
|
|
# Safe access (returns null if missing)
|
|||
|
|
let missing = $config | get -i missing_key
|
|||
|
|
|
|||
|
|
# Update record
|
|||
|
|
let updated = $config | insert new_field "value"
|
|||
|
|
let modified = $config | update version "0.2.0"
|
|||
|
|
|
|||
|
|
# Remove field
|
|||
|
|
let smaller = $config | reject features
|
|||
|
|
|
|||
|
|
# Merge records
|
|||
|
|
let merged = $config | merge {author: "Akasha"}
|
|||
|
|
|
|||
|
|
# Check if key exists
|
|||
|
|
if "name" in $config {
|
|||
|
|
print "Has name field"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### List Operations
|
|||
|
|
```nushell
|
|||
|
|
# Create list
|
|||
|
|
let items = [1, 2, 3, 4, 5]
|
|||
|
|
|
|||
|
|
# Access elements
|
|||
|
|
let first = $items | first
|
|||
|
|
let last = $items | last
|
|||
|
|
let at_index = $items | get 2
|
|||
|
|
|
|||
|
|
# Add elements
|
|||
|
|
let appended = $items | append 6
|
|||
|
|
let prepended = $items | prepend 0
|
|||
|
|
|
|||
|
|
# Filter
|
|||
|
|
let evens = $items | where {|x| $x mod 2 == 0}
|
|||
|
|
|
|||
|
|
# Map
|
|||
|
|
let doubled = $items | each {|x| $x * 2}
|
|||
|
|
|
|||
|
|
# Reduce
|
|||
|
|
let sum = $items | reduce {|acc, val| $acc + $val}
|
|||
|
|
|
|||
|
|
# Sort
|
|||
|
|
let sorted = $items | sort
|
|||
|
|
let sorted_desc = $items | sort --reverse
|
|||
|
|
|
|||
|
|
# Unique
|
|||
|
|
let unique = [1, 2, 2, 3] | uniq
|
|||
|
|
|
|||
|
|
# Flatten
|
|||
|
|
let flat = [[1, 2], [3, 4]] | flatten
|
|||
|
|
|
|||
|
|
# Zip
|
|||
|
|
let pairs = [$items, $doubled] | zip
|
|||
|
|
|
|||
|
|
# Take/skip
|
|||
|
|
let first_three = $items | take 3
|
|||
|
|
let skip_two = $items | skip 2
|
|||
|
|
|
|||
|
|
# Check membership
|
|||
|
|
if 3 in $items {
|
|||
|
|
print "Found"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Length
|
|||
|
|
let count = $items | length
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## File System Operations
|
|||
|
|
|
|||
|
|
### Path Operations
|
|||
|
|
```nushell
|
|||
|
|
# Check if path exists
|
|||
|
|
def file_exists [path: string] -> bool {
|
|||
|
|
$path | path exists
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Path manipulation
|
|||
|
|
let expanded = "~/config" | path expand
|
|||
|
|
let joined = ["configs", "database.toml"] | path join
|
|||
|
|
let dirname = "/path/to/file.txt" | path dirname
|
|||
|
|
let basename = "/path/to/file.txt" | path basename
|
|||
|
|
let extension = "file.txt" | path extension
|
|||
|
|
|
|||
|
|
# Path type checking
|
|||
|
|
if ($path | path exists) and ($path | path type) == "dir" {
|
|||
|
|
print "Is a directory"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### File Operations
|
|||
|
|
```nushell
|
|||
|
|
# Read file
|
|||
|
|
def read_config [path: string] -> record {
|
|||
|
|
if not ($path | path exists) {
|
|||
|
|
return {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
open $path
|
|||
|
|
} catch {
|
|||
|
|
print $"Warning: Could not read ($path)"
|
|||
|
|
{}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Write file
|
|||
|
|
def save_config [config: record, path: string] {
|
|||
|
|
try {
|
|||
|
|
$config | to toml | save --force $path
|
|||
|
|
print $"✓ Saved to ($path)"
|
|||
|
|
} catch { |err|
|
|||
|
|
print $"✗ Failed to save: ($err.msg)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Directory operations
|
|||
|
|
def ensure_dir [path: string] {
|
|||
|
|
if not ($path | path exists) {
|
|||
|
|
mkdir $path
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# List files with glob
|
|||
|
|
def find_nu_scripts [] -> list<string> {
|
|||
|
|
glob **/*.nu
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## External Commands
|
|||
|
|
|
|||
|
|
### Running External Commands
|
|||
|
|
```nushell
|
|||
|
|
# Use ^ prefix for external commands
|
|||
|
|
def run_cargo_check [] {
|
|||
|
|
^cargo check --workspace
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Capture output
|
|||
|
|
def get_git_branch [] -> string {
|
|||
|
|
^git branch --show-current | str trim
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Check command existence
|
|||
|
|
def has_command [cmd: string] -> bool {
|
|||
|
|
which $cmd | length > 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Conditional command execution
|
|||
|
|
def maybe_run [cmd: string] {
|
|||
|
|
if (has_command $cmd) {
|
|||
|
|
^$cmd --version
|
|||
|
|
} else {
|
|||
|
|
print $"Command not found: ($cmd)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Suppress errors with try
|
|||
|
|
def silent_command [] {
|
|||
|
|
try {
|
|||
|
|
^some-command 2>/dev/null
|
|||
|
|
} catch {
|
|||
|
|
null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Shell Redirection
|
|||
|
|
```nushell
|
|||
|
|
# Redirect stderr
|
|||
|
|
^command 2>/dev/null
|
|||
|
|
|
|||
|
|
# Redirect both stdout and stderr
|
|||
|
|
^command 2>&1
|
|||
|
|
|
|||
|
|
# Pipe to file
|
|||
|
|
^command | save output.txt
|
|||
|
|
|
|||
|
|
# Append to file
|
|||
|
|
^command | save --append output.txt
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Module System
|
|||
|
|
|
|||
|
|
### Module Structure
|
|||
|
|
```nushell
|
|||
|
|
# my-module.nu
|
|||
|
|
export def public_function [] {
|
|||
|
|
print "Public"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def private_function [] {
|
|||
|
|
print "Private"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export def another_public [] {
|
|||
|
|
private_function # Can call private functions
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Importing Modules
|
|||
|
|
```nushell
|
|||
|
|
# Import all exports
|
|||
|
|
use my-module.nu
|
|||
|
|
|
|||
|
|
# Import specific functions
|
|||
|
|
use my-module.nu [public_function, another_public]
|
|||
|
|
|
|||
|
|
# Import with alias
|
|||
|
|
use my-module.nu [public_function as pub_fn]
|
|||
|
|
|
|||
|
|
# Relative imports
|
|||
|
|
use ../common/utils.nu [helper]
|
|||
|
|
use ./local-module.nu *
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Standard Library
|
|||
|
|
```nushell
|
|||
|
|
# Use standard library modules
|
|||
|
|
use std log
|
|||
|
|
use std assert
|
|||
|
|
|
|||
|
|
def example [] {
|
|||
|
|
log info "Starting process"
|
|||
|
|
log debug "Debug information"
|
|||
|
|
|
|||
|
|
assert equal 1 1 # Unit test assertion
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Testing
|
|||
|
|
|
|||
|
|
### Test Functions
|
|||
|
|
```nushell
|
|||
|
|
# Test annotation
|
|||
|
|
#[test]
|
|||
|
|
def test_addition [] {
|
|||
|
|
assert equal (1 + 1) 2
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
def test_string_operations [] {
|
|||
|
|
let result = "hello" | str upcase
|
|||
|
|
assert equal $result "HELLO"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Test with error handling
|
|||
|
|
#[test]
|
|||
|
|
def test_error_case [] {
|
|||
|
|
assert error { error make { msg: "test error" } }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Integration Tests
|
|||
|
|
```nushell
|
|||
|
|
# test-integration.nu
|
|||
|
|
use std assert
|
|||
|
|
|
|||
|
|
def main [] {
|
|||
|
|
print "Running integration tests..."
|
|||
|
|
|
|||
|
|
test_config_loading
|
|||
|
|
test_file_operations
|
|||
|
|
|
|||
|
|
print "All tests passed!"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def test_config_loading [] {
|
|||
|
|
let config = load_config "test-config.toml"
|
|||
|
|
assert ($config.name == "test")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
def test_file_operations [] {
|
|||
|
|
let temp_file = "temp-test.txt"
|
|||
|
|
"test content" | save $temp_file
|
|||
|
|
assert ($temp_file | path exists)
|
|||
|
|
rm $temp_file
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Performance Considerations
|
|||
|
|
|
|||
|
|
### Efficient Data Processing
|
|||
|
|
```nushell
|
|||
|
|
# ✅ Good: Use pipeline for streaming
|
|||
|
|
def process_large_file [] {
|
|||
|
|
open --raw large.txt
|
|||
|
|
| lines
|
|||
|
|
| where {|line| $line | str contains "ERROR"}
|
|||
|
|
| each {|line| parse_error_line $line}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ❌ Avoid: Loading everything into memory
|
|||
|
|
def process_large_file_bad [] {
|
|||
|
|
let all_lines = open large.txt | lines
|
|||
|
|
let errors = $all_lines | where {|line| $line | str contains "ERROR"}
|
|||
|
|
# Processes entire file at once
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Lazy Evaluation
|
|||
|
|
```nushell
|
|||
|
|
# Take advantage of lazy evaluation
|
|||
|
|
def find_first_match [pattern: string] {
|
|||
|
|
glob **/*.txt
|
|||
|
|
| each { open }
|
|||
|
|
| where {|content| $content | str contains $pattern}
|
|||
|
|
| first # Stops after first match
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Avoid Unnecessary Conversions
|
|||
|
|
```nushell
|
|||
|
|
# ✅ Good: Work with structured data
|
|||
|
|
def get_names [] {
|
|||
|
|
open users.json
|
|||
|
|
| get users
|
|||
|
|
| each {|user| $user.name}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ❌ Avoid: Converting to strings unnecessarily
|
|||
|
|
def get_names_bad [] {
|
|||
|
|
open users.json
|
|||
|
|
| to text # Unnecessary conversion
|
|||
|
|
| from json
|
|||
|
|
| get users
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Common Pitfalls
|
|||
|
|
|
|||
|
|
### Pitfall 1: Closure vs Block Confusion
|
|||
|
|
```nushell
|
|||
|
|
# ❌ Wrong: Using $in in closure parameter context
|
|||
|
|
let bad = [1, 2, 3] | where { $in > 1 } # May not work in newer versions
|
|||
|
|
|
|||
|
|
# ✅ Correct: Use explicit parameter
|
|||
|
|
let good = [1, 2, 3] | where {|x| $x > 1}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pitfall 2: Mutable Variable Scope
|
|||
|
|
```nushell
|
|||
|
|
# ❌ Wrong: Trying to mutate outer scope
|
|||
|
|
def increment_bad [] {
|
|||
|
|
let mut counter = 0
|
|||
|
|
[1, 2, 3] | each {|x|
|
|||
|
|
$counter = $counter + 1 # Error: can't mutate outer scope
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ✅ Correct: Use reduce or return values
|
|||
|
|
def increment_good [] -> int {
|
|||
|
|
[1, 2, 3] | reduce {|acc, val| $acc + 1} --fold 0
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pitfall 3: Missing Error Handling
|
|||
|
|
```nushell
|
|||
|
|
# ❌ Risky: No error handling
|
|||
|
|
def load_config [] {
|
|||
|
|
open config.toml | get database.url # Crashes if missing
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ✅ Safe: Handle errors gracefully
|
|||
|
|
def load_config_safe [] -> string {
|
|||
|
|
try {
|
|||
|
|
open config.toml | get database.url
|
|||
|
|
} catch {
|
|||
|
|
print "Warning: Using default database URL"
|
|||
|
|
"sqlite::memory:"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pitfall 4: Type Mismatches
|
|||
|
|
```nushell
|
|||
|
|
# ❌ Wrong: Implicit type assumption
|
|||
|
|
def add [a, b] { # No type annotations
|
|||
|
|
$a + $b # May fail if wrong types
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ✅ Correct: Explicit types
|
|||
|
|
def add [a: int, b: int] -> int {
|
|||
|
|
$a + $b
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pitfall 5: Path Handling
|
|||
|
|
```nushell
|
|||
|
|
# ❌ Wrong: Hardcoded paths
|
|||
|
|
def get_config [] {
|
|||
|
|
open /Users/username/config.toml # Not portable
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ✅ Correct: Use environment variables and path expansion
|
|||
|
|
def get_config [] -> record {
|
|||
|
|
let config_path = $"($env.HOME)/.config/syntaxis/config.toml"
|
|||
|
|
if ($config_path | path exists) {
|
|||
|
|
open $config_path
|
|||
|
|
} else {
|
|||
|
|
{}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Pitfall 6: Ignoring Null Values
|
|||
|
|
```nushell
|
|||
|
|
# ❌ Wrong: Assumes value exists
|
|||
|
|
def get_value [data: record] {
|
|||
|
|
$data.key.nested.value # Crashes if any key missing
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# ✅ Correct: Safe navigation
|
|||
|
|
def get_value_safe [data: record] -> any {
|
|||
|
|
$data | get -i key.nested.value | default null
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Migration Checklist for Existing Scripts
|
|||
|
|
|
|||
|
|
When updating scripts to Nushell 0.109+:
|
|||
|
|
|
|||
|
|
- [ ] **CRITICAL:** Update return type syntax from `] -> type {` to `]: nothing -> type {`
|
|||
|
|
- [ ] Update closures to use explicit parameters `{|x| ...}` instead of `{ $in ... }`
|
|||
|
|
- [ ] Add type annotations to function parameters and return types
|
|||
|
|
- [ ] Replace deprecated commands (check Nushell changelog)
|
|||
|
|
- [ ] Add proper error handling with try-catch
|
|||
|
|
- [ ] Use `export def` for public functions
|
|||
|
|
- [ ] Add documentation comments
|
|||
|
|
- [ ] Update file operations to use proper path checking
|
|||
|
|
- [ ] Replace string-based path handling with path commands
|
|||
|
|
- [ ] Add tests for critical functions
|
|||
|
|
- [ ] Verify null handling with `-i` flag and `default`
|
|||
|
|
- [ ] Check external command calls use `^` prefix
|
|||
|
|
- [ ] Ensure ANSI color usage is consistent
|
|||
|
|
- [ ] Change `let mut` to `mut` for mutable variables
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## References
|
|||
|
|
|
|||
|
|
- [Nushell Official Documentation](https://www.nushell.sh/book/)
|
|||
|
|
- [Nushell Language Guide](https://www.nushell.sh/lang-guide/)
|
|||
|
|
- [Nushell Cookbook](https://www.nushell.sh/cookbook/)
|
|||
|
|
- [Nushell Release Notes](https://github.com/nushell/nushell/releases)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**Last Updated**: 2025-12-01
|
|||
|
|
**Minimum Version**: Nushell 0.109+
|
|||
|
|
**Maintained by**: syntaxis Development Team
|