# dotpath

The master addressing engine for the dotsuite ecosystem. A fully extensible, registration-based path engine where each capability is encapsulated in a self-contained segment class.

## Overview

`dotpath` is the Turing-complete addressing engine that underpins the entire Depth pillar. While simpler tools like `dotget` and `dotstar` handle common cases, `dotpath` provides the full power of advanced path expressions including filters, slices, regex matching, and recursive descent.

## Installation

```bash
pip install dotsuite
```

## Quick Start

```python
from depth.dotpath import find_all, find_first

data = {
    "users": [
        {"name": "Alice", "age": 30, "role": "admin"},
        {"name": "Bob", "age": 25, "role": "user"},
        {"name": "Charlie", "age": 35, "role": "admin"}
    ]
}

# Find all user names
names = find_all("users.*.name", data)
# ["Alice", "Bob", "Charlie"]

# Find first admin
admin = find_first("users[?(@['role'] == 'admin')]", data)
# {"name": "Alice", "age": 30, "role": "admin"}

# Slice operations
first_two = find_all("users[:2].name", data)
# ["Alice", "Bob"]
```

## Path Segment Types

### Key
Access dictionary keys using dot notation or bracket notation.

```python
# Dot notation
find_first("user.name", {"user": {"name": "Alice"}})  # "Alice"

# Bracket notation for special characters
find_first("['user-info']['full-name']", {"user-info": {"full-name": "Alice"}})
```

### Index
Access list elements by position (supports negative indices).

```python
find_first("items[0]", {"items": ["a", "b", "c"]})   # "a"
find_first("items[-1]", {"items": ["a", "b", "c"]})  # "c"
```

### Slice
Extract ranges from lists using Python slice syntax.

```python
find_all("items[1:3]", {"items": [0, 1, 2, 3, 4]})    # [1, 2]
find_all("items[::2]", {"items": [0, 1, 2, 3, 4]})    # [0, 2, 4]
find_all("items[-3:]", {"items": [0, 1, 2, 3, 4]})    # [2, 3, 4]
```

### Wildcard (`*`)
Match all keys in a dict or all elements in a list.

```python
find_all("users.*.name", data)  # All user names
find_all("matrix.*.*", {"matrix": [[1,2], [3,4]]})  # [1, 2, 3, 4]
```

### Descendant (`**`)
Recursively descend through all nested structures.

```python
data = {"a": {"b": {"c": 1}}, "d": [{"e": 2}]}
find_all("**.c", data)  # Would need filtering, but ** yields all descendants
```

### Filter (`[?(...)]`)
Filter list elements using predicate expressions.

```python
# Filter by field value
find_all("users[?(@['age'] > 25)].name", data)  # ["Alice", "Charlie"]

# Filter by equality
find_all("users[?(@['role'] == 'admin')].name", data)  # ["Alice", "Charlie"]
```

### RegexKey (`~r/.../`)
Match dictionary keys using regular expressions.

```python
data = {"user_1": "Alice", "user_2": "Bob", "admin": "Charlie"}
find_all("~r/user_\\d+/", data)  # ["Alice", "Bob"]

# Case-insensitive
find_all("~r/USER_\\d+/i", data)  # ["Alice", "Bob"]
```

## Advanced Usage

### Custom Path Engine

Create an engine with limited capabilities:

```python
from depth.dotpath import PathEngine, Key, Index

# Minimal engine with only key and index access
engine = PathEngine()
engine.register(Key)
engine.register(Index)

# Can parse simple paths
ast = engine.parse("users[0].name")

# Cannot parse wildcards (not registered)
# engine.parse("users.*")  # Raises ValueError
```

### AST Serialization

Path expressions can be serialized to JSON for storage or transmission:

```python
from depth.dotpath import create_default_engine

engine = create_default_engine()

# Parse to AST
ast = engine.parse("users[0].name")

# Serialize
json_str = engine.ast_to_json(ast)
# '[{"$type": "key", "name": "users"}, {"$type": "index", "value": 0}, ...]'

# Deserialize
restored_ast = engine.json_to_ast(json_str)
```

### Creating Custom Segments

Extend the path language by implementing the `PathSegment` interface:

```python
from depth.dotpath import PathSegment
from dataclasses import dataclass

@dataclass(frozen=True)
class MySegment(PathSegment):
    _type_name = "my_segment"
    # ... implement parse(), evaluate(), to_dict(), from_dict()
```

## API Reference

### Functions

| Function | Description |
|----------|-------------|
| `find_all(path, doc)` | Find all values matching the path |
| `find_first(path, doc)` | Find first value matching the path (or None) |
| `create_default_engine()` | Create engine with all standard segments |

### Classes

| Class | Description |
|-------|-------------|
| `PathEngine` | Registration-based path parsing and evaluation engine |
| `PathSegment` | Abstract base class for path segment implementations |
| `Key` | Dictionary key access |
| `Index` | List index access |
| `Slice` | List slice operations |
| `Wildcard` | Match all children |
| `Descendant` | Recursive descent |
| `Filter` | Predicate-based filtering |
| `RegexKey` | Regex-based key matching |

## Command Line

The `dotpath` CLI supports three modes: evaluate a path expression against data, serialize a path to its JSON AST, and evaluate from a pre-serialized JSON AST.

```bash
# Evaluate a path expression against a JSON file
dotpath "users.*.name" data.json

# Read data from stdin (default)
echo '{"items": [1, 2, 3]}' | dotpath "items[1:]"

# Return only the first match
dotpath -f "users[?(@['role'] == 'admin')]" data.json

# Serialize a path expression to its JSON AST (no data needed)
dotpath --to-json "users[0].name"
# [{"$type": "key", "name": "users"}, {"$type": "index", "value": 0}, ...]

# Evaluate from a pre-serialized JSON AST file
dotpath --from-json path_ast.json data.json

# YAML input (inferred from file extension)
dotpath "servers.*.host" config.yaml

# Force input format
dotpath "items[::2]" data.txt --in yaml
```

### Options

| Flag | Description |
|------|-------------|
| `path` | The dotpath expression string, or with `--from-json`, the path to a JSON AST file. Required. |
| `data_file` | Path to the data file (JSON or YAML). Defaults to stdin (`-`). Not needed with `--to-json`. |
| `-f`, `--first` | Return only the first matching result instead of all results. |
| `--to-json` | Parse the path string and print its serialized JSON AST. Does not evaluate against data. Mutually exclusive with `--from-json`. |
| `--from-json` | Evaluate a path from a JSON AST file (provided as the `path` argument) instead of parsing a path string. Mutually exclusive with `--to-json`. |
| `--in`, `--input-format` | Format of the input data: `json` or `yaml`. Inferred from file extension if not provided. Defaults to `json` for stdin. |

Results are written to stdout as pretty-printed JSON.

## See Also

- [dotget](dotget.md) - Simple exact path access
- [dotstar](dotstar.md) - Wildcard pattern matching
- [dotselect](dotselect.md) - User-friendly interface to dotpath
