Skip to content

About

An app that allows to paste a visual tree and makes it an actual directory

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

StructureX

CI Latest release License: MIT

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 ./out

And 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).


Contents


Features

  • Reads tree command 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-run to preview, [y/N] confirmation by default, --yes for 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, .hpp and more.
  • Rejects absolute paths, .. traversal and symlink escapes.
  • Single self-contained program per operating system, with nothing else to install.
  • Understands Windows' own tree /F output and UTF-16 files, and checks names against Windows' naming rules when running on Windows.

Download: pick your operating system

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.

What's in the OS folders?

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.

Build from source

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 StructureX

Then 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

Quick start

# 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 --yes

Example 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.

Command-line options

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.


Input format

Supported tree styles

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 ./copy

Windows' 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 .\copy

File encoding

The 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.

Rules

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.

Rejected input

Parsing fails, with the line number, on:

  • absolute paths (/etc/passwd; drive paths like C:\… 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 as aux.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.


Starter content

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 build

How it works

StructureX 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

Stage 1 — Start-up (src/main.rs)

  1. Parse arguments. clap turns the command line into a Cli struct. Missing -i or unknown flags print usage and exit with code 2.
  2. 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 with error: cannot read input file ….

Stage 2 — Parse the tree (src/parser.rs)

  1. Clean each line. Strip a leading byte-order mark, remove the comment (strip_comment), and skip blank lines, tree's summary footer and the Windows tree banner lines. . and drive lines like C:. stand for the target itself.

  2. Split the prefix from the name. split_prefix walks 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.rs 8 main.rs
    └── README.md 4 README.md
  3. 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.md
    

    Because only relative columns matter, any consistent indentation works.

  4. 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).

  5. 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.

Stage 3 — Plan against the disk (src/scaffold.rs)

  1. Resolve the target to an absolute path. If it exists it must be a directory; if not, the plan notes it will be created.

  2. 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.

  3. 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
    
  4. Print the plan — every entry labelled [DIR] or [FILE], existing ones marked (exists, skipping), and a summary count.

Stage 4 — Confirm and create (src/main.rs, src/scaffold.rs)

  1. Decide whether to proceed:
    • --dry-run → print "nothing was written", exit 0.
    • nothing to create → print "Nothing to do", exit 0.
    • no --yes → ask Create N entries? [y/N]. Only y/yes continues; anything else (including Enter) aborts with exit 1. If stdin is not a terminal, fail and ask for --yes instead of hanging.
  2. 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 as skipped … (appeared during run)).
  3. Fill each new file with starter content from templates::render, which matches on exact file name first, then extension.
  4. Report what was created and skipped, and exit 0.

Safety guarantees

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

Exit codes

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

Project layout

.
├── .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

Development

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 installing

The 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.

Continuous integration

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, or windows\build.ps1 under 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.


Publishing a release

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.0

GitHub 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.


License

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.

About

An app that allows to paste a visual tree and makes it an actual directory

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages