Turn an ASCII directory tree into real directories and files. Works on Windows, macOS and Linux.
Write (or paste) a tree like this into a text file:
my-app/ # project root
├── Cargo.toml
├── src/
│ └── main.rs
└── README.md
Run one command:
structurex -i tree.txt -t ./outAnd get the structure on disk, with minimal starter code in every known file type, without ever overwriting anything that already exists.
The command is structurex (lowercase, like most command-line tools).
- Features
- Download: pick your operating system
- Build from source
- Quick start
- Command-line options
- Input format
- Starter content
- How it works
- Safety guarantees
- Exit codes
- Project layout
- Development
- Publishing a release
- License
- Reads
treecommand output, hand-drawn Unicode trees (├──,└──,│), ASCII trees (|--,`--,+--,\--) and plain indented lists. - Ignores blank lines and
#comments. - Detects directories (trailing
/or nested children) and files. - Prints a clear
[DIR]/[FILE]plan before doing anything. --dry-runto preview,[y/N]confirmation by default,--yesfor scripts.- Creates missing parent directories automatically.
- Never overwrites existing files.
- Fills new files with starter code for
Cargo.toml,CMakeLists.txt,README.md,.rs,.py,.cpp,.hppand more. - Rejects absolute paths,
..traversal and symlink escapes. - Single self-contained program per operating system, with nothing else to install.
- Understands Windows' own
tree /Foutput and UTF-16 files, and checks names against Windows' naming rules when running on Windows.
Each operating system has its own version of StructureX, and they are not
interchangeable. A Windows .exe won't run on Linux or macOS, and the reverse
is also true. To keep them apart, every download has the operating system in
its file name, and the repository has one folder per operating system with a
step-by-step guide:
| Your computer | Download from Releases | Step-by-step guide |
|---|---|---|
| Windows 10 / 11 (64-bit) | structurex-windows-x86_64.zip |
windows/README.md |
| macOS with Apple Silicon (M1, M2, M3, M4…) | structurex-macos-arm64.tar.gz |
macOS/README.md |
| macOS with an Intel processor | structurex-macos-x86_64.tar.gz |
macOS/README.md |
| Linux (64-bit Intel/AMD, any distribution) | structurex-linux-x86_64.tar.gz |
linux/README.md |
Each download has a matching .sha256 file so you can check it wasn't
corrupted. The guides show how.
Don't know which Mac you have? Apple menu → About This Mac. "Chip: Apple M…" means Apple Silicon, and "Processor: Intel…" means Intel.
linux/ README.md (install guide) + build.sh → builds into linux/bin/
macOS/ README.md (install guide) + build.sh → builds into macOS/bin/
windows/ README.md (install guide) + build.ps1 → builds into windows\bin\
- The source code in
src/is shared. All three versions are built from the same code, so a fix or feature reaches every OS at once. - The folders hold the per-OS instructions and build scripts. Each build script checks which OS it's running on and refuses to run on the wrong one, so you can't accidentally build or mix up versions.
- Ready-made programs are not stored in git. They are built automatically
for each OS by GitHub Actions and attached to each
release under their OS
name. The
bin/folders are only filled locally when you run a build script, and git ignores them.
You need Rust 1.85 or newer (rustup.rs). On Windows you also need the Visual Studio C++ Build Tools ("Desktop development with C++").
git clone https://github.com/OGpisuars/StructureX.git
cd StructureXThen run the build script for your OS:
| OS | Command | Result |
|---|---|---|
| Linux | ./linux/build.sh |
linux/bin/structurex |
| macOS | ./macOS/build.sh |
macOS/bin/structurex |
| Windows (PowerShell) | powershell -ExecutionPolicy Bypass -File .\windows\build.ps1 |
windows\bin\structurex.exe |
Or use Cargo directly on any OS:
cargo build --release # → target/release/structurex (structurex.exe on Windows)
cargo install --path . # installs into ~/.cargo/bin, which is on your PATH# 1. Preview what would be created (writes nothing)
structurex -i examples/tree.txt -t ./out --dry-run
# 2. Create it, answering the [y/N] prompt
structurex -i examples/tree.txt -t ./out
# 3. Or skip the prompt (CI, scripts)
structurex -i examples/tree.txt -t ./out --yesExample session:
$ structurex -i tree.txt -t ./out
Target: /home/you/out (will be created)
[DIR] my-app/
[FILE] my-app/Cargo.toml
[DIR] my-app/src/
[FILE] my-app/src/main.rs
[FILE] my-app/README.md
2 directories and 3 files to create; 0 existing entries skipped.
Create 5 entries? [y/N] y
created my-app/
created my-app/Cargo.toml
created my-app/src/
created my-app/src/main.rs
created my-app/README.md
Done: created 2 directories and 3 files; skipped 0 existing.
On Windows, the same commands work in PowerShell and Command Prompt.
Backslash paths are fine for -i and -t (e.g. -t .\out or
-t C:\Projects\demo). Inside the tree file itself, always use /.
See windows/README.md for Windows-specific tips.
Run it again and existing entries are left alone:
[DIR] my-app/ (exists, skipping)
[FILE] my-app/Cargo.toml (exists, skipping)
...
Nothing to do: every entry already exists.
structurex -i <PATH> [-t <PATH>] [-d] [-y]
| Option | Description | Default |
|---|---|---|
-i, --input <PATH> |
Text file containing the directory tree | required |
-t, --target <PATH> |
Directory to create the structure in (created if missing) | . |
-d, --dry-run |
Print the plan and exit without writing anything | off |
-y, --yes |
Skip the [y/N] confirmation prompt |
off |
-h, --help |
Show help | |
-V, --version |
Show version |
Output streams: the plan and progress go to stdout; warnings, errors
and the confirmation prompt go to stderr, so you can redirect the plan
(structurex -i t.txt -d > plan.txt) without losing diagnostics.
Non-interactive use: if stdin is not a terminal (pipes, CI) and --yes
is not given, StructureX exits with an error instead of waiting for input.
All of these produce the same result:
Unicode (hand-drawn or `tree`) ASCII Plain indentation
────────────────────────────── ─────────────────── ─────────────────
app/ app/ app/
├── src/ |-- src/ src/
│ └── main.rs | `-- main.rs main.rs
└── README.md `-- README.md README.md
Output from the tree command works directly, including its leading .
line, its non-breaking-space padding and its trailing
N directories, M files summary:
tree -F my-template > tree.txt # Linux / macOS
structurex -i tree.txt -t ./copyWindows' built-in tree works too. Its header lines (Folder PATH listing…,
Volume serial number…) and drive line (C:. or C:\PATH) are skipped
automatically:
tree /F /A my-template > tree.txt # Windows (PowerShell or cmd)
structurex -i tree.txt -t .\copyThe tree file may be UTF-8 (with or without a byte-order mark) or
UTF-16 with a byte-order mark. Windows PowerShell 5.1 writes UTF-16 when
you redirect with >. Windows (CRLF) and Unix (LF) line endings both work.
| Rule | Behaviour |
|---|---|
| Hierarchy | Determined by the column where each name begins. Any consistent indent width works; a tab counts as 4 columns. |
| Directories | Names ending in /, or any entry that has children. |
| Files | Everything else, e.g. main.rs, Makefile, .gitignore. |
| Ambiguous names | An extension-less leaf such as docs becomes a file, with a warning suggesting docs/. Well-known names (Makefile, LICENSE, Dockerfile, …) and dotfiles don't warn. |
| Comments | # begins a comment when it starts the line or follows whitespace. src/ # sources → src/, while C#/ and issue#12.md stay intact. |
| Blank lines | Ignored, as are lines containing only │ guide characters. |
| Nested names | src/bin/tool.rs on one line creates src/ and src/bin/ implicitly. |
| Duplicates | Merged; each path is created once. |
| Glyph-like names | +page.svelte, -draft.md and `notes` are kept as names: a connector is only recognised when a branch glyph is followed by a dash. |
Parsing fails, with the line number, on:
- absolute paths (
/etc/passwd; drive paths likeC:\…on Windows) .or..components (../escape.txt)- empty components (
a//b) - NUL bytes
- a file with no entries at all
- on Windows only, names Windows can't create: names containing
< > : " | ? *or\, names ending in a dot or space, reserved device names (CON,PRN,AUX,NUL,COM1–COM9,LPT1–LPT9, also with an extension such asaux.rs), and names over 255 characters. These are caught before anything is written. On Linux and macOS these names are allowed.
A file that is neither UTF-8 nor UTF-16 is rejected with a hint on how to re-save it.
New files are pre-filled based on their exact name first, then their extension. Existing files are never touched.
| File | Content |
|---|---|
Cargo.toml |
Minimal [package] (name derived from the containing directory, made Cargo-safe), edition = "2024", empty [dependencies] |
CMakeLists.txt |
cmake_minimum_required(3.20), project(...), C++20 settings, and add_executable listing every .cpp/.cc/.cxx declared beneath it |
README.md |
# <directory name> heading and a placeholder line |
*.rs |
// Rust module — main.rs also gets a fn main() |
*.py |
#!/usr/bin/env python3 and a module docstring — main.py / __main__.py also get a main() and if __name__ == "__main__": guard |
*.cpp, *.cc, *.cxx |
main.* → #include <iostream> + int main(); others → #include "<name>.hpp" if a matching header sits next to them, else #include <iostream> |
*.hpp, *.h, *.hh, *.hxx |
#pragma once |
| anything else | empty file |
The project name used by Cargo.toml, CMakeLists.txt and README.md is
the name of the directory that contains the file; for files at the target
root, the target directory's own name is used.
The generated starters compile as-is:
cd out/my-project/rust-core && cargo run # Hello, world!
cd out/my-project/cpp-engine && cmake -B build && cmake --build buildStructureX runs in four stages: parse → plan → confirm → create. Every check happens before the first byte is written, so a run either does nothing or completes cleanly.
tree.txt ─► clean lines ─► (column, name) ─► stack finds parent ─► [Dir/File] list
│
created on disk ◄── [y/N] ◄── print [DIR]/[FILE] plan ◄── inspect each path on disk
- Parse arguments.
clapturns the command line into aClistruct. Missing-ior unknown flags print usage and exit with code2. - Read and decode the input file (
src/input.rs): detect UTF-8 or UTF-16 from the byte-order mark. Unreadable or undecodable files fail witherror: cannot read input file ….
-
Clean each line. Strip a leading byte-order mark, remove the comment (
strip_comment), and skip blank lines,tree's summary footer and the Windowstreebanner lines..and drive lines likeC:.stand for the target itself. -
Split the prefix from the name.
split_prefixwalks past spaces, tabs,│guides and one connector (├──,|--, …), returning the column where the name begins and the name itself:Line Column Name my-app/0 my-app/├── src/4 src/│ └── main.rs8 main.rs└── README.md4 README.md -
Find the parent with a stack of open entries. For each line, pop every entry whose column is ≥ the current column; the remaining top is the parent; then push the current entry:
my-app/ col 0 → stack empty → path my-app src/ col 4 → top my-app (0 < 4) → path my-app/src main.rs col 8 → top src (4 < 8) → path my-app/src/main.rs README.md col 4 → pop main.rs, pop src → path my-app/README.mdBecause only relative columns matter, any consistent indentation works.
-
Validate the name (
validate_name): reject absolute paths,./.., empty components and NUL bytes, reporting the line number. On Windows, also apply Windows' naming rules (windows_name_problem). -
Resolve kinds (
resolve): a path is a directory if it ends in/or has descendants, otherwise a file. Implicit parents are inserted, duplicates merged, and ambiguous names produce warnings. The result is an ordered list where every parent precedes its children.
-
Resolve the target to an absolute path. If it exists it must be a directory; if not, the plan notes it will be created.
-
Inspect each entry (
inspect):On disk Result nothing Create same kind Exists — skipped other kind (file vs. directory) problem symlink resolving outside the target problem Entries under a directory that will be created are known to be new and are not probed; entries under a problem entry are skipped to avoid noise.
-
Abort on problems. All of them are reported together, before any write:
error: refusing to scaffold into `/home/you/out`: line 2: `my-app/src/` is declared as a directory but a file exists at that path -
Print the plan — every entry labelled
[DIR]or[FILE], existing ones marked(exists, skipping), and a summary count.
- Decide whether to proceed:
--dry-run→ print "nothing was written", exit0.- nothing to create → print "Nothing to do", exit
0. - no
--yes→ askCreate N entries? [y/N]. Onlyy/yescontinues; anything else (including Enter) aborts with exit1. If stdin is not a terminal, fail and ask for--yesinstead of hanging.
- Create entries in order (
Plan::execute), so parents always exist before children:- directories via
create_dir(already present → counted as skipped); - files via
create_new, which atomically fails if the file exists — so even a file that appears between planning and writing is never overwritten (reported asskipped … (appeared during run)).
- directories via
- Fill each new file with starter content from
templates::render, which matches on exact file name first, then extension. - Report what was created and skipped, and exit
0.
| Guarantee | How |
|---|---|
| No file is ever overwritten | Files are opened with create_new (atomic "create only if absent") |
| No partial run on conflicts | All conflicts are detected during planning, before any write |
| Nothing is written outside the target | Absolute paths and .. are rejected while parsing; existing paths are canonicalized and must stay inside the target |
| No accidental runs | Confirmation is required unless --yes; non-interactive runs without --yes fail fast |
| No silent misinterpretation | Ambiguous extension-less names produce warnings |
| No Windows surprises | On Windows, invalid and reserved names (CON, aux.rs, a:b) are rejected before writing, so you never get a half-finished run, a write to a device, or a hidden NTFS stream |
| Code | Meaning |
|---|---|
0 |
Success, dry run, or nothing to do |
1 |
Error (unreadable input, invalid tree, conflict, I/O failure) or confirmation declined |
2 |
Invalid command-line usage |
.
├── .cargo/config.toml # static C runtime for Windows builds
├── .github/workflows/
│ ├── ci.yml # tests on Linux, Windows and macOS on every push
│ └── release.yml # builds per-OS downloads when a v* tag is pushed
├── Cargo.toml # package manifest; release profile uses LTO + strip
├── LICENSE # MIT
├── README.md
├── clippy.toml
├── examples/
│ └── tree.txt # sample input covering Rust, C++, Python and more
├── linux/ # Linux guide + build.sh (builds into linux/bin/)
├── macOS/ # macOS guide + build.sh (builds into macOS/bin/)
├── windows/ # Windows guide + build.ps1 (builds into windows\bin\)
├── src/ # shared source code for all operating systems
│ ├── main.rs # CLI, plan display, confirmation, exit codes
│ ├── input.rs # UTF-8 / UTF-16 decoding of the tree file
│ ├── parser.rs # tree text → ordered list of (path, Dir/File)
│ ├── scaffold.rs # disk inspection, plan, conflict checks, creation
│ └── templates.rs # starter content per file name / extension
└── tests/
└── cli.rs # end-to-end tests against temporary directories
cargo test # unit + end-to-end tests
cargo clippy --all-targets -- -W clippy::pedantic
cargo fmt
cargo run -- -i examples/tree.txt -d # run without installingThe test suite covers every supported tree style (including Windows tree
output), comment handling, UTF-8/UTF-16 decoding, Windows naming rules,
implicit parents, unsafe paths, the no-overwrite guarantee, file/directory
conflicts, symlink escapes, the non-interactive --yes requirement and all
starter templates.
Every push and pull request runs ci.yml on
Linux, Windows and macOS:
- formatting (
cargo fmt --check) and lints (cargo clippy -D warnings) - the full test suite
- a dry run of
examples/tree.txt - that OS's build script (
linux/build.sh,macOS/build.sh, orwindows\build.ps1under Windows PowerShell 5.1) - a build with the minimum supported Rust version (1.85)
Results are on the Actions tab and in the badge at the top of this page.
Releases are built by release.yml when a
version tag is pushed:
# 1. Set the new version in Cargo.toml, e.g. version = "0.2.0", then:
cargo build # updates Cargo.lock
git commit -am "Release v0.2.0"
# 2. Tag and push. The tag must match Cargo.toml, or the release fails.
git tag v0.2.0
git push origin master v0.2.0GitHub Actions then builds all four downloads on real Windows, macOS and Linux machines and attaches them, with checksums, to a new release. The download links in the OS guides always point to the latest release, so they only work once the first release has been published.
StructureX is released under the MIT License. You may use, copy, modify and distribute it freely, including commercially, as long as the license text is kept with it.
No warranty. StructureX is provided "as is", without warranty of any kind.
The authors are not liable for any damage or data loss arising from its use.
Use --dry-run to review what will be created before writing to disk.