Merge pull request 'develop' (#3) from develop into main
Reviewed-on: Fabel/Tree-Structurer#3
This commit is contained in:
commit
1d8545cab9
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Win32",
|
||||
"includePath": [
|
||||
"${workspaceFolder}/**",
|
||||
"C:/Users/Falko/AppData/Local/Programs/Python/Python311/include",
|
||||
"C:/Users/Falko/AppData/Local/Programs/Python/Python311/Lib/site-packages/pybind11/include"
|
||||
],
|
||||
"defines": [],
|
||||
"windowsSdkVersion": "10.0.19041.0",
|
||||
"compilerPath": "cl.exe",
|
||||
"cStandard": "c17",
|
||||
"cppStandard": "c++17",
|
||||
"intelliSenseMode": "windows-msvc-x64"
|
||||
}
|
||||
],
|
||||
"version": 4
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"files.associations": {
|
||||
"*.py": "python",
|
||||
"algorithm": "cpp",
|
||||
"array": "cpp",
|
||||
"atomic": "cpp",
|
||||
"bit": "cpp",
|
||||
"cctype": "cpp",
|
||||
"charconv": "cpp",
|
||||
"chrono": "cpp",
|
||||
"clocale": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"exception": "cpp",
|
||||
"filesystem": "cpp",
|
||||
"format": "cpp",
|
||||
"forward_list": "cpp",
|
||||
"functional": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iomanip": "cpp",
|
||||
"ios": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"istream": "cpp",
|
||||
"iterator": "cpp",
|
||||
"limits": "cpp",
|
||||
"list": "cpp",
|
||||
"locale": "cpp",
|
||||
"memory": "cpp",
|
||||
"mutex": "cpp",
|
||||
"new": "cpp",
|
||||
"optional": "cpp",
|
||||
"ostream": "cpp",
|
||||
"ratio": "cpp",
|
||||
"sstream": "cpp",
|
||||
"stdexcept": "cpp",
|
||||
"stop_token": "cpp",
|
||||
"streambuf": "cpp",
|
||||
"string": "cpp",
|
||||
"system_error": "cpp",
|
||||
"thread": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeindex": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"unordered_set": "cpp",
|
||||
"utility": "cpp",
|
||||
"vector": "cpp",
|
||||
"xfacet": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xiosbase": "cpp",
|
||||
"xlocale": "cpp",
|
||||
"xlocbuf": "cpp",
|
||||
"xlocinfo": "cpp",
|
||||
"xlocmes": "cpp",
|
||||
"xlocmon": "cpp",
|
||||
"xlocnum": "cpp",
|
||||
"xloctime": "cpp",
|
||||
"xmemory": "cpp",
|
||||
"xstring": "cpp",
|
||||
"xtr1common": "cpp",
|
||||
"xutility": "cpp"
|
||||
}
|
||||
}
|
58
README.md
58
README.md
|
@ -1,3 +1,59 @@
|
|||
# Tree-Structurer
|
||||
|
||||
Gives you an overview of your Project structure
|
||||
This is a Python package designed to display directory structures in a tree-like format. This tool provides an easy-to-read overview of the directory hierarchy, making it easier for developers to navigate complex project structures.
|
||||
|
||||
#### Installation
|
||||
|
||||
To install `tree_structurer`, you can use pip:
|
||||
|
||||
```bash
|
||||
pip install pip install git+https://gitea.fabelous.app/Fabel/Tree-Structurer.git
|
||||
|
||||
```
|
||||
|
||||
#### Usage
|
||||
|
||||
You can run `tree_structurer` from the command line. By default, it will analyze the current directory and print its structure in a tree-like format. You can also specify a path to another directory.
|
||||
|
||||
To display the directory structure of the current directory:
|
||||
|
||||
```bash
|
||||
python -m tree_structurer
|
||||
```
|
||||
|
||||
To display the directory structure of a specific directory:
|
||||
|
||||
```bash
|
||||
python -m tree_structurer /path/to/directory
|
||||
```
|
||||
|
||||
#### Options
|
||||
|
||||
- `-v` or `--verbose`: Show more detailed output, including information about the path being analyzed.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
python -m tree_structurer /path/to/directory -v
|
||||
```
|
||||
|
||||
#### Important Files and Folders Ignored
|
||||
|
||||
By default, `tree_structurer` ignores files and folders that start with `_` or `.`. However, it does include the following exceptions:
|
||||
|
||||
- `__init__.py` and `__main__.py` files: These are considered important and will be included in the output.
|
||||
- Special folders like `build`, `.git`, `node_modules`, etc.: These are also ignored to keep the output focused on the essential parts of the directory structure.
|
||||
|
||||
#### Example Output
|
||||
|
||||
```
|
||||
dir Structure for /path/to/directory:
|
||||
/path/to/directory
|
||||
├── main.py
|
||||
├── module1
|
||||
│ ├── __init__.py
|
||||
│ └── submodule1.py
|
||||
└── utils
|
||||
├── helper.py
|
||||
└── constants.py
|
||||
```
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
from tree_structurer import create_tree_structurer, get_structure
|
||||
|
||||
create_tree_structurer()
|
||||
for file in get_structure():
|
||||
print(file)
|
|
@ -0,0 +1,27 @@
|
|||
from setuptools import setup, Extension
|
||||
import platform
|
||||
|
||||
extra_compile_args = []
|
||||
extra_link_args = []
|
||||
|
||||
# Set C++17 flag based on the compiler
|
||||
if platform.system() == "Windows":
|
||||
extra_compile_args.append('/std:c++17')
|
||||
else:
|
||||
extra_compile_args.append('-std=c++17')
|
||||
|
||||
tree_structurer_module = Extension(
|
||||
'tree_structurer._tree_structurer',
|
||||
sources=['tree_structurer/cpp/bindings.cpp', 'tree_structurer/cpp/tree_structurer.cpp'],
|
||||
include_dirs=['tree_structurer/cpp'],
|
||||
extra_compile_args=extra_compile_args,
|
||||
extra_link_args=extra_link_args,
|
||||
)
|
||||
|
||||
setup(
|
||||
name='tree_structurer',
|
||||
version='0.0.5',
|
||||
description='A module for analyzing directory structures',
|
||||
ext_modules=[tree_structurer_module],
|
||||
packages=['tree_structurer'],
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
from tree_structurer._tree_structurer import create_tree_structurer, get_structure
|
|
@ -0,0 +1,28 @@
|
|||
import os
|
||||
import sys
|
||||
import argparse
|
||||
from tree_structurer import create_tree_structurer, get_structure
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Display directory structure in a tree-like format')
|
||||
parser.add_argument('path', nargs='?', default=os.getcwd(),
|
||||
help='Path to analyze (default: current directory)')
|
||||
parser.add_argument('-v', '--verbose', action='store_true',
|
||||
help='Show more detailed output')
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.verbose:
|
||||
print(f"Analyzing directory: {args.path}")
|
||||
create_tree_structurer()
|
||||
# Get and print structure directly
|
||||
structure = get_structure(args.path)
|
||||
for line in structure:
|
||||
print(line)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,72 @@
|
|||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#include "tree_structurer.hpp"
|
||||
|
||||
static PyObject* TreeStructurerError;
|
||||
static TreeStructurer* g_tree_structurer = nullptr;
|
||||
|
||||
static PyObject* create_tree_structurer(PyObject* self, PyObject* args) {
|
||||
if (g_tree_structurer != nullptr) {
|
||||
delete g_tree_structurer;
|
||||
}
|
||||
g_tree_structurer = new TreeStructurer();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject* get_structure(PyObject* self, PyObject* args) {
|
||||
const char* path = nullptr;
|
||||
if (!PyArg_ParseTuple(args, "|s", &path)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (g_tree_structurer == nullptr) {
|
||||
PyErr_SetString(TreeStructurerError, "TreeStructurer not initialized");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
try {
|
||||
std::string path_str = path ? path : "";
|
||||
std::vector<std::string> structure = g_tree_structurer->get_directory_structure(path_str);
|
||||
PyObject* list = PyList_New(structure.size());
|
||||
for (size_t i = 0; i < structure.size(); i++) {
|
||||
PyList_SET_ITEM(list, i, PyUnicode_FromString(structure[i].c_str()));
|
||||
}
|
||||
return list;
|
||||
} catch (const std::exception& e) {
|
||||
PyErr_SetString(TreeStructurerError, e.what());
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static PyMethodDef TreeStructurerMethods[] = {
|
||||
{"create_tree_structurer", create_tree_structurer, METH_NOARGS,
|
||||
"Create a new TreeStructurer instance"},
|
||||
{"get_structure", get_structure, METH_VARARGS,
|
||||
"Get the directory structure for the given path"},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
static struct PyModuleDef tree_structurer_module = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"_tree_structurer", // Changed module name to match Python import
|
||||
"Module for analyzing directory structures",
|
||||
-1,
|
||||
TreeStructurerMethods
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC PyInit__tree_structurer(void) { // Changed function name to match module name
|
||||
PyObject* m = PyModule_Create(&tree_structurer_module);
|
||||
if (m == NULL)
|
||||
return NULL;
|
||||
|
||||
TreeStructurerError = PyErr_NewException("tree_structurer.error", NULL, NULL);
|
||||
Py_XINCREF(TreeStructurerError);
|
||||
if (PyModule_AddObject(m, "error", TreeStructurerError) < 0) {
|
||||
Py_XDECREF(TreeStructurerError);
|
||||
Py_CLEAR(TreeStructurerError);
|
||||
Py_DECREF(m);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
#include "tree_structurer.hpp"
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
bool TreeStructurer::should_ignore_dir(const std::string& dirname) {
|
||||
static const std::vector<std::string> ignore_list = {
|
||||
"build", "venv", "myenv", "dist", "node_modules", "CMakeFiles",
|
||||
".git", ".idea", ".vscode", "__pycache__", "**pycache**"
|
||||
};
|
||||
|
||||
// Check for __main__.py or __init__.py files
|
||||
if (!dirname.empty() && (dirname == "__main__.py" || dirname == "__init__.py")) {
|
||||
return false; // Do not ignore these files
|
||||
}
|
||||
|
||||
if (std::find(ignore_list.begin(), ignore_list.end(), dirname) != ignore_list.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!dirname.empty()) {
|
||||
if (dirname[0] == '.' || dirname[0] == '_') {
|
||||
return true;
|
||||
}
|
||||
if (dirname.find("__") == 0 && dirname.find("__", 2) != std::string::npos) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TreeStructurer::should_ignore_file(const std::string& filename) {
|
||||
static const std::vector<std::string> ignore_extensions = {
|
||||
".pyc", ".pyo", ".pyd", ".so", ".dll", ".dylib",
|
||||
".o", ".obj", ".a", ".lib"
|
||||
};
|
||||
|
||||
// Check for __main__.py or __init__.py files
|
||||
if (!filename.empty() && (filename == "__main__.py" || filename == "__init__.py")) {
|
||||
return false; // Do not ignore these files
|
||||
}
|
||||
|
||||
if (!filename.empty() && (filename[0] == '.' || filename[0] == '_')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fs::path path(filename);
|
||||
std::string ext = path.extension().string();
|
||||
if (std::find(ignore_extensions.begin(), ignore_extensions.end(), ext) != ignore_extensions.end()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the missing get_relative_path implementation
|
||||
std::string TreeStructurer::get_relative_path(const fs::path& path, const fs::path& base) {
|
||||
fs::path rel = fs::relative(path, base);
|
||||
return rel.string();
|
||||
}
|
||||
|
||||
std::vector<fs::path> TreeStructurer::get_filtered_paths(const fs::path& start) {
|
||||
std::vector<fs::path> paths;
|
||||
fs::directory_options options = fs::directory_options::skip_permission_denied;
|
||||
|
||||
try {
|
||||
if (fs::exists(start) && fs::is_directory(start)) {
|
||||
paths.push_back(start);
|
||||
|
||||
for (const auto& entry : fs::recursive_directory_iterator(start, options)) {
|
||||
const auto& path = entry.path();
|
||||
|
||||
bool should_skip = false;
|
||||
for (const auto& component : path) {
|
||||
if (should_ignore_dir(component.string())) {
|
||||
should_skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (should_skip) continue;
|
||||
|
||||
if (entry.is_directory()) {
|
||||
if (!should_ignore_dir(path.filename().string())) {
|
||||
paths.push_back(path);
|
||||
}
|
||||
} else {
|
||||
if (!should_ignore_file(path.filename().string())) {
|
||||
paths.push_back(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
throw std::runtime_error("Error accessing path: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
std::sort(paths.begin(), paths.end());
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::vector<std::string> TreeStructurer::get_directory_structure(const std::string& startpath) {
|
||||
std::vector<std::string> result;
|
||||
|
||||
// Normalize the input path by removing ./ or .\ prefix if present
|
||||
std::string normalized_path = startpath;
|
||||
if (startpath.substr(0, 2) == ".\\" || startpath.substr(0, 2) == "./") {
|
||||
normalized_path = startpath.substr(2);
|
||||
}
|
||||
|
||||
fs::path start = normalized_path.empty() ? fs::current_path() : fs::path(normalized_path);
|
||||
|
||||
try {
|
||||
auto paths = get_filtered_paths(start);
|
||||
if (paths.empty()) return result;
|
||||
|
||||
// Don't add the start directory name to the output
|
||||
// Remove the following lines:
|
||||
// if (start != fs::current_path()) {
|
||||
// result.push_back(start.filename().string());
|
||||
// }
|
||||
|
||||
std::vector<bool> is_last_at_level(256, false);
|
||||
|
||||
for (size_t i = 1; i < paths.size(); ++i) {
|
||||
const auto& path = paths[i];
|
||||
std::string rel_path = get_relative_path(path, start);
|
||||
|
||||
int level = std::count(rel_path.begin(), rel_path.end(), fs::path::preferred_separator);
|
||||
|
||||
bool is_last = true;
|
||||
for (size_t j = i + 1; j < paths.size(); ++j) {
|
||||
std::string next_rel_path = get_relative_path(paths[j], start);
|
||||
int next_level = std::count(next_rel_path.begin(), next_rel_path.end(), fs::path::preferred_separator);
|
||||
if (next_level == level) {
|
||||
is_last = false;
|
||||
break;
|
||||
}
|
||||
if (next_level < level) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
is_last_at_level[level] = is_last;
|
||||
|
||||
std::string line;
|
||||
for (int j = 0; j < level; ++j) {
|
||||
if (j == level - 1) {
|
||||
line += is_last ? "└── " : "├── ";
|
||||
} else {
|
||||
line += is_last_at_level[j] ? " " : "│ ";
|
||||
}
|
||||
}
|
||||
|
||||
line += path.filename().string();
|
||||
if (fs::is_directory(path)) {
|
||||
line += "/";
|
||||
}
|
||||
result.push_back(line);
|
||||
}
|
||||
} catch (const fs::filesystem_error& e) {
|
||||
throw std::runtime_error("Failed to access directory: " + std::string(e.what()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
class TreeStructurer {
|
||||
public:
|
||||
TreeStructurer() = default;
|
||||
std::vector<std::string> get_directory_structure(const std::string& startpath = std::filesystem::current_path().string());
|
||||
|
||||
private:
|
||||
bool should_ignore_dir(const std::string& dirname);
|
||||
bool should_ignore_file(const std::string& filename);
|
||||
std::string create_indent(int level);
|
||||
std::string get_relative_path(const std::filesystem::path& path, const std::filesystem::path& base);
|
||||
std::vector<std::filesystem::path> get_filtered_paths(const std::filesystem::path& start);
|
||||
};
|
Loading…
Reference in New Issue