🪵 fs-tree-spec (tree_adapter)
Translate between ASCII “tree” text, JSON/YAML tree specs, and real directory structures.
This tiny Python utility helps you describe, version, and reproduce directory layouts in a portable, human-readable way.
✨ Features
- Parse ASCII "tree" text (like
treecommand output) → nested Python structure - Scan existing directories (
--from-fs) → generate tree specs - Serialize / deserialize JSON and YAML tree specs
- Render a Unicode tree from structured data
- Validate naming rules (strict enforcement, no invalid characters)
- Compute a dry-run plan (
--plan) of what would be created - Apply (
--apply) to actually build the filesystem - Smart defaults: ignores
__pycache__,.git,node_modules, etc. - Optional YAML support via
pip install fs-tree-spec[yaml]
🧩 Installation
pip install fs-tree-spec
# or with YAML support:
pip install fs-tree-spec[yaml]
After installation, the CLI command fs-tree-spec becomes available.
🧭 Usage (CLI)
Basic
# Plan creation from an ASCII spec
fs-tree-spec --from-ascii second_brain_tree.txt --root ./second-brain --plan
# Apply (actually create directories/files)
fs-tree-spec --from-ascii second_brain_tree.txt --root ./second-brain --apply
# Print as normalized ASCII and JSON
fs-tree-spec --from-ascii second_brain_tree.txt --print-tree --print-json
From JSON or YAML
fs-tree-spec --from-json my_tree.json --plan
fs-tree-spec --from-yaml my_tree.yaml --apply
From Existing Directory
Generate a tree spec by scanning an existing directory structure:
# Generate ASCII tree from current directory
fs-tree-spec --from-fs . --print-tree
# Generate JSON spec from a project
fs-tree-spec --from-fs ./my-project --print-json
# Generate YAML spec
fs-tree-spec --from-fs ./src --print-yaml
# Exclude root directory name from output
fs-tree-spec --from-fs ./my-project --print-tree --no-root-label
Default ignore patterns: __pycache__, *.pyc, .git, .venv, node_modules, .pytest_cache, .mypy_cache, .ruff_cache, *.egg-info, .DS_Store
# Custom ignore patterns
fs-tree-spec --from-fs . --print-tree --ignore "*.log" "tmp/" "build/"
# Disable default ignores (include everything)
fs-tree-spec --from-fs . --print-tree --ignore
📜 Example Input
ASCII tree text
second-brain/
├─ .env # API keys + config (not committed)
├─ pyproject.toml
├─ README.md
├─ data/
│ ├─ raw/
│ │ └─ apple_notes_export/
│ ├─ attachments/
│ │ └─ ...
│ ├─ processed/
│ │ ├─ notes.jsonl
│ │ ├─ attachments.jsonl
│ │ └─ urls.jsonl
└─ notebooks/
└─ exploration.ipynb
Equivalent JSON
{
"second-brain": {
".env": null,
"pyproject.toml": null,
"README.md": null,
"data": {
"raw": { "apple_notes_export": {} },
"attachments": {},
"processed": {
"notes.jsonl": null,
"attachments.jsonl": null,
"urls.jsonl": null
}
},
"notebooks": {
"exploration.ipynb": null
}
}
}
Equivalent YAML
second-brain:
.env: null
pyproject.toml: null
README.md: null
data:
raw:
apple_notes_export: {}
attachments: {}
processed:
notes.jsonl: null
attachments.jsonl: null
urls.jsonl: null
notebooks:
exploration.ipynb: null
🧠 Naming Rules (Enforced)
- Names are single path segments (no
/or\). - Names must be non-empty after trimming.
- Names cannot be
.,..,..., or…. - Names cannot contain tree/box characters: `├└│─|+```
- Inline comments begin with
" #"and are ignored. "..."and"…"lines are treated as placeholders only.- Conflicts between file vs. directory at same level raise an error.
🧪 Python API Example
Create from spec
from tree_adapter import parse_ascii_tree, plan_fs_from_tree, create_fs_from_tree
ascii_spec = Path("second_brain_tree.txt").read_text()
tree = parse_ascii_tree(ascii_spec)
# Inspect
print(plan_fs_from_tree(tree, "./second-brain"))
# Create directories and files
create_fs_from_tree(tree, "./second-brain", create_files=True)
Generate from existing directory
from tree_adapter import from_fs, to_ascii_tree, to_json, to_yaml
# Scan a directory
tree = from_fs("./my-project", include_root_label=True)
# Convert to different formats
print(to_ascii_tree(tree))
print(to_json(tree))
print(to_yaml(tree)) # requires PyYAML
# Custom ignore patterns
tree = from_fs("./src", ignore_patterns=["*.log", "tmp/"])
# Include everything (no filtering)
tree = from_fs("./src", ignore_patterns=[])
🛠️ Development
# Clone and set up development environment
git clone https://github.com/swoodeng/fs-tree-spec.git
cd fs-tree-spec
# Install dependencies with uv
uv sync --all-extras --dev
# Run tests
uv run pytest
# Run linters
uv run ruff check .
uv run black --check .
uv run mypy tree_adapter.py
Run locally:
uv run fs-tree-spec --from-ascii second_brain_tree.txt --plan
📦 Packaging
This project uses PEP 621 metadata and can be built with:
python -m build
⚖️ License
MIT License © 2025 Stephen Wood
💡 Inspiration
treecommand output formatting- JSON/YAML for reproducible data models
- Cross-tool “infrastructure as text” patterns
🪴 Example Workflows
Spec → Filesystem
- Write a tree spec in a
README.mdfor documentation. - Extract the snippet and feed it to
fs-tree-spec --from-ascii. - Plan or apply to generate the directory structure for new projects.
- Optionally export the same spec to JSON or YAML for automation.
Filesystem → Spec
- Scan an existing project with
fs-tree-spec --from-fs ./project --print-tree. - Document by copying the output into your README.
- Export to JSON/YAML for programmatic use or version control.
- Recreate the structure elsewhere with
--apply.
"Describe once. Recreate anywhere."
Metadata
Release files for fs-tree-spec 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fs_tree_spec-0.2.0.tar.gz | 11.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fs_tree_spec-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 22.7 kB
Release files / fs_tree_spec-0.2.0.tar.gz
| Download URL | fs_tree_spec-0.2.0.tar.gz |
|---|---|
| Size | 11.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5c5cf56f938ee3c53665ff049920f0e183846268f9cea415b2f7a5698911d9af
|
|
BLAKE2b-256 checksum How to use checksums |
fb518e1e1e3bebed5ca14fe9f854d473b71b2cc5b1821a4708de207b0036f9c0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|
Release files / fs_tree_spec-0.2.0-py3-none-any.whl
| Download URL | fs_tree_spec-0.2.0-py3-none-any.whl |
|---|---|
| Size | 11.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1e34af3178bbf021207768bfb456df6e14d2cbfaeb4478eea5f0aadf1502d63d
|
|
BLAKE2b-256 checksum How to use checksums |
ef8b2edb6a17860477e7129591acf92a8ff45d6ed9cdc1c429b66d753c77da38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.0
|