## Path Manipulation Functions
Path manipulation functions: basename, dirname, join_path, normalize_path, and path checks.
Functions for manipulating file paths and checking filesystem metadata. These functions do not read file contents and work without security restrictions.
#### `basename(path)` / `| basename`
Extract the filename from a path.
**Arguments:**
- `path` (required) - File path
**Returns:** Filename (last component of the path)
**Function syntax:**
```jinja
{{ basename(path="/path/to/file.txt") }}
{# Output: file.txt #}
```
**Filter syntax:**
```jinja
{# Use with glob results #}
{% for file in glob(pattern="src/**/*.rs") %}
```
#### `dirname(path)` / `| dirname`
Extract the directory portion from a path.
**Arguments:**
- `path` (required) - File path
**Returns:** Directory path (all components except the last)
**Function syntax:**
```jinja
{{ dirname(path="/path/to/file.txt") }}
{# Output: /path/to #}
```
**Filter syntax:**
```jinja
{# Get parent directory #}
{% set file_path = "config/app/settings.json" %}
Config directory: {{ file_path | dirname }}
{# Output: config/app #}
```
#### `file_extension(path)` / `| file_extension`
Extract the file extension from a path.
**Arguments:**
- `path` (required) - File path
**Returns:** File extension without the dot (empty string if no extension)
**Function syntax:**
```jinja
{{ file_extension(path="document.pdf") }}
{# Output: pdf #}
{{ file_extension(path="/path/to/file.tar.gz") }}
{# Output: gz #}
```
**Filter syntax:**
```jinja
{# Chain with basename #}
{# Group files by extension #}
{% for file in glob(pattern="docs/*") %}
{% endif %}
{% endfor %}
```
#### `join_path(parts)` / `| join_path`
Join multiple path components into a single path.
**Arguments:**
- `parts` (required) - Array of path components
**Returns:** Joined path string
**Function syntax:**
```jinja
{{ join_path(parts=["path", "to", "file.txt"]) }}
{# Output: path/to/file.txt #}
{{ join_path(parts=["/home", "user", "documents"]) }}
{# Output: /home/user/documents #}
```
**Filter syntax:**
```jinja
{# Build dynamic paths #}
{% set path_parts = ["config", "production", "settings.json"] %}
{{ path_parts | join_path }}
{# Output: config/production/settings.json #}
```
#### `normalize_path(path)` / `| normalize_path`
Normalize a path by resolving `.` (current directory) and `..` (parent directory) components.
**Arguments:**
- `path` (required) - Path to normalize
**Returns:** Normalized path string
**Function syntax:**
```jinja
{{ normalize_path(path="./foo/bar") }}
{# Output: foo/bar #}
{{ normalize_path(path="a/b/c/../../d") }}
{# Output: a/d #}
```
**Filter syntax:**
```jinja
{# Clean up path components #}
{% set messy_path = "./config/../data/./files.txt" %}
{{ messy_path | normalize_path }}
{# Output: data/files.txt #}
```
#### `is_file(path)` / `{% if path is file %}`
Check if a path exists and is a file. Supports both function syntax and "is" test syntax.
**Function Syntax Arguments:**
- `path` (required) - Path to check
**Is-Test Syntax:**
- The value must be a string representing a file path
**Returns:** Boolean (true if path exists and is a file)
**Examples:**
```jinja
{# Function syntax #}
{% if is_file(path="config.txt") %}
Config file found!
{% endif %}
{# Is-test syntax (preferred for readability) #}
{% if "config.txt" is file %}
Config file found!
{% endif %}
{# With variables #}
{% set config_path = "config.json" %}
{% if config_path is file %}
{{ read_file(path=config_path) }}
{% endif %}
```
#### `is_dir(path)` / `{% if path is dir %}`
Check if a path exists and is a directory. Supports both function syntax and "is" test syntax.
**Function Syntax Arguments:**
- `path` (required) - Path to check
**Is-Test Syntax:**
- The value must be a string representing a directory path
**Returns:** Boolean (true if path exists and is a directory)
**Examples:**
```jinja
{# Function syntax #}
{% if is_dir(path="src") %}
Source directory exists
{% endif %}
{# Is-test syntax (preferred for readability) #}
{% if "src" is dir %}
Source directory exists
{% endif %}
{# With variables #}
{% set test_dir = "tests" %}
{% if test_dir is dir %}
{% set test_files = glob(pattern="tests/**/*.rs") %}
Found {{ test_files | length }} test files
{% endif %}
```
#### `is_symlink(path)` / `{% if path is symlink %}`
Check if a path is a symbolic link. Supports both function syntax and "is" test syntax.
**Function Syntax Arguments:**
- `path` (required) - Path to check
**Is-Test Syntax:**
- The value must be a string representing a path
**Returns:** Boolean (true if path is a symlink)
**Examples:**
```jinja
{# Function syntax #}
{% if is_symlink(path="current") %}
'current' is a symbolic link
{% endif %}
{# Is-test syntax (preferred for readability) #}
{% if "current" is symlink %}
'current' is a symbolic link
{% endif %}
{# With variables #}
{% set link_path = "latest" %}
{% if link_path is symlink %}
Following symlink...
{% endif %}
```
#### `read_lines(path, max_lines)`
Read lines from a file with flexible line selection.
**Arguments:**
- `path` (required) - Path to file
- `max_lines` (optional) - Number of lines to read (default: 10, max abs value: 10000)
- **Positive number**: Read first N lines
- **Negative number**: Read last N lines
- **Zero**: Read entire file
**Returns:** Array of strings (lines without newline characters)
**Security:** Requires `--trust` flag for absolute paths or parent directory traversal
**Examples:**
```jinja
{# Read first 5 lines #}
{% set first_lines = read_lines(path="log.txt", max_lines=5) %}
Recent log entries:
{% for line in first_lines %}
{{ loop.index }}: {{ line }}
{% endfor %}
{# Read last 5 lines #}
{% set last_lines = read_lines(path="log.txt", max_lines=-5) %}
Latest log entries:
{% for line in last_lines %}
{{ line }}
{% endfor %}
{# Read entire file #}
{% set all_lines = read_lines(path="config.txt", max_lines=0) %}
Total lines: {{ all_lines | length }}
{# Preview file content #}
{% if is_file(path="README.md") %}
README preview (first 3 lines):
{% for line in read_lines(path="README.md", max_lines=3) %}
{{ line }}
{% endfor %}
{% endif %}
{# Process log file tail #}
{% set log_tail = read_lines(path="app.log", max_lines=-10) %}
{% for line in log_tail %}
{% if "ERROR" in line %}
⚠️ {{ line }}
{% endif %}
{% endfor %}
```
**Practical Example - Project Structure:**
```jinja
# Project Analysis
## Directory Structure
{% for item in ["src", "tests", "examples", "docs"] %}
{% if is_dir(path=item) %}
✓ {{ item }}/ ({{ glob(pattern=item ~ "/**/*") | length }} files)
{% else %}
✗ {{ item }}/ (missing)
{% endif %}
{% endfor %}
## Configuration Files
{% for config_file in ["Cargo.toml", "package.json", ".gitignore"] %}
{% if is_file(path=config_file) %}
✓ {{ config_file }}
{% set lines = read_lines(path=config_file, max_lines=3) %}
Preview: {{ lines[0] | truncate(length=50) }}
{% else %}
✗ {{ config_file }} (not found)
{% endif %}
{% endfor %}
## Source Files by Type
{% set all_files = glob(pattern="src/**/*") %}
{% for file in all_files %}
{% set ext = file_extension(path=file) %}
{% if ext == "rs" %}
- Rust: {{ basename(path=file) }}
{% elif ext == "md" %}
- Markdown: {{ basename(path=file) }}
{% endif %}
{% endfor %}
```