Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 114 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,57 +9,114 @@
</a>
</p>

A simple and powerful CLI tool to create project folder and file structures from a Markdown tree.
A simple and powerful CLI tool to create project folder and file structures from **Markdown** and **YAML** tree definitions.

Stop creating files and folders manually. Define your project's skeleton in a readable Markdown file and let `nirman` build it for you in seconds.
Stop creating files and folders manually. Define your projects skeleton in a readable Markdown or YAML file, and let `nirman` build it for you in seconds.

## Key Features

- **Intuitive Input:** Uses a visual, tree-style Markdown format.
- **Safe by Default:** Includes a `--dry-run` mode to preview changes.
- **Flexible:** Supports overwriting files with the `--force` flag.
- **Simple & Lightweight:** No external dependencies.
* **Two Input Formats:**

* Markdown (`.md`, `.markdown`)
* YAML (`.yml`, `.yaml`)
* **Readable Tree Syntax:** Write clean collapsible structures.
* **Safe Execution:** Preview actions with `--dry-run`.
* **Flexible:** Overwrite files using `--force`.
* **Lightweight & Fast:** Uses simple tree-based parsing.
* **Cross-platform:** Works on Linux, macOS, and Windows.

---

## Installation

You can install `Nirman-cli` directly from PyPI:
Install from PyPI:

```bash
pip install Nirman-cli
```

## Usage
---

1. Create a Markdown file (e.g., `structure.md`) defining your desired project layout:
# Usage

```markdown
my-python-app/
├── src/
│ ├── __init__.py
│ └── main.py
├── tests/
│ └── test_main.py
├── .gitignore
└── README.md
```
## 1) Markdown Example

Create a file `structure.md`:

2. Run the `nirman` command from your terminal:
```markdown
my-python-app/
├── src/
│ ├── __init__.py
│ └── main.py
├── tests/
│ └── test_main.py
└── README.md
```

```bash
nirman structure.md
```
Build the structure:

This will create the `my-python-app/` directory and all its contents in your current location.
```bash
nirman structure.md
```

### Command-Line Options
---

## 2) YAML Example

Nirman also supports YAML.
**Rule:** Individual files must be listed under a `files:` key.

Example (`structure.yml`):

```yaml
project:
src:
files:
- main.py
- utils.py
services:
api:
files:
- handler.py
- routes.py
files:
- README.md
- .gitignore
```

Build it:

```bash
nirman structure.yml
```

This produces:

```
output_folder/
└── project/
├── src/
│ ├── main.py
│ └── utils.py
├── services/
│ └── api/
│ ├── handler.py
│ └── routes.py
├── README.md
└── .gitignore
```

---

# Command-Line Options

```
usage: nirman [-h] [-o OUTPUT] [--dry-run] [-f] input_file

Build a project structure from a Markdown tree file.
Build a project structure from a Markdown (.md) or YAML (.yml/.yaml) file.

positional arguments:
input_file Path to the Markdown file containing the project structure (must have a .md or .markdown extension).
input_file Path to the structure file.

options:
-h, --help show this help message and exit
Expand All @@ -69,6 +126,36 @@ options:
-f, --force Overwrite existing files if they are encountered.
```

---

# YAML Rules (Important)

Your YAML structure must follow these rules:

1. **Every folder is a dictionary key.**
2. **All direct files inside a folder must be placed under:**

```yaml
files:
- file1.txt
- file2.py
```
3. Nested folders must be dictionaries.
4. Lists may contain:

* file names (strings)
* dictionaries for nested folders

This rule is reflected in the updated parser:

```python
# Individual files must be under "files:"
if key == "files":
...
```

---

## License

This project is licensed under the MIT License. See the [LICENSE](https://github.com/Hemanth0411/Nirman-cli/blob/main/LICENSE) file for details.
19 changes: 14 additions & 5 deletions structure.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
project:
src:
- main.py
- utils:
- helper.py
tests:
- test_main.py
files:
- main.py
- utils.py
services:
api:
files:
- handler.py
- routes.py
files:
- config.yaml
- requirements.txt
files:
- README.md
- .gitignore
28 changes: 28 additions & 0 deletions tests/test_cli_yaml_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import sys
from pathlib import Path


def test_cli_yaml_basic_generation(tmp_path, monkeypatch):
yaml_data = """
project:
files:
- a.py
- b.txt
"""

input_file = tmp_path / "structure.yml"
input_file.write_text(yaml_data)

output_dir = tmp_path / "out"

monkeypatch.setattr(
sys, "argv",
["nirman", str(input_file), "-o", str(output_dir)]
)

from nirman.cli import main
main()

assert (output_dir / "project").is_dir()
assert (output_dir / "project" / "a.py").is_file()
assert (output_dir / "project" / "b.txt").is_file()
78 changes: 78 additions & 0 deletions tests/test_yaml_parser_extended.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import pytest
from nirman.yaml_parser import parse_yaml_tree


def test_yaml_simple_files_under_files_key():
yaml_data = """
app:
files:
- a.py
- b.txt
"""
expected = [
(0, "app", True),
(1, "a.py", False),
(1, "b.txt", False),
]

assert parse_yaml_tree(yaml_data) == expected


def test_yaml_nested_structure_with_files():
yaml_data = """
project:
src:
files:
- main.py
- config.json
utils:
helpers:
files:
- helper.py
"""
expected = [
(0, "project", True),
(1, "src", True),
(2, "main.py", False),
(2, "config.json", False),
(1, "utils", True),
(2, "helpers", True),
(3, "helper.py", False),
]

assert parse_yaml_tree(yaml_data) == expected


def test_yaml_dict_without_files_is_folder():
yaml_data = """
root:
empty_folder: {}
"""
expected = [
(0, "root", True),
(1, "empty_folder", True),
]
assert parse_yaml_tree(yaml_data) == expected


def test_yaml_list_of_mixed_items():
yaml_data = """
root:
items:
- folder1:
files:
- a.py
- folder2:
files:
- b.py
"""
expected = [
(0, "root", True),
(1, "items", True),
(2, "folder1", True),
(3, "a.py", False),
(2, "folder2", True),
(3, "b.py", False),
]

assert parse_yaml_tree(yaml_data) == expected