strux is a command-line utility written in Rust that parses specially formatted markdown files to generate directory structures and file contents. It's useful for:
- Project Bootstrapping: Quickly create initial project layouts from a template.
- Documentation: Document file structures and provide their content within a single markdown file.
- Reproducible Setups: Define file layouts declaratively.
The tool reads a markdown file, identifies actions (like creating, appending, prepending, deleting, or moving files) based on specific header formats, and executes these actions relative to a specified output directory.
- Supports creating files with content defined in code blocks.
- Supports appending content to existing files (creates if not exists).
- Supports prepending content to existing files (creates if not exists).
- Supports deleting files.
- Flexible association of headers with code blocks, allowing for descriptive text in between.
- Supports moving files.
- Multiple header formats for defining actions (Markdown headers, backticks, internal comments).
- "Wrapped" header format for associating headers with subsequent code blocks or for standalone delete/move actions.
- Automatic creation of parent directories for created, appended, prepended or moved files.
- Safety checks to prevent writing or moving files outside the target base directory.
- Option to force overwriting existing files (for create and move actions).
- Detailed summary output of actions performed, skipped, or failed.
- Pre-commit hooks configured for code quality and consistency.
-
Ensure you have Rust and Cargo installed (https://rustup.rs/).
-
Clone the repository:
git clone https://github.com/romelium/strux.git cd strux -
Build and install the binary:
cargo install --path .This will install the
struxexecutable into your Cargo bin directory (usually~/.cargo/bin/).
cargo install struxPre-compiled binaries for Linux (x86_64), macOS (x86_64, arm64), and Windows (x86_64) are available on the GitHub Releases page. Download the appropriate archive for your system, extract it, and place the strux (or strux.exe) executable in a directory included in your system's PATH.
strux [OPTIONS] <MARKDOWN_FILE>Arguments:
<MARKDOWN_FILE>: Path to the input markdown file containing the file structure definitions.
Options:
-o <DIR>,--output-dir <DIR>: The base directory where files and directories will be created, deleted or moved.- Default:
./project-generated. This path is relative to the current working directory where you run the command. - The directory will be created if it doesn't exist.
- The command will fail if the specified path exists but is not a directory.
- Default:
-f,--force: Overwrite existing files when aFileorMoved Fileaction targets a path that already exists as a file. Without this flag, existing files will be skipped. This flag does not allow replacing a directory with a file. It does not currently affectAppend FileorPrepend Fileactions beyond their standard behavior (they will operate on existing files or create new ones).-h,--help: Print help information.-V,--version: Print version information.
The processor identifies actions based on specific header patterns.
The following header formats are recognized:
1. File Actions (Create/Overwrite):
These headers are associated with the next available fenced code block (e.g., `````, ```). While they are typically placed immediately before the code block, they can be separated by paragraphs of text. The content of the associated code block becomes the content of the file.
- Markdown Headers:
## File: path/to/your/file.txt(and with more hashes like###,####, etc.)### Any descriptive text File: path/to/your/file.txt**File: path/to/your/file.txt**
- Backtick Path Only: (Implies
Fileaction)`path/to/your/file.txt`1.path/to/your/file.txt`` (Numbered list item)**path/to/your/file.txt**##path/to/your/file.txt``
Example (Standard Header):
## File: src/main.rs
```rust
fn main() {
println!("Hello, world!");
}
```2. Append File Actions:
These headers must be immediately followed by a fenced code block. The content of the code block will be appended to the specified file. If the file does not exist, it will be created with the content.
- Markdown Headers:
## Append File: path/to/your/file.txt**Append File: path/to/your/file.txt**
Example (Append Header):
## Append File: logs/app.log
```
[INFO] Application started.
```3. Prepend File Actions:
These headers must be immediately followed by a fenced code block. The content of the code block will be prepended to the specified file. If the file does not exist, it will be created with the content.
- Markdown Headers:
## Prepend File: path/to/your/file.txt**Prepend File: path/to/your/file.txt**
Example (Prepend Header):
## Prepend File: config.ini
```ini
; Generated by Strux
```4. Deleted File Actions:
These headers define files to be deleted. They should not be followed by a code block unless using the special case below.
-
Standalone Headers:
## Deleted File: path/to/old/file.log**Deleted File: path/to/old/file.log**
-
Special Case (Path in Block): A
## Deleted File:header without a path, immediately followed by a code block containing only the path to delete on its first line.
## Deleted File:
```text
path/inside/block_to_delete.tmp
```5. Moved File Actions:
These headers define files to be moved. They should not be followed by a code block. The keyword " to " (case-sensitive, with spaces) separates the source and destination paths.
- Standalone Headers:
## Moved File: old/path/file.txt to new/path/file.txt**Moved File: old/path/file.txt to new/path/file.txt**
- If a path segment itself contains " to ", that segment must be enclosed in backticks:
## Moved File: \archive/file with to in name.log` to archive/renamed_file.log`## Moved File: old/path.txt to \new path with to in name.txt``
Example (Moved File):
## Moved File: temp/report.docx to final/official_report.docx6. Internal Comment Headers (Inside Code Blocks for File, Append File, Prepend File actions):
These headers can appear on the first line inside a code block to define the file path for a File, Append File, or Prepend File action.
Supported types: File (e.g., // File: path/to/file.ext). Support for Append File and Prepend File in this format may be added in the future.
-
// File: path/to/file.ext: The header line itself is excluded from the file content. Supports paths in backticks (// File:\path with spaces.txt``).// File: utils/helper.js function greet(name) { console.log(`Hello, ${name}!`); } module.exports = { greet };
-
// path/to/file.ext: The header line is included in the file content.// scripts/run_analysis.js console.log("Running analysis...")
Heuristics apply to avoid misinterpreting comments as paths.
7. Wrapped Headers:
A header can be placed inside a ```markdown or ```md block.
-
For
File,Append File, orPrepend Fileactions, it applies to the next adjacent code block. -
For
Deleted FileorMoved Fileactions, it's a standalone action. -
Create Example:
```markdown ## File: complex_config.yaml ``` ```yaml # This is the actual content settings: feature_a: true ```
-
Append Example:
```markdown ## Append File: notes.txt ``` ``` Another note. ```
-
Delete Example:
```markdown **Deleted File: legacy_script.sh** ``` *(No following code block needed for delete)*
-
Move Example:
```markdown ## Moved File: staging/data.csv to processed/data.csv ``` *(No following code block needed for move)*
- Paths specified in headers are treated as relative to the
--output-dir. - Parent directories are created automatically as needed for
File,Append File,Prepend Fileactions and for the destination ofMoved Fileactions. - Safety: The tool prevents writing or moving files outside the resolved base output directory. Paths containing
..that would escape the base directory will cause the action to fail safely. - Paths containing invalid components (like
//or trailing/) will be skipped.
- The entire content within the fenced code block (excluding the fences themselves and certain internal headers) is written to the file (or appended/prepended).
- A trailing newline (
\n) is added to this content chunk if it doesn't already end with one.
Input (example.md):
# Example Project Structure
## File: src/main.py
```python
# Main application script
import utils
def main(): utils.helper()
if __name__ == "__main__": main()
```
## File: src/utils.py
```python
# Utility functions
def helper(): print("Helper function called.")
```
## File: temp/draft.txt
```
This is a draft file that will be moved.
```
**Deleted File: old_data.csv**
`README.md`
```markdown
# My Project
Generated by Strux.
```
## Moved File: temp/draft.txt to docs/final_draft.txt
## Append File: README.md
```
This content will be appended.
```
```md
## Deleted File: temp/to_delete.log
```
```markdown
// File: .gitignore
*.pyc
__pycache__/
```Command:
# Assuming you run this in /home/user/projects/
strux -o my_project example.md
# Output will be in /home/user/projects/my_projectResult (my_project directory):
my_project/
├── .gitignore
├── README.md # Content: "# My Project\nGenerated by Strux.\nThis content will be appended.\n"
├── docs/
│ └── final_draft.txt # Moved from temp/draft.txt
└── src/
├── main.py
└── utils.py
my_project/src/main.pyandmy_project/src/utils.pycontain their Python code.my_project/README.mdcontains its original content plus the appended text.my_project/.gitignorecontains its content.my_project/docs/final_draft.txtcontains "This is a draft file that will be moved.\n" (moved frommy_project/temp/draft.txt).- The original
my_project/temp/draft.txtis gone as it was moved. - Any pre-existing
my_project/old_data.csvormy_project/temp/to_delete.log(if they existed in the output directory before the run) would be deleted.
- Rust & Cargo (https://rustup.rs/)
pre-commit(https://pre-commit.com/)
- Clone the repository.
- Install pre-commit hooks:
pre-commit install
cargo build # Development build
cargo build --release # Release buildcargo test # Run all testsThis project uses pre-commit for automated code quality checks (formatting, linting, tests) before each commit.
This project follows the Conventional Commits specification. See COMMIT.md for details.