Merge _configs/ into config/ for single configuration directory. Update all path references. Changes: - Move _configs/* to config/ - Update .gitignore for new patterns - No code references to _configs/ found Impact: -1 root directory (layout_conventions.md compliance)
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
|