# 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
