Skip to main content

Generates a textual or json project directory structure representation following git's .gitignore rules

Project description

Runex

A context generation tool to bring vibe coding in your life... or do even more.


Introduction

runex is a python tool that generates a plain-text or json representation of a project's directory structure (plus file contents), following Git's own .gitignore path and file matching rules.

Everything's close enough... until it isn't.

[!NOTE]
Before using this program it is highly recommended you read the Use of AI during development paragraph


Example output(s)

Let's see if this can be good for you, will it be love at first sight?

Default behavior

plain-text

no args needed

You ask:

python prompt_generator.py .

You get:

Project Structure:

my-project/
├── .gitignore
├── src/
│   └── next-big-thing.py
└── tests/


# File: next-big-thing.py
# Path: src/next-big-thing.py

#!/usr/bin/env python
"""
Hey there
"""
    
if __name__ == "__main__":
    print("Hello World!")

json

add -oj

You ask:

python prompt_generator.py . -oj

You get:

{
  "structure": {
    "name": "my-project",
    "children": [
      {
        "name": ".gitignore"
      },
      {
        "name": "src",
        "children": [
          {
            "name": "next-big-thing.py"
          }
        ]
      },
      {
        "name": "tests",
        "children": []
      }
    ]
  },
  "files": [
    {
      "filename": "next-big-thing.py",
      "path": "src/next-big-thing.py",
      "content": "#!/usr/bin/env python\n\"\"\"\nHey there\n\"\"\"\n    \nif __name__ == \"__main__\":\n    print(\"Hello World!\")\n"
    }
  ]
}

No need for file contents?

just add -s

plain text

You ask:

python prompt_generator.py . -s

You get:

Project Structure:

my-project/
├── .gitignore
├── src/
│   └── next-big-thing.py
└── tests/

json

You ask:

python prompt_generator.py . -oj -s

You get:

{
  "structure": {
    "name": "my-project",
    "children": [
      {
        "name": ".gitignore"
      },
      {
        "name": "src",
        "children": [
          {
            "name": "next-big-thing.py"
          }
        ]
      },
      {
        "name": "tests",
        "children": []
      }
    ]
  }
}

So, you got this far.

You might need this tool?
Well...

What have we learned other than that?

Answer:
You ask, You get


Purpose of This Program

The og scope of the program was to cater the need for an easy and quick way (see rfc1925 point 7a) of providing "context" to a large language model service, without painstakingly crtl-c an ctrl-v(ing) source code out of the IDE into the browser, all while t(c)rying to explain to the llm where a file should be read from and what it should actually do. (you still have to paste it back to the IDE so at least it's almost half of the work done)

If you want that, we (me and gpt) have a runex visual studio code extension available.


Usage - Deep Dive

prompt_generator.py [-h] [--casefold] [--json] [--only-structure] [--relative-root] <root_dir> [output_file]

Positional arguments:

  • root_dir
    Root directory of the project to be scanned
  • output_file
    Optional output file (default: stdout)

Options:

  • -h, --help
    show this help message and exit
  • --casefold, -c
    Enable case-insensitive matching (WM_CASEFOLD)
  • --json, -oj
    Output JSON instead of text
  • --only-structure, -s
    Omit file contents in the output
  • --relative-root, -rr
    Force the root directory name to be '.' instead of basename

In the default mode, after traversing the specified project directory, runex builds and concatenates into a single file (or stdout):

  1. A pretty-printed tree-style output of the folder structure (excluding ignored paths).
  2. A concatenated listing of the file contents found along it's path (excluding ignored files).

The same output can be also requested in a json format and for both you can request only the structure of the project to be returned.


Examples

Tool executed with the --only-structure arg on the "." directory:

venvnicolobalestrino@Nicolos-Air runex % python prompt_generator.py . -s
Project Structure:

runex/
├── .gitignore
├── README.md
├── json_test_cases/
│   ├── t_00.json
│   ├── t_01.json
│   ├── t_02.json
│   ├── t_03.json
│   ├── t_04.json
│   ├── t_05.json
│   ├── t_06.json
│   ├── t_07.json
│   ├── t_08.json
│   ├── t_09.json
│   ├── t_10.json
│   ├── t_11.json
│   └── t_12.json
├── modules/
│   ├── __init__.py
│   ├── ignore_logic.py
│   └── wildmatch.py
├── prompt_generator.py
├── requirements.txt
└── tests/
    ├── __init__.py
    ├── all_unicode_posix_extended_test.py
    ├── all_unicode_posix_test.py
    ├── custom_posix_test.py
    ├── deepseek_test.py
    ├── extreme_test.py
    ├── gitignore_doc_json_test.py
    ├── ignore_logic_test.py
    ├── posix_test.py
    ├── prompt_generation_test.py
    ├── prompt_generator_json_test.py
    ├── suite_test.py
    ├── te_test.py
    └── wildmatch_test.py

Tool executed with the --only-structure and --json args on the "." directory:

venvnicolobalestrino@Nicolos-Air runex % python prompt_generator.py . -oj -s
{
  "structure": {
    "name": "runex",
    "children": [
      {
        "name": ".gitignore"
      },
      {
        "name": "README.md"
      },
      {
        "name": "json_test_cases",
        "children": [
          {
            "name": "t_00.json"
          },
          {
            "name": "t_01.json"
          },
          {
            "name": "t_02.json"
          },
          {
            "name": "t_03.json"
          },
          {
            "name": "t_04.json"
          },
          {
            "name": "t_05.json"
          },
          {
            "name": "t_06.json"
          },
          {
            "name": "t_07.json"
          },
          {
            "name": "t_08.json"
          },
          {
            "name": "t_09.json"
          },
          {
            "name": "t_10.json"
          },
          {
            "name": "t_11.json"
          },
          {
            "name": "t_12.json"
          }
        ]
      },
      {
        "name": "modules",
        "children": [
          {
            "name": "__init__.py"
          },
          {
            "name": "ignore_logic.py"
          },
          {
            "name": "wildmatch.py"
          }
        ]
      },
      {
        "name": "prompt_generator.py"
      },
      {
        "name": "requirements.txt"
      },
      {
        "name": "tests",
        "children": [
          {
            "name": "__init__.py"
          },
          {
            "name": "all_unicode_posix_extended_test.py"
          },
          {
            "name": "all_unicode_posix_test.py"
          },
          {
            "name": "custom_posix_test.py"
          },
          {
            "name": "deepseek_test.py"
          },
          {
            "name": "extreme_test.py"
          },
          {
            "name": "gitignore_doc_json_test.py"
          },
          {
            "name": "ignore_logic_test.py"
          },
          {
            "name": "posix_test.py"
          },
          {
            "name": "prompt_generation_test.py"
          },
          {
            "name": "prompt_generator_json_test.py"
          },
          {
            "name": "suite_test.py"
          },
          {
            "name": "te_test.py"
          },
          {
            "name": "wildmatch_test.py"
          }
        ]
      }
    ]
  }
}

The plain text output without the -s will output everything, following the .gitignore rules:

Project Structure:

...tree representation...

# File: README.md
# Path: README.md

# Runex

`runex` is a python tool that generates a plain text or json representation of a project's directory structure (plus file contents) following Git's `.gitignore` path and file matching rules.

Without further ado let's share the motto of this project:

_Well_ <br>
_I guess that it's close enough until it isn't._

etc etc etc etc

The json output will follow this standard:

  • files will never have a children attribute
  • directories, even if empty, will always have a children attribute

That's how you will be able to differentiate between them.

In -s mode the following will be omitted:

  • "path" - full file path
  • "content" - actual file source content

Testing

The goal of this program is to mimic 100% the .gitignore logic, so we need a way of testing that.

The initial tests during development were written with AI in an unreliable way (you will find them under the tests directory) and were a pain to maintain.

So now, there is a single test file that actually matters, it is: tests/suite_test.py

This, will read and execute against runex's json output, multiple test cases, written and evaluated by a human, found in a specific dedicated folder json_test_cases all following the naming t_*.json.

This will enable you to just submit truly tested cases to which the expected git behavior is known, this way we can just make this implementation bulletproof.

The test format goes as follows:

ex, t_00.json

{
    "gitignore": [],
    "initial_structure": {
        "structure": {
            "name": "project",
            "children": [
                {"name": ".gitignore"},
                {"name": "file.txt"}
            ]
        }
    },
    "tracked_structure": {
        "structure": {
            "name": "project",
            "children": [
                {"name": ".gitignore"},
                {"name": "file.txt"}
            ]
        }
    }
}

That's it, a json file:

  • "gitignore": [] is an array containing strings that represent ignore rules, "gitignore": ["file.txt", "file2.csv"] ignores those two files, "gitignore": ["!file.txt", "!file2.csv"] unignores them and so on, see each element of the array as being one line of a .gitignore.

  • "initial_structure" will be used by the script to build, using tempfile, the actual directory and file structure to test against

    • the .gitignore, if present in the initial_structure, will be populated with the contents found in the array of step 1.
  • "tracked_structure" is the expected behavior, if our script correctly mimics .gitignore rules, it will produce this output.

If the output produced by runex when run against initial_structure matches 100% the tracked_structure the single test case will be passed.

What about nested .gitignore files?

This is how you can do it:

{
    "gitignore": ["file.txt"],
    "initial_structure": {
        "structure": {
            "name": "project",
            "children": [
                {
                    "name": ".gitignore",
                    "contents": ["!file.txt"]
                },
                {"name": "file.txt"}
            ]
        }
    },
    "tracked_structure": {
        "structure": {
            "name": "project",
            "children": [
                {"name": ".gitignore"},
                {"name": "file.txt"}
            ]
        }
    }
}

In the initial_structure if you create a child named .gitignore and you give it a contents key whose value is an array structured just like the array in the simple test cases described before, upon folder creation that file will be written with the specified contents.
If no contents are provided the program will just leave it empty.

Please, write a lot of edge cases that will make this program literally burn, this is the only way it will become better.

I mean everything, posix bracket expressions, unicode characters, nested structures combined and coordinated firework explosions etcetera.

At tests/suite_test.py there is the code that will run all of the t_*.json test cases present in the json_test_cases folder.

The other tests found under tests all pass for now, so in theory the program should be kept so that they keep passing in the future, but given that they were all AI generated... they might just be wrong, a lot of them are duplicated, and i'm not even sure what all of them actually do, they are too many, and i'm too lazy, so, you'll do the work if you want the tool!

The main intended way of testing this program from now on is adding tests under the folder json_test_cases


Use of AI during development

Keep in mind: this was written extensively with the use of AI llm models, in particular you can find code from OpenAI o3-mini-high, o3-mini - Deepseek R1 - Gemini 2.0 Flash

o3-mini-high overrall kept this project together,
when it got stuck i used Deepseek R1 to get it-unstuck,
Gemini 2.0 Flash, well i played with it for a while but for now wasn't really helpful (it truly wasn't i can safely say now)

Code and Comments as well as knowledge on how git works come from the AI, and i just kind of directed the whole thing by approximation and intuition, in fact, the only thing that is not AI generated is this readme.

This code is not guaranteed to mimic 100% the behavior of the .gitignore ignore logic of the master and commander (c git) - it is the dream though, but, well, don't ask for the differences between .gitignore and this, because the AI doenn't know for now, and i certainly don't. I guess that it's close enough.


Hopefully it was useful!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

runex-0.0.2.tar.gz (15.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

runex-0.0.2-py3-none-any.whl (14.2 kB view details)

Uploaded Python 3

File details

Details for the file runex-0.0.2.tar.gz.

File metadata

  • Download URL: runex-0.0.2.tar.gz
  • Upload date:
  • Size: 15.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.1 CPython/3.13.2 Darwin/23.6.0

File hashes

Hashes for runex-0.0.2.tar.gz
Algorithm Hash digest
SHA256 5bfe726e03c3dd42b7473579f3086c4af4dcb74eff2c9a93d6c78e1d8feb3eb7
MD5 cefce2774251cf2b5aabf67aa8b1a2e1
BLAKE2b-256 df6eac73efb9cec804df62ce852e1b28ab900142461a243a0bb711c36b7efd64

See more details on using hashes here.

File details

Details for the file runex-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: runex-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 14.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.1.1 CPython/3.13.2 Darwin/23.6.0

File hashes

Hashes for runex-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 ffd624ef37694494d92dc30cd060d2c7274c013c8354fd6e5d9bdbddabd8e75f
MD5 b48b3f814b68b31d1134d6e1c48a4cd7
BLAKE2b-256 67831ce59fc9c81a521e8f2cf07a84789dd22ead93582ba0380063a95df74b18

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page