<context>
<directory_tree>
dir2prompt/
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── howto.txt
├── project/
│   ├── README.md
│   ├── experiments/
│   ├── requirements.txt
│   └── src/
│       ├── __init__.py
│       ├── main.py
│       ├── old.py
│       ├── production.ipynb
│       └── testing.rs
├── pyproject.toml
└── src/
    ├── config.json
    └── lib.rs
</directory_tree>

<files>

<file>
<path>Cargo.lock</path>
<content>
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4

[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
 "memchr",
]

[[package]]
name = "anstream"
version = "0.6.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933"
dependencies = [
 "anstyle",
 "anstyle-parse",
 "anstyle-query",
 "anstyle-wincon",
 "colorchoice",
 "is_terminal_polyfill",
 "utf8parse",
]

[[package]]
name = "anstyle"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"

[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
 "utf8parse",
]

[[package]]
name = "anstyle-query"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9"
dependencies = [
 "windows-sys",
]

[[package]]
name = "anstyle-wincon"
version = "3.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882"
dependencies = [
 "anstyle",
 "once_cell_polyfill",
 "windows-sys",
]

[[package]]
name = "anyhow"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"

[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"

[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"

[[package]]
name = "clap"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
dependencies = [
 "clap_builder",
 "clap_derive",
]

[[package]]
name = "clap_builder"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
dependencies = [
 "anstream",
 "anstyle",
 "clap_lex",
 "strsim",
]

[[package]]
name = "clap_derive"
version = "4.5.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
dependencies = [
 "heck",
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "clap_lex"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"

[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"

[[package]]
name = "dir2prompt"
version = "2.0.0"
dependencies = [
 "anyhow",
 "clap",
 "once_cell",
 "pyo3",
 "serde",
 "serde_json",
 "yash-fnmatch",
]

[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"

[[package]]
name = "indoc"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"

[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"

[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"

[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"

[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"

[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
 "autocfg",
]

[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"

[[package]]
name = "once_cell_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad"

[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"

[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
 "unicode-ident",
]

[[package]]
name = "pyo3"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
dependencies = [
 "cfg-if",
 "indoc",
 "libc",
 "memoffset",
 "once_cell",
 "portable-atomic",
 "pyo3-build-config",
 "pyo3-ffi",
 "pyo3-macros",
 "unindent",
]

[[package]]
name = "pyo3-build-config"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
dependencies = [
 "once_cell",
 "target-lexicon",
]

[[package]]
name = "pyo3-ffi"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
dependencies = [
 "libc",
 "pyo3-build-config",
]

[[package]]
name = "pyo3-macros"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
dependencies = [
 "proc-macro2",
 "pyo3-macros-backend",
 "quote",
 "syn",
]

[[package]]
name = "pyo3-macros-backend"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
dependencies = [
 "heck",
 "proc-macro2",
 "pyo3-build-config",
 "quote",
 "syn",
]

[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-automata",
 "regex-syntax",
]

[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
 "aho-corasick",
 "memchr",
 "regex-syntax",
]

[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"

[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"

[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
 "serde_derive",
]

[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
 "itoa",
 "memchr",
 "ryu",
 "serde",
]

[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"

[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-ident",
]

[[package]]
name = "target-lexicon"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"

[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
 "thiserror-impl",
]

[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"

[[package]]
name = "unindent"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"

[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"

[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
 "windows-targets",
]

[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
 "windows_aarch64_gnullvm",
 "windows_aarch64_msvc",
 "windows_i686_gnu",
 "windows_i686_gnullvm",
 "windows_i686_msvc",
 "windows_x86_64_gnu",
 "windows_x86_64_gnullvm",
 "windows_x86_64_msvc",
]

[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"

[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"

[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"

[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"

[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"

[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"

[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"

[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"

[[package]]
name = "yash-fnmatch"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d33de323788da593d6dff84d7826f849a58df55664a5bb505e52b092d558d3ec"
dependencies = [
 "regex",
 "regex-syntax",
 "thiserror",
]

</content>
</file>

<file>
<path>Cargo.toml</path>
<content>
[package]
name = "dir2prompt"
version = "2.0.0"
edition = "2024"

[lib]
crate-type = ["cdylib"]

[dependencies]
anyhow = "1.0.98"
clap = { version = "4.5.41", features = ["derive"] }
once_cell = "1.21.3"
pyo3 = { version = "0.22.2", features = ["extension-module"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
yash-fnmatch = "1.1.2"

</content>
</file>

<file>
<path>LICENSE</path>
<content>
MIT License

Copyright (c) 2024 mkieffer1107

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

</content>
</file>

<file>
<path>README.md</path>
<content>
# dir2prompt

[![PyPI version](https://img.shields.io/pypi/v/dir2prompt.svg)](https://pypi.org/project/dir2prompt/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

When you feel too lazy to selectively copy code from all the various files in your project, just copy it all! Inspired by [repo2prompt](https://github.com/andrewgcodes/repo2prompt).

## Installation 💻

You can install `dir2prompt` using pip:

```sh
pip install dir2prompt
```

## Usage 🚀

To generate a prompt from a directory, use the `d2p` command followed by the desired options:

```sh
d2p [directory path] --filters [file extensions] --outpath [output path] --outfile [output file name] --ignore-dir [directories to ignore] --ignore-file [files to ignore] --config [path to config file]

```

For ease of use, you can select a directory by passing it in as a positional argument

```sh
d2p [directory path]
```

## Options ⚙️

`--filters`: File extensions to include in the prompt (default: all files).

`--outpath`: The output path for the prompt file (default: current directory).

`--outfile`: The name of the output file (default: `<directory_name>_prompt`).

`--ignore-dir`: Additional directories to ignore (e.g., `experiments`, `run*`).

`--ignore-file`: Additional file types to ignore (e.g., `pt`, `rs`).

`--config`: Path to a custom config file (default: `config.json` in the package directory).

`--clean`: Remove all `<folder>_prompt.txt` files based on discovered directories.

## Example 🌟

Here's an example of how to use `dir2prompt` to generate a prompt:

```sh
d2p --filters py txt md ipynb --ignore-dir experiments __pycache__ --ignore-file old.py
```

This command will generate a prompt for the specified directory, including only files with the extensions `py`, `txt`, `md`, `ipynb`, ignoring the `experiments` and `__pycache__` directories, and ignoring the `old.py` file. 

Note that ignored directories are not included in the directory tree, but that ignored files are. However, the content of the ignored files will not be written to the final prompt under the `<files>` tag. This might be changed later...

In this example, the generated prompt will be saved as a `txt` file in the directory that `d2p` is called in with the name `project_prompt.txt`, and will have the following structure:

**<dir_name>_prompt.txt**
```xml
<context>
<directory_tree>
project/
├── README.md
├── requirements.txt
└── src/
    ├── __init__.py
    ├── main.py
    ├── old.py
    ├── production.ipynb
    └── testing.rs
</directory_tree>

<files>

<file>
<path>README.md</path>
<content>
# Project Title

This is an example Python project.
</content>
</file>

<file>
<path>requirements.txt</path>
<content>
numpy==1.21.0
pandas==1.3.0
</content>
</file>

<file>
<path>src/__init__.py</path>
<content>
EMPTY FILE
</content>
</file>

<file>
<path>src/main.py</path>
<content>
import numpy as np
import pandas as pd

def main():
    print("Hello, World!")

if __name__ == "__main__":
    main()
</content>
</file>

<file>
<path>src/production.ipynb</path>
<content>
---------- Cell 1 (markdown) ----------
### Biologically inspired artificial neuron 

$$
y = mx + b
$$

---------- Cell 2 (code) ----------
def neuron(x, m, b):
    return m * x + b


</content>
</file>

</files>
</context>
```

You can then feed this prompt into an LLM to provide it with context about your project 🦾

## Config File 📋

`dir2prompt` uses a config file, `config.json`, to list common directories and files that should be ignored and excluded from the prompt. You can customize the behavior by supplying your own config file using the `--config` option:

**example.json**
```json
{
    "IGNORE_DIRS": [
        "experiments",
        "run*",
        ...
    ],
    "IGNORE_FILES": [
        ".pt",
        ".rs",
        ...
    ]
}
```





## License 📄

`dir2prompt` is released under the MIT License 🤓

## Contributing 🤝

Contributions are welcome! If you find any issues or have suggestions for improvements, please open an issue or submit a pull request on the GitHub repository.



</content>
</file>

<file>
<path>howto.txt</path>
<content>
https://packaging.python.org/en/latest/tutorials/packaging-projects/#installing-your-newly-uploaded-package

To push changes, remove contents from list

rm -rf dist/*

Then 

python3 -m build 
twine upload dist/*


https://pypi.org/project/dir2prompt/

api key in pypi dir 



----------------
new stuff:


build with 

maturin develop --release
</content>
</file>

<file>
<path>project/README.md</path>
<content>
# Project Title

This is an example Python project.
</content>
</file>

<file>
<path>project/requirements.txt</path>
<content>
numpy==1.21.0
pandas==1.3.0
</content>
</file>

<file>
<path>project/src/__init__.py</path>
<content>
EMPTY FILE
</content>
</file>

<file>
<path>project/src/main.py</path>
<content>
import numpy as np
import pandas as pd

def main():
    print("Hello, World!")

if __name__ == "__main__":
    main()
</content>
</file>

<file>
<path>project/src/old.py</path>
<content>
EMPTY FILE
</content>
</file>

<file>
<path>project/src/production.ipynb</path>
<content>
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Biologically inspired artificial neuron \n",
    "\n",
    "$$\n",
    "y = mx + b\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def neuron(x, m, b):\n",
    "    return m * x + b"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "cashbot",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.9.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}

</content>
</file>

<file>
<path>project/src/testing.rs</path>
<content>
EMPTY FILE
</content>
</file>

<file>
<path>pyproject.toml</path>
<content>
[project]
name = "dir2prompt"
version = "2.0.0"
license = {file = "LICENSE"}
authors = [
  { name="Max Kieffer", email="wkieffer@ufl.edu" },
]
description = "Generate prompts for long-context LLMs using the content in your directory"
readme = "README.md"
requires-python = ">=3.6"
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
]
keywords = [
    "prompt engineering",
    "large language model",
    "directory structure",
    "prompt generation",
    "file tree visualization",
    "directory to prompt",
    "automation tools",
    "developer utilities",
    "code documentation",
]

[project.urls]
Homepage = "https://github.com/mkieffer1107/dir2prompt"
Issues = "https://github.com/mkieffer1107/dir2prompt/issues"

[project.scripts]
d2p = "dir2prompt:cli"

[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
</content>
</file>

<file>
<path>src/config.json</path>
<content>
{
    "IGNORE_DIRS": [
        ".bundle",
        ".cache",
        ".eggs",
        ".env",
        ".gradle",
        ".hypothesis",
        ".idea",
        ".ipynb_checkpoints",
        ".mvn",
        ".mypy_cache",
        ".nox",
        ".pytest_cache",
        ".tox",
        ".venv",
        ".vs",
        ".vscode",
        ".egg-info",
        "pycache",
        "bin",
        "build",
        "classes",
        "coverage",
        "deps",
        "dist",
        "env",
        "logs",
        "node_modules",
        "obj",
        "out",
        "target",
        "temp",
        "tmp",
        "vendor",
        "venv"
    ],
    "IGNORE_FILES": [
        "##",
        ".#",
        ".DS_Store",
        ".Python",
        ".byebug_history",
        ".coverage",
        ".coveragerc",
        ".env",
        ".env.development.local",
        ".env.local",
        ".env.test.local",
        ".gitignore",
        ".sw?",
        "~",
        ".a",
        ".bak",
        ".class",
        ".cmake",
        ".cov",
        ".d",
        ".db",
        ".dll",
        ".ear",
        ".exe",
        ".gcda",
        ".gcno",
        ".gcov",
        ".gem",
        ".gz",
        ".iso",
        ".jar",
        ".lib",
        ".lock",
        ".log",
        ".map",
        ".min.css",
        ".min.js",
        ".o",
        ".obj",
        ".out",
        ".pdb",
        ".phpt",
        ".pid",
        ".prof",
        ".pyc",
        ".pyd",
        ".pyo",
        ".rar",
        ".rbc",
        ".rlib",
        ".rmeta",
        ".seed",
        ".so",
        ".sqlite3",
        ".swo",
        ".swp",
        ".tar",
        ".temp",
        ".test",
        ".tgz",
        ".tmp",
        ".war",
        ".7z",
        "CMakeCache.txt",
        "TEST-.xml",
        "Thumbs.db",
        "composer.phar",
        "coverage.xml",
        "desktop.ini",
        "hs_err_pid*",
        "junit.xml",
        "nosetests.xml",
        "npm-debug.log",
        "pip-delete-this-directory.txt",
        "pip-log.txt",
        "pytest.xml",
        "yarn-debug.log",
        "yarn-error.log"
    ]
}
</content>
</file>

<file>
<path>src/lib.rs</path>
<content>
use std::{
    env,
    fs,
    path::{Path, PathBuf},
    collections::HashSet,
};

use clap::Parser;
use once_cell::sync::Lazy;
use pyo3::prelude::*;
use pyo3::wrap_pyfunction;
use serde::Deserialize;
use yash_fnmatch::{without_escape, Pattern};

/// ----------  Config that used to live in config.json  ----------
static DEFAULT_CONFIG: &str = include_str!("config.json");

#[derive(Deserialize, Clone)]
struct IgnoreConfig {
    #[serde(rename = "IGNORE_DIRS")]
    dirs: Vec<String>,
    #[serde(rename = "IGNORE_FILES")]
    files: Vec<String>,
}

/// Lazily parse the default ignore lists once.
static DEFAULT_IGNORE: Lazy<IgnoreConfig> = Lazy::new(|| {
    serde_json::from_str(DEFAULT_CONFIG).expect("embedded config.json is valid")
});

/// ----------  Command-line interface  ----------
#[derive(Parser, Debug)]
#[command(name = "d2p", about = "Generate a prompt for a directory")]
struct Cli {
    /// Directory to scan
    #[arg(default_value = ".", help = "The directory to generate the prompt for")]
    dir: String,

    /// File-extension filters
    #[arg(long, help = "Filter for and process only files with these extensions (e.g., --filters py rs txt md)")]
    filters: Vec<String>,

    /// Additional directories to ignore
    #[arg(long = "ignore-dir", help = "Additional directories to ignore (e.g. --ignore-dir experiments __pycache__)")]
    ignore_dirs: Vec<String>,

    /// Additional files to ignore
    #[arg(long = "ignore-file", help = "Additional files or extensions to ignore (e.g. --ignore-file old.py rs)")]
    ignore_files: Vec<String>,

    /// Output path for prompt file
    #[arg(long, default_value = ".", hide_default_value = true,help = "The output path for the prompt file (default: current directory)")]
    outpath: String,

    /// Output file name for prompt file
    #[arg(long, help = "The name of the output file (default: <dir_name>_prompt)")]
    outfile: Option<String>,

    /// Path to custom config file
    #[arg(long, help = "Path to a custom configuration file (default: embedded config.json)")]
    config: Option<PathBuf>,

    /// Clean up all <folder>_prompt.txt files
    #[arg(long, help = "Remove all <folder>_prompt.txt files based on discovered directories")]
    clean: bool,
}

/// Exported for use in Python’s console-script stub.
#[pyfunction]
fn cli(py: Python<'_>) -> PyResult<()> {
    // Borrow sys.argv from Python so `d2p` behaves the same via pip or cargo run
    let sys = py.import_bound("sys")?;
    let argv: Vec<String> = sys.getattr("argv")?.extract()?;
    run_cli(argv.into_iter().skip(1))
        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
}

/// Real CLI body so we can call it from native tests too.
fn run_cli<I: IntoIterator<Item = String>>(raw_args: I) -> anyhow::Result<()> {
    // Insert dummy program name so clap parses flags correctly
    let mut args: Vec<String> = vec!["d2p".to_string()];
    args.extend(raw_args);

    let cli = Cli::parse_from(args);
    let config = cli
        .config
        .as_deref()
        .map(load_config)
        .transpose()?
        .unwrap_or_else(|| DEFAULT_IGNORE.clone());

    let dir_ignore = merge(&config.dirs, &cli.ignore_dirs);
    let dir_patterns = compile_patterns(&dir_ignore)?;

    if cli.clean {
        let dir_path = Path::new(&cli.dir);
        let base = if cli.dir == "." {
            env::current_dir()?.file_name().unwrap().to_string_lossy().into_owned()
        } else {
            dir_path
                .file_name()
                .ok_or_else(|| anyhow::anyhow!("invalid directory"))?
                .to_string_lossy()
                .into_owned()
        };
        let mut dir_names: HashSet<String> = HashSet::new();
        dir_names.insert(base);
        let sub_dirs = collect_dirs(dir_path, &dir_patterns)?;
        dir_names.extend(sub_dirs);

        for name in dir_names {
            let prompt_file = Path::new(&cli.outpath).join(format!("{}_prompt.txt", name));
            if prompt_file.exists() {
                fs::remove_file(&prompt_file)?;
                println!("Removed {}", prompt_file.display());
            }
        }
        Ok(())
    } else {
        let dir_path = Path::new(&cli.dir);
        let dir_name = if cli.dir == "." {
            env::current_dir()?.file_name().unwrap().to_string_lossy().to_string()
        } else {
            dir_path.file_name().ok_or_else(|| anyhow::anyhow!("invalid directory"))?.to_string_lossy().to_string()
        };
        let outfile = cli.outfile.unwrap_or_else(|| format!("{}_prompt", dir_name));

        let prompt = build_prompt_internal(
            &cli.dir,
            &cli.filters,
            &dir_ignore,
            &merge(&config.files, &cli.ignore_files),
        )?;

        let outpath = Path::new(&cli.outpath).join(format!("{outfile}.txt"));
        fs::write(&outpath, prompt)?;
        println!("Prompt saved to {}", outpath.display());
        Ok(())
    }
}

/// Helper: merge default + CLI ignore lists
fn merge(base: &[String], extra: &[String]) -> Vec<String> {
    let mut out = base.to_owned();
    out.extend(extra.iter().cloned());
    out
}

/// Optional external config file
fn load_config(path: &Path) -> anyhow::Result<IgnoreConfig> {
    Ok(serde_json::from_str(&fs::read_to_string(path)?)?)
}

/// ----------  Python-facing build_prompt()  ----------
#[pyfunction]
#[pyo3(signature = (dir=".", filters=Vec::<String>::new(), ignore_dirs=Vec::<String>::new(), ignore_files=Vec::<String>::new()))]
fn build_prompt(
    dir: &str,
    filters: Vec<String>,
    ignore_dirs: Vec<String>,
    ignore_files: Vec<String>,
) -> PyResult<String> {
    build_prompt_internal(dir, &filters, &ignore_dirs, &ignore_files)
        .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
}

/// Shared implementation for CLI + Python call
fn build_prompt_internal(
    dir: &str,
    filters: &[String],
    ignore_dirs: &[String],
    ignore_files: &[String],
) -> anyhow::Result<String> {
    // 1. prepare ignore globs
    let dir_patterns = compile_patterns(ignore_dirs)?;
    let file_patterns = compile_patterns(ignore_files)?;

    // Collect all non-ignored directory names
    let dir_path = Path::new(dir);
    let base = if dir == "." {
        env::current_dir()?.file_name().unwrap().to_string_lossy().into_owned()
    } else {
        dir_path
            .file_name()
            .ok_or_else(|| anyhow::anyhow!("invalid directory"))?
            .to_string_lossy()
            .into_owned()
    };
    let mut dir_names: HashSet<String> = HashSet::new();
    dir_names.insert(base.clone());
    let sub_dirs = collect_dirs(dir_path, &dir_patterns)?;
    dir_names.extend(sub_dirs);

    // Create exact patterns for <dir>_prompt.txt
    let prompt_ignores: Vec<String> = dir_names.iter().map(|d| format!("{}_prompt.txt", d)).collect();
    let prompt_patterns = compile_patterns(&prompt_ignores)?;

    // 2. walk directory, collect files, render tree
    let mut tree = format!("{}/\n", base);
    let mut files = Vec::<PathBuf>::new();
    walk(
        dir_path,
        Path::new(""),
        "",
        &dir_patterns,
        &file_patterns,
        &prompt_patterns,
        &mut tree,
        &mut files,
    )?;

    // 3. stitch final prompt
    let mut prompt = String::from("<context>\n<directory_tree>\n");
    prompt.push_str(&tree);
    prompt.push_str("</directory_tree>\n\n<files>\n\n");

    for rel in files {
        let full = dir_path.join(&rel);
        if filters.is_empty() || filters.iter().any(|f| rel.to_string_lossy().ends_with(f)) {
            let content = fs::read_to_string(&full).unwrap_or_else(|_| "BINARY OR UNREADABLE".into());
            prompt.push_str(&format!(
                "<file>\n<path>{}</path>\n<content>\n{}\n</content>\n</file>\n\n",
                rel.display(),
                if content.trim().is_empty() { "EMPTY FILE" } else { &content }
            ));
        }
    }
    prompt.push_str("</files>\n</context>");
    Ok(prompt)
}

/// Collect non-ignored directory names
fn collect_dirs(abs: &Path, dir_pats: &[Pattern]) -> anyhow::Result<HashSet<String>> {
    let mut dirs = HashSet::new();
    let mut entries: Vec<_> = fs::read_dir(abs)?
        .filter_map(|e| e.ok())
        .map(|e| e.file_name().to_string_lossy().into_owned())
        .collect();
    entries.sort();

    for entry in entries {
        let abs_path = abs.join(&entry);
        if abs_path.is_dir() {
            if !dir_pats.iter().any(|p| p.is_match(&entry)) {
                dirs.insert(entry.clone());
                let sub = collect_dirs(&abs_path, dir_pats)?;
                dirs.extend(sub);
            }
        }
    }
    Ok(dirs)
}

/// Walk directory recursively with proper indentation
fn walk(
    abs: &Path,
    rel: &Path,
    current_indent: &str,
    dir_pats: &[Pattern],
    file_pats: &[Pattern],
    prompt_pats: &[Pattern],
    tree: &mut String,
    files: &mut Vec<PathBuf>,
) -> anyhow::Result<()> {
    let mut visible_entries: Vec<String> = Vec::new();
    for entry_res in fs::read_dir(abs)? {
        if let Ok(dir_entry) = entry_res {
            let entry = dir_entry.file_name().to_string_lossy().into_owned();
            let abs_path = abs.join(&entry);
            let ignore = if abs_path.is_dir() {
                dir_pats.iter().any(|p| p.is_match(&entry))
            } else {
                file_pats.iter().any(|p| p.is_match(&entry)) || prompt_pats.iter().any(|p| p.is_match(&entry))
            };
            if !ignore {
                visible_entries.push(entry);
            }
        }
    }
    visible_entries.sort();

    for (i, entry) in visible_entries.iter().enumerate() {
        let is_last = i + 1 == visible_entries.len();
        let connector = if is_last { "└── " } else { "├── " };
        tree.push_str(current_indent);
        tree.push_str(connector);
        tree.push_str(entry);

        let abs_path = abs.join(entry);
        if abs_path.is_dir() {
            tree.push_str("/\n");
            let child_indent = format!("{}{}", current_indent, if is_last { "    " } else { "│   " });
            walk(
                &abs_path,
                &rel.join(entry),
                &child_indent,
                dir_pats,
                file_pats,
                prompt_pats,
                tree,
                files,
            )?;
        } else {
            tree.push('\n');
            files.push(rel.join(entry));
        }
    }
    Ok(())
}

/// Compile ignore patterns
fn compile_patterns(globs: &[String]) -> anyhow::Result<Vec<Pattern>> {
    globs
        .iter()
        .map(|g| Pattern::parse(without_escape(g)).map_err(|e| anyhow::anyhow!(e.to_string())))
        .collect()
}

/// ----------  Python module entry-point  ----------
#[pymodule]
fn dir2prompt(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_function(wrap_pyfunction!(build_prompt, m)?)?;
    m.add_function(wrap_pyfunction!(cli, m)?)?;
    Ok(())
}
</content>
</file>

</files>
</context>