From 1eb338789c392dfa0d4a3c45bc8a54c7fd988398 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 5 Feb 2025 22:39:10 +0100 Subject: [PATCH 1/6] added first creation scripts but currently this is not really well working. --- src/prodir/cpp/bindings.cpp | 63 ++++++++-- src/prodir/cpp/tree_structurer.cpp | 187 +++++++++++++++++++++++++++-- src/prodir/cpp/tree_structurer.hpp | 32 ++++- 3 files changed, 261 insertions(+), 21 deletions(-) diff --git a/src/prodir/cpp/bindings.cpp b/src/prodir/cpp/bindings.cpp index 6aa031a..625c630 100644 --- a/src/prodir/cpp/bindings.cpp +++ b/src/prodir/cpp/bindings.cpp @@ -18,12 +18,11 @@ static PyObject* get_structure(PyObject* self, PyObject* args) { 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 structure = g_tree_structurer->get_directory_structure(path_str); @@ -38,27 +37,77 @@ static PyObject* get_structure(PyObject* self, PyObject* args) { } } +static PyObject* create_structure_from_file(PyObject* self, PyObject* args) { + const char* structure_file = nullptr; + const char* target_path = nullptr; + + if (!PyArg_ParseTuple(args, "s|s", &structure_file, &target_path)) { + return NULL; + } + + if (g_tree_structurer == nullptr) { + PyErr_SetString(TreeStructurerError, "TreeStructurer not initialized"); + return NULL; + } + + try { + std::string target_path_str = target_path ? target_path : ""; + g_tree_structurer->create_structure_from_file(structure_file, target_path_str); + Py_RETURN_NONE; + } catch (const std::exception& e) { + PyErr_SetString(TreeStructurerError, e.what()); + return NULL; + } +} + +static PyObject* create_structure_from_string(PyObject* self, PyObject* args) { + const char* structure_str = nullptr; + const char* target_path = nullptr; + + if (!PyArg_ParseTuple(args, "s|s", &structure_str, &target_path)) { + return NULL; + } + + if (g_tree_structurer == nullptr) { + PyErr_SetString(TreeStructurerError, "TreeStructurer not initialized"); + return NULL; + } + + try { + std::string target_path_str = target_path ? target_path : ""; + g_tree_structurer->create_structure_from_string(structure_str, target_path_str); + Py_RETURN_NONE; + } catch (const std::exception& e) { + PyErr_SetString(TreeStructurerError, e.what()); + return NULL; + } +} + static PyMethodDef TreeStructurerMethods[] = { - {"create_tree_structurer", create_tree_structurer, METH_NOARGS, + {"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"}, + {"create_structure_from_file", create_structure_from_file, METH_VARARGS, + "Create directory structure from a file containing the structure description"}, + {"create_structure_from_string", create_structure_from_string, METH_VARARGS, + "Create directory structure from a string containing the structure description"}, {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", + "Module for analyzing and creating directory structures", -1, TreeStructurerMethods }; -PyMODINIT_FUNC PyInit__tree_structurer(void) { // Changed function name to match module name +PyMODINIT_FUNC PyInit__tree_structurer(void) { 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) { @@ -67,6 +116,6 @@ PyMODINIT_FUNC PyInit__tree_structurer(void) { // Changed function name to matc Py_DECREF(m); return NULL; } - + return m; } \ No newline at end of file diff --git a/src/prodir/cpp/tree_structurer.cpp b/src/prodir/cpp/tree_structurer.cpp index 2ef3be2..67dffce 100644 --- a/src/prodir/cpp/tree_structurer.cpp +++ b/src/prodir/cpp/tree_structurer.cpp @@ -1,6 +1,9 @@ #include "tree_structurer.hpp" #include #include +#include +#include +#include namespace fs = std::filesystem; @@ -10,22 +13,21 @@ bool TreeStructurer::should_ignore_dir(const std::string& dirname) { ".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 + return false; } if (std::find(ignore_list.begin(), ignore_list.end(), dirname) != ignore_list.end()) { - return true; - } + 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 true; + } } return false; @@ -37,10 +39,9 @@ bool TreeStructurer::should_ignore_file(const std::string& filename) { ".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 - } + return false; +} if (!filename.empty() && (filename[0] == '.' || filename[0] == '_')) { return true; @@ -55,7 +56,6 @@ bool TreeStructurer::should_ignore_file(const std::string& filename) { 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(); @@ -76,7 +76,6 @@ std::vector TreeStructurer::get_filtered_paths(const fs::path& start) paths.push_back(start); - // Check if directory is empty bool is_empty = fs::directory_iterator(start) == fs::directory_iterator(); if (is_empty) { throw std::runtime_error("Directory is empty: " + start.string()); @@ -115,7 +114,6 @@ std::vector TreeStructurer::get_filtered_paths(const fs::path& start) std::vector TreeStructurer::get_directory_structure(const std::string& startpath) { std::vector 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); @@ -171,4 +169,169 @@ std::vector TreeStructurer::get_directory_structure(const std::stri } return result; +} + +void TreeStructurer::create_structure_from_file(const std::string& filepath, const std::string& target_path) { + std::vector lines = read_structure_file(filepath); + validate_structure(lines); + TreeNode root = build_tree_from_lines(lines); + create_node(root, target_path); +} + +void TreeStructurer::create_structure_from_string(const std::string& structure, const std::string& target_path) { + std::istringstream iss(structure); + std::vector lines; + std::string line; + + while (std::getline(iss, line)) { + if (!line.empty()) { + lines.push_back(line); + } + } + + validate_structure(lines); + TreeNode root = build_tree_from_lines(lines); + create_node(root, target_path); +} + +void TreeStructurer::validate_structure(const std::vector& lines) { + if (lines.empty()) { + throw std::runtime_error("Empty structure provided"); + } + + int prev_indent = -1; + for (const auto& line : lines) { + int current_indent = get_indent_level(line); + if (current_indent > prev_indent + 1) { + throw std::runtime_error("Invalid indentation level in structure"); + } + prev_indent = current_indent; + } +} + +TreeStructurer::TreeNode TreeStructurer::build_tree_from_lines(const std::vector& lines) { + TreeNode root{"root", true, {}}; + std::vector stack{&root}; + size_t prev_indent = 0; + + for (const auto& line : lines) { + size_t current_indent = get_indent_level(line); + + while (current_indent <= prev_indent && stack.size() > 1) { + stack.pop_back(); + prev_indent--; + } + + TreeNode new_node = parse_structure_line(line, current_indent); + stack.back()->children.push_back(new_node); + + if (!new_node.is_file) { + stack.push_back(&(stack.back()->children.back())); + } + + prev_indent = current_indent; + } + + return root; +} + +void TreeStructurer::create_node(const TreeNode& node, const std::filesystem::path& current_path) { + std::filesystem::path new_path = current_path / node.name; + + if (node.is_file) { + create_file(new_path); + } else { + create_directory(new_path); + for (const auto& child : node.children) { + create_node(child, new_path); + } + } +} + +void TreeStructurer::create_directory(const std::filesystem::path& path) { + if (!std::filesystem::exists(path)) { + if (!std::filesystem::create_directory(path)) { + throw std::runtime_error("Failed to create directory: " + path.string()); + } + } +} + +void TreeStructurer::create_file(const std::filesystem::path& path) { + if (!std::filesystem::exists(path)) { + std::ofstream file(path); + if (!file.is_open()) { + throw std::runtime_error("Failed to create file: " + path.string()); + } + } +} + +TreeStructurer::TreeNode TreeStructurer::parse_structure_line(const std::string& line, size_t indent_level) { + size_t name_start = 0; + for (size_t i = 0; i < line.length(); ++i) { + if (line[i] != ' ' && + static_cast(line[i]) != INDENT_MARKER_PIPE && + static_cast(line[i]) != INDENT_MARKER_DASH && + static_cast(line[i]) != INDENT_MARKER_END && + line[i] != '-') { + name_start = i; + break; + } + } + + std::string name = line.substr(name_start); + + name.erase(0, name.find_first_not_of(" ")); + name.erase(name.find_last_not_of(" ") + 1); + + bool is_file = !is_directory_marker(name); + + if (!is_file && name.back() == '/') { + name.pop_back(); + } + + return TreeNode{name, is_file, {}}; +} + +size_t TreeStructurer::get_indent_level(const std::string& line) { + size_t level = 0; + for (size_t i = 0; i < line.length(); ++i) { + if (line[i] == ' ') { + continue; + } + if (line[i] == '-' || + static_cast(line[i]) == INDENT_MARKER_PIPE || + static_cast(line[i]) == INDENT_MARKER_DASH || + static_cast(line[i]) == INDENT_MARKER_END) { + level++; + } else { + break; + } + } + return level / 4; +} + +bool TreeStructurer::is_directory_marker(const std::string& line) { + return !line.empty() && line.back() == DIRECTORY_MARKER; +} + +std::string TreeStructurer::create_indent(size_t level) { + return std::string(level * 4, ' '); +} + +std::vector TreeStructurer::read_structure_file(const std::string& filepath) { + std::vector lines; + std::ifstream file(filepath); + + if (!file.is_open()) { + throw std::runtime_error("Failed to open file: " + filepath); + } + + std::string line; + while (std::getline(file, line)) { + if (!line.empty()) { + lines.push_back(line); + } + } + + return lines; } \ No newline at end of file diff --git a/src/prodir/cpp/tree_structurer.hpp b/src/prodir/cpp/tree_structurer.hpp index 06c51be..ddcdee0 100644 --- a/src/prodir/cpp/tree_structurer.hpp +++ b/src/prodir/cpp/tree_structurer.hpp @@ -2,16 +2,44 @@ #include #include #include +#include +#include +#include class TreeStructurer { public: TreeStructurer() = default; + std::vector get_directory_structure(const std::string& startpath = std::filesystem::current_path().string()); + + void create_structure_from_file(const std::string& filepath, const std::string& target_path = std::filesystem::current_path().string()); + void create_structure_from_string(const std::string& structure, const std::string& target_path = std::filesystem::current_path().string()); private: + struct TreeNode { + std::string name; + bool is_file; + std::vector children; + }; + bool should_ignore_dir(const std::string& dirname); bool should_ignore_file(const std::string& filename); - std::string create_indent(int level); + std::string create_indent(size_t level); std::string get_relative_path(const std::filesystem::path& path, const std::filesystem::path& base); std::vector get_filtered_paths(const std::filesystem::path& start); -}; \ No newline at end of file + + TreeNode parse_structure_line(const std::string& line, size_t indent_level); + TreeNode build_tree_from_lines(const std::vector& lines); + void create_node(const TreeNode& node, const std::filesystem::path& current_path); + size_t get_indent_level(const std::string& line); + bool is_directory_marker(const std::string& line); + void create_directory(const std::filesystem::path& path); + void create_file(const std::filesystem::path& path); + std::vector read_structure_file(const std::string& filepath); + void validate_structure(const std::vector& lines); + + static const unsigned char DIRECTORY_MARKER = '/'; + static const unsigned char INDENT_MARKER_PIPE = '|'; + static const unsigned char INDENT_MARKER_DASH = '+'; + static const unsigned char INDENT_MARKER_END = '\\'; +}; From 055fa5bcc7576b72507a995def65573ef29454dd Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Wed, 5 Feb 2025 22:39:33 +0100 Subject: [PATCH 2/6] Python files updated for creation bindings --- src/prodir/__init__.py | 14 ++++- src/prodir/__main__.py | 118 +++++++++++++++++++++++++++++++++++------ 2 files changed, 114 insertions(+), 18 deletions(-) diff --git a/src/prodir/__init__.py b/src/prodir/__init__.py index 520a304..defd188 100644 --- a/src/prodir/__init__.py +++ b/src/prodir/__init__.py @@ -1 +1,13 @@ -from prodir._tree_structurer import create_tree_structurer, get_structure \ No newline at end of file +from prodir._tree_structurer import ( + create_tree_structurer, + get_structure, + create_structure_from_file, + create_structure_from_string +) + +__all__ = [ + 'create_tree_structurer', + 'get_structure', + 'create_structure_from_file', + 'create_structure_from_string' +] \ No newline at end of file diff --git a/src/prodir/__main__.py b/src/prodir/__main__.py index 1967b82..ecb4aa6 100644 --- a/src/prodir/__main__.py +++ b/src/prodir/__main__.py @@ -1,36 +1,120 @@ import os import sys import argparse -from prodir import create_tree_structurer, get_structure +from prodir import ( + create_tree_structurer, + get_structure, + create_structure_from_file, + create_structure_from_string +) 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') + parser = argparse.ArgumentParser( + description='Directory structure tool: Display and create directory structures' + ) + + # Create subparsers for different commands + subparsers = parser.add_subparsers(dest='command', help='Available commands') + + # Display command + display_parser = subparsers.add_parser('display', help='Display directory structure') + display_parser.add_argument( + 'path', + nargs='?', + default=os.getcwd(), + help='Path to analyze (default: current directory)' + ) + display_parser.add_argument( + '-v', '--verbose', + action='store_true', + help='Show more detailed output' + ) + + # Create command + create_parser = subparsers.add_parser('create', help='Create directory structure') + create_parser.add_argument( + '-f', '--file', + help='File containing the directory structure' + ) + create_parser.add_argument( + '-s', '--string', + help='String containing the directory structure' + ) + create_parser.add_argument( + '-o', '--output', + default=os.getcwd(), + help='Output directory (default: current directory)' + ) + create_parser.add_argument( + '-v', '--verbose', + action='store_true', + help='Show more detailed output' + ) + args = parser.parse_args() - + + # If no command is specified, default to display + if not args.command: + args.command = 'display' + args.path = os.getcwd() + args.verbose = False + try: - if args.verbose: - print(f"Analyzing directory: {args.path}") - create_tree_structurer() - structure = get_structure(args.path) - - for line in structure: - print(line) + + if args.command == 'display': + if args.verbose: + print(f"Analyzing directory: {args.path}") + structure = get_structure(args.path) + for line in structure: + print(line) + + elif args.command == 'create': + if not args.file and not args.string: + parser.error("Either --file or --string must be specified") + + if args.file and args.string: + parser.error("Cannot specify both --file and --string") + + if args.verbose: + print(f"Creating directory structure in: {args.output}") + + if args.file: + if args.verbose: + print(f"Using structure from file: {args.file}") + create_structure_from_file(args.file, args.output) + + elif args.string: + if args.verbose: + print("Creating structure from provided string") + create_structure_from_string(args.string, args.output) + + if args.verbose: + print("Structure created successfully") + print("\nResulting structure:") + structure = get_structure(args.output) + for line in structure: + print(line) + except Exception as e: error_msg = str(e) if "Directory does not exist" in error_msg: - print(f"Error: The specified directory does not exist: {args.path}", file=sys.stderr) + print(f"Error: The specified directory does not exist: {args.path if args.command == 'display' else args.output}", + file=sys.stderr) elif "Directory is empty" in error_msg: - print(f"Error: The specified directory is empty: {args.path}", file=sys.stderr) + print(f"Error: The specified directory is empty: {args.path if args.command == 'display' else args.output}", + file=sys.stderr) elif "Path is not a directory" in error_msg: - print(f"Error: The specified path is not a directory: {args.path}", file=sys.stderr) + print(f"Error: The specified path is not a directory: {args.path if args.command == 'display' else args.output}", + file=sys.stderr) + elif "Unable to open structure file" in error_msg: + print(f"Error: Unable to open structure file: {args.file}", file=sys.stderr) + elif "Invalid structure format" in error_msg: + print(f"Error: Invalid structure format in {'file' if args.file else 'string'}", file=sys.stderr) else: print(f"Error: {error_msg}", file=sys.stderr) sys.exit(1) + if __name__ == "__main__": main() \ No newline at end of file From 16f0e89e918a445e55a856b0d85e6383e603e01a Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Thu, 6 Feb 2025 17:53:30 +0100 Subject: [PATCH 3/6] folders are now crearted just wrongly, so the subfolders are not yet detected correctly --- src/prodir/cpp/bindings.cpp | 29 +++++++----- src/prodir/cpp/tree_structurer.cpp | 74 +++++++++++++++++------------- src/prodir/cpp/tree_structurer.hpp | 11 +++-- 3 files changed, 64 insertions(+), 50 deletions(-) diff --git a/src/prodir/cpp/bindings.cpp b/src/prodir/cpp/bindings.cpp index 625c630..141b82e 100644 --- a/src/prodir/cpp/bindings.cpp +++ b/src/prodir/cpp/bindings.cpp @@ -27,9 +27,17 @@ static PyObject* get_structure(PyObject* self, PyObject* args) { std::string path_str = path ? path : ""; std::vector structure = g_tree_structurer->get_directory_structure(path_str); PyObject* list = PyList_New(structure.size()); + if (!list) { + return NULL; + } for (size_t i = 0; i < structure.size(); i++) { - PyList_SET_ITEM(list, i, PyUnicode_FromString(structure[i].c_str())); - } + PyObject* str = PyUnicode_FromString(structure[i].c_str()); + if (!str) { + Py_DECREF(list); + return NULL; + } + PyList_SET_ITEM(list, i, str); + } return list; } catch (const std::exception& e) { PyErr_SetString(TreeStructurerError, e.what()); @@ -39,8 +47,7 @@ static PyObject* get_structure(PyObject* self, PyObject* args) { static PyObject* create_structure_from_file(PyObject* self, PyObject* args) { const char* structure_file = nullptr; - const char* target_path = nullptr; - + const char* target_path = "."; // Default to current directory if (!PyArg_ParseTuple(args, "s|s", &structure_file, &target_path)) { return NULL; } @@ -51,8 +58,7 @@ static PyObject* create_structure_from_file(PyObject* self, PyObject* args) { } try { - std::string target_path_str = target_path ? target_path : ""; - g_tree_structurer->create_structure_from_file(structure_file, target_path_str); + g_tree_structurer->create_structure_from_file(structure_file, target_path); Py_RETURN_NONE; } catch (const std::exception& e) { PyErr_SetString(TreeStructurerError, e.what()); @@ -62,25 +68,24 @@ static PyObject* create_structure_from_file(PyObject* self, PyObject* args) { static PyObject* create_structure_from_string(PyObject* self, PyObject* args) { const char* structure_str = nullptr; - const char* target_path = nullptr; + const char* target_path = "."; // Default to current directory if (!PyArg_ParseTuple(args, "s|s", &structure_str, &target_path)) { return NULL; } - + if (g_tree_structurer == nullptr) { PyErr_SetString(TreeStructurerError, "TreeStructurer not initialized"); return NULL; } try { - std::string target_path_str = target_path ? target_path : ""; - g_tree_structurer->create_structure_from_string(structure_str, target_path_str); + g_tree_structurer->create_structure_from_string(structure_str, target_path); Py_RETURN_NONE; } catch (const std::exception& e) { PyErr_SetString(TreeStructurerError, e.what()); return NULL; - } +} } static PyMethodDef TreeStructurerMethods[] = { @@ -97,7 +102,7 @@ static PyMethodDef TreeStructurerMethods[] = { static struct PyModuleDef tree_structurer_module = { PyModuleDef_HEAD_INIT, - "_tree_structurer", // Changed module name to match Python import + "_tree_structurer", "Module for analyzing and creating directory structures", -1, TreeStructurerMethods diff --git a/src/prodir/cpp/tree_structurer.cpp b/src/prodir/cpp/tree_structurer.cpp index 67dffce..bf8f097 100644 --- a/src/prodir/cpp/tree_structurer.cpp +++ b/src/prodir/cpp/tree_structurer.cpp @@ -209,32 +209,57 @@ void TreeStructurer::validate_structure(const std::vector& lines) { } } + +size_t TreeStructurer::get_indent_level(const std::string& line) { + size_t level = 0; + for (size_t i = 0; i < line.length(); ++i) { + if (line[i] == ' ') { + continue; + } + if (line[i] == '-' || + static_cast(line[i]) == INDENT_MARKER_PIPE || + static_cast(line[i]) == INDENT_MARKER_DASH || + static_cast(line[i]) == INDENT_MARKER_END) { + level++; + } else { + break; + } + } + return level / 4; +} + + TreeStructurer::TreeNode TreeStructurer::build_tree_from_lines(const std::vector& lines) { - TreeNode root{"root", true, {}}; + TreeNode root{"root", false, {}}; std::vector stack{&root}; size_t prev_indent = 0; - + for (const auto& line : lines) { + if (line.empty()) continue; + size_t current_indent = get_indent_level(line); - + while (current_indent <= prev_indent && stack.size() > 1) { stack.pop_back(); prev_indent--; } - + TreeNode new_node = parse_structure_line(line, current_indent); - stack.back()->children.push_back(new_node); - - if (!new_node.is_file) { - stack.push_back(&(stack.back()->children.back())); + if (!new_node.name.empty()) { + stack.back()->children.push_back(new_node); + + if (!new_node.is_file) { + stack.push_back(&(stack.back()->children.back())); + } + + prev_indent = current_indent; } - - prev_indent = current_indent; } - + return root; } + void TreeStructurer::create_node(const TreeNode& node, const std::filesystem::path& current_path) { std::filesystem::path new_path = current_path / node.name; @@ -272,44 +297,27 @@ TreeStructurer::TreeNode TreeStructurer::parse_structure_line(const std::string& static_cast(line[i]) != INDENT_MARKER_PIPE && static_cast(line[i]) != INDENT_MARKER_DASH && static_cast(line[i]) != INDENT_MARKER_END && - line[i] != '-') { + line[i] != '-' && + line[i] != '├' && + line[i] != '└') { name_start = i; break; } } std::string name = line.substr(name_start); - name.erase(0, name.find_first_not_of(" ")); name.erase(name.find_last_not_of(" ") + 1); - bool is_file = !is_directory_marker(name); + bool is_file = name.empty() || name.back() != '/'; - if (!is_file && name.back() == '/') { + if (!is_file) { name.pop_back(); } return TreeNode{name, is_file, {}}; } -size_t TreeStructurer::get_indent_level(const std::string& line) { - size_t level = 0; - for (size_t i = 0; i < line.length(); ++i) { - if (line[i] == ' ') { - continue; - } - if (line[i] == '-' || - static_cast(line[i]) == INDENT_MARKER_PIPE || - static_cast(line[i]) == INDENT_MARKER_DASH || - static_cast(line[i]) == INDENT_MARKER_END) { - level++; - } else { - break; - } - } - return level / 4; -} - bool TreeStructurer::is_directory_marker(const std::string& line) { return !line.empty() && line.back() == DIRECTORY_MARKER; } diff --git a/src/prodir/cpp/tree_structurer.hpp b/src/prodir/cpp/tree_structurer.hpp index ddcdee0..f9c020e 100644 --- a/src/prodir/cpp/tree_structurer.hpp +++ b/src/prodir/cpp/tree_structurer.hpp @@ -17,9 +17,9 @@ public: private: struct TreeNode { - std::string name; - bool is_file; - std::vector children; + std::string name; + bool is_file; + std::vector children; }; bool should_ignore_dir(const std::string& dirname); @@ -27,11 +27,11 @@ private: std::string create_indent(size_t level); std::string get_relative_path(const std::filesystem::path& path, const std::filesystem::path& base); std::vector get_filtered_paths(const std::filesystem::path& start); - + size_t get_indent_level(const std::string& line); TreeNode parse_structure_line(const std::string& line, size_t indent_level); TreeNode build_tree_from_lines(const std::vector& lines); + void create_node(const TreeNode& node, const std::filesystem::path& current_path); - size_t get_indent_level(const std::string& line); bool is_directory_marker(const std::string& line); void create_directory(const std::filesystem::path& path); void create_file(const std::filesystem::path& path); @@ -42,4 +42,5 @@ private: static const unsigned char INDENT_MARKER_PIPE = '|'; static const unsigned char INDENT_MARKER_DASH = '+'; static const unsigned char INDENT_MARKER_END = '\\'; + }; From 0d01f188e7040c697237399f789d120a329f0d9e Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Fri, 7 Feb 2025 16:30:29 +0100 Subject: [PATCH 4/6] updated getting dir --- src/prodir/cpp/tree_structurer.cpp | 178 +++++++++++++++++++---------- src/prodir/cpp/tree_structurer.hpp | 20 ++-- 2 files changed, 126 insertions(+), 72 deletions(-) diff --git a/src/prodir/cpp/tree_structurer.cpp b/src/prodir/cpp/tree_structurer.cpp index bf8f097..baae534 100644 --- a/src/prodir/cpp/tree_structurer.cpp +++ b/src/prodir/cpp/tree_structurer.cpp @@ -18,16 +18,16 @@ bool TreeStructurer::should_ignore_dir(const std::string& dirname) { } if (std::find(ignore_list.begin(), ignore_list.end(), dirname) != ignore_list.end()) { - return true; - } + 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 true; + } } return false; @@ -41,7 +41,7 @@ bool TreeStructurer::should_ignore_file(const std::string& filename) { if (!filename.empty() && (filename == "__main__.py" || filename == "__init__.py")) { return false; -} + } if (!filename.empty() && (filename[0] == '.' || filename[0] == '_')) { return true; @@ -171,6 +171,10 @@ std::vector TreeStructurer::get_directory_structure(const std::stri return result; } +// +// UPDATED: Instead of processing the lines “on the fly,” we now build the tree and then create the project. +// This also allows the same parsing logic to be reused by create_structure_from_string(). +// void TreeStructurer::create_structure_from_file(const std::string& filepath, const std::string& target_path) { std::vector lines = read_structure_file(filepath); validate_structure(lines); @@ -194,6 +198,9 @@ void TreeStructurer::create_structure_from_string(const std::string& structure, create_node(root, target_path); } +// +// UPDATED: Now using a Unicode‑aware (token‑based) indent calculation. +// void TreeStructurer::validate_structure(const std::vector& lines) { if (lines.empty()) { throw std::runtime_error("Empty structure provided"); @@ -201,7 +208,7 @@ void TreeStructurer::validate_structure(const std::vector& lines) { int prev_indent = -1; for (const auto& line : lines) { - int current_indent = get_indent_level(line); + int current_indent = static_cast(get_indent_level(line)); if (current_indent > prev_indent + 1) { throw std::runtime_error("Invalid indentation level in structure"); } @@ -209,64 +216,110 @@ void TreeStructurer::validate_structure(const std::vector& lines) { } } - +// +// UPDATED: Parse the indent level by looking for 4‑character tokens. +// size_t TreeStructurer::get_indent_level(const std::string& line) { size_t level = 0; - for (size_t i = 0; i < line.length(); ++i) { - if (line[i] == ' ') { - continue; - } - if (line[i] == '-' || - static_cast(line[i]) == INDENT_MARKER_PIPE || - static_cast(line[i]) == INDENT_MARKER_DASH || - static_cast(line[i]) == INDENT_MARKER_END) { - level++; - } else { - break; - } + size_t pos = 0; + // Look for “blank” or vertical bar segments (each 4 characters) + while (pos + 4 <= line.size() && (line.compare(pos, 4, " ") == 0 || line.compare(pos, 4, "│ ") == 0)) { + level++; + pos += 4; } - return level / 4; + // Then look for the branch indicator at this level + if (pos + 4 <= line.size() && (line.compare(pos, 4, "└── ") == 0 || line.compare(pos, 4, "├── ") == 0)) { + level++; + } + return level; } - +// +// UPDATED: Build a tree of nodes from the structure lines using the same token‐based parsing. +// The first (root) line is expected to be a directory (with a trailing '/'). TreeStructurer::TreeNode TreeStructurer::build_tree_from_lines(const std::vector& lines) { - TreeNode root{"root", false, {}}; - std::vector stack{&root}; - size_t prev_indent = 0; + if (lines.empty()) { + throw std::runtime_error("Empty structure provided"); + } - for (const auto& line : lines) { + // Process the first line as the root. + std::string first_line = lines[0]; + size_t pos = 0; + // Skip any indent tokens (if any) + while (pos + 4 <= first_line.size() && (first_line.compare(pos, 4, " ") == 0 || first_line.compare(pos, 4, "│ ") == 0)) { + pos += 4; + } + if (pos + 4 <= first_line.size() && (first_line.compare(pos, 4, "└── ") == 0 || first_line.compare(pos, 4, "├── ") == 0)) { + pos += 4; + } + std::string root_name = first_line.substr(pos); + if (root_name.empty() || root_name.back() != DIRECTORY_MARKER) { + throw std::runtime_error("Root must be a directory (ending with '" + std::string(1, DIRECTORY_MARKER) + "')"); + } + root_name.pop_back(); // Remove trailing '/' + + TreeNode root{root_name, false, {}}; + std::vector stack; + stack.push_back(&root); + + // Process remaining lines. + for (size_t i = 1; i < lines.size(); ++i) { + const std::string& line = lines[i]; if (line.empty()) continue; - - size_t current_indent = get_indent_level(line); - - while (current_indent <= prev_indent && stack.size() > 1) { - stack.pop_back(); - prev_indent--; + size_t pos = 0; + size_t current_level = 0; + // Process any “blank” or vertical indent segments (4 characters each) + while (pos + 4 <= line.size() && (line.compare(pos, 4, " ") == 0 || line.compare(pos, 4, "│ ") == 0)) { + current_level++; + pos += 4; } + // Process the branch token if present + if (pos + 4 <= line.size() && (line.compare(pos, 4, "└── ") == 0 || line.compare(pos, 4, "├── ") == 0)) { + current_level++; + pos += 4; + } + std::string name = line.substr(pos); + if (name.empty()) continue; + bool is_file = true; + if (name.back() == DIRECTORY_MARKER) { + is_file = false; + name.pop_back(); + } + name = sanitize_path(name); + if (name.empty()) continue; - TreeNode new_node = parse_structure_line(line, current_indent); - if (!new_node.name.empty()) { - stack.back()->children.push_back(new_node); - - if (!new_node.is_file) { - stack.push_back(&(stack.back()->children.back())); - } - - prev_indent = current_indent; + // Adjust the stack to the proper level. + while (stack.size() > current_level) { + stack.pop_back(); + } + if (stack.empty()) { + throw std::runtime_error("Invalid indentation structure in the file."); + } + TreeNode new_node{name, is_file, {}}; + stack.back()->children.push_back(new_node); + if (!is_file) { + // For a directory, push the new node onto the stack. + stack.push_back(&stack.back()->children.back()); } } return root; } - void TreeStructurer::create_node(const TreeNode& node, const std::filesystem::path& current_path) { std::filesystem::path new_path = current_path / node.name; if (node.is_file) { + // Create file + std::filesystem::path parent_path = new_path.parent_path(); + if (!std::filesystem::exists(parent_path)) { + std::filesystem::create_directories(parent_path); + } create_file(new_path); } else { + // Create directory create_directory(new_path); + // Recursively create children for (const auto& child : node.children) { create_node(child, new_path); } @@ -275,9 +328,7 @@ void TreeStructurer::create_node(const TreeNode& node, const std::filesystem::pa void TreeStructurer::create_directory(const std::filesystem::path& path) { if (!std::filesystem::exists(path)) { - if (!std::filesystem::create_directory(path)) { - throw std::runtime_error("Failed to create directory: " + path.string()); - } + std::filesystem::create_directories(path); } } @@ -287,30 +338,33 @@ void TreeStructurer::create_file(const std::filesystem::path& path) { if (!file.is_open()) { throw std::runtime_error("Failed to create file: " + path.string()); } + file.close(); } } -TreeStructurer::TreeNode TreeStructurer::parse_structure_line(const std::string& line, size_t indent_level) { - size_t name_start = 0; - for (size_t i = 0; i < line.length(); ++i) { - if (line[i] != ' ' && - static_cast(line[i]) != INDENT_MARKER_PIPE && - static_cast(line[i]) != INDENT_MARKER_DASH && - static_cast(line[i]) != INDENT_MARKER_END && - line[i] != '-' && - line[i] != '├' && - line[i] != '└') { - name_start = i; - break; +std::string TreeStructurer::sanitize_path(const std::string& path) { + std::string result; + for (char c : path) { + // Allow alphanumeric, common punctuation, the directory separator, and our tree tokens. + if (std::isalnum(static_cast(c)) || + c == '.' || c == '_' || c == '-' || c == '/' || + c == TREE_PIPE || c == TREE_BRANCH || c == TREE_CORNER || c == TREE_DASH) { + result += c; } } - + return result; +} + +TreeStructurer::TreeNode TreeStructurer::parse_structure_line(const std::string& line, size_t indent_level) { + size_t name_start = line.find_first_not_of(" │├└─"); + if (name_start == std::string::npos) { + return TreeNode{"", true, {}}; + } + std::string name = line.substr(name_start); - name.erase(0, name.find_first_not_of(" ")); - name.erase(name.find_last_not_of(" ") + 1); - - bool is_file = name.empty() || name.back() != '/'; + name = sanitize_path(name); + bool is_file = !name.empty() && name.back() != DIRECTORY_MARKER; if (!is_file) { name.pop_back(); } @@ -342,4 +396,4 @@ std::vector TreeStructurer::read_structure_file(const std::string& } return lines; -} \ No newline at end of file +} diff --git a/src/prodir/cpp/tree_structurer.hpp b/src/prodir/cpp/tree_structurer.hpp index f9c020e..c9e29c0 100644 --- a/src/prodir/cpp/tree_structurer.hpp +++ b/src/prodir/cpp/tree_structurer.hpp @@ -17,9 +17,9 @@ public: private: struct TreeNode { - std::string name; - bool is_file; - std::vector children; + std::string name; + bool is_file; + std::vector children; }; bool should_ignore_dir(const std::string& dirname); @@ -30,17 +30,17 @@ private: size_t get_indent_level(const std::string& line); TreeNode parse_structure_line(const std::string& line, size_t indent_level); TreeNode build_tree_from_lines(const std::vector& lines); - void create_node(const TreeNode& node, const std::filesystem::path& current_path); bool is_directory_marker(const std::string& line); void create_directory(const std::filesystem::path& path); void create_file(const std::filesystem::path& path); std::vector read_structure_file(const std::string& filepath); void validate_structure(const std::vector& lines); - - static const unsigned char DIRECTORY_MARKER = '/'; - static const unsigned char INDENT_MARKER_PIPE = '|'; - static const unsigned char INDENT_MARKER_DASH = '+'; - static const unsigned char INDENT_MARKER_END = '\\'; + std::string sanitize_path(const std::string& path); -}; + static const char DIRECTORY_MARKER = '/'; + static const char TREE_PIPE = '│'; + static const char TREE_BRANCH = '├'; + static const char TREE_CORNER = '└'; + static const char TREE_DASH = '─'; +}; \ No newline at end of file From 2a2c13bddfb6f0c3fa7fad80ea5ec7be2dccdd8d Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Sat, 8 Feb 2025 18:02:50 +0100 Subject: [PATCH 5/6] debug prints to find out at which indent we are falling, uft 8 error back --- src/prodir/cpp/tree_structurer.cpp | 485 ++++++++++++++--------------- src/prodir/cpp/tree_structurer.hpp | 13 +- 2 files changed, 246 insertions(+), 252 deletions(-) diff --git a/src/prodir/cpp/tree_structurer.cpp b/src/prodir/cpp/tree_structurer.cpp index baae534..0558e06 100644 --- a/src/prodir/cpp/tree_structurer.cpp +++ b/src/prodir/cpp/tree_structurer.cpp @@ -1,9 +1,12 @@ #include "tree_structurer.hpp" + #include #include #include #include #include +#include +#include namespace fs = std::filesystem; @@ -13,6 +16,7 @@ bool TreeStructurer::should_ignore_dir(const std::string& dirname) { ".git", ".idea", ".vscode", "__pycache__", "**pycache**" }; + // Allow these Python files even if their names start with underscores. if (!dirname.empty() && (dirname == "__main__.py" || dirname == "__init__.py")) { return false; } @@ -111,289 +115,278 @@ std::vector TreeStructurer::get_filtered_paths(const fs::path& start) return paths; } +// ----------------------------------------------------------------------------- +// Directory Structure Generation (tree printing) +// ----------------------------------------------------------------------------- + std::vector TreeStructurer::get_directory_structure(const std::string& startpath) { - std::vector result; - - 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()) { - throw std::runtime_error("No valid files or directories found in: " + start.string()); - } - std::vector 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())); + std::vector lines; + fs::path start(startpath); + if (!fs::exists(start)) { + throw std::runtime_error("Path does not exist: " + startpath); } - return result; + // Recursive lambda that traverses the filesystem. + std::function traverse; + traverse = [&](const fs::path& path, const std::string& indent, bool isLast) { + std::string line; + if (indent.empty()) { + // For the root we simply output the name (appending a directory marker if needed) + line = path.filename().string(); + } else { + // If not at the root, prepend a branch marker. + std::string branch = isLast ? + (std::string(1, static_cast(TREE_CORNER)) + "── ") : + (std::string(1, static_cast(TREE_BRANCH)) + "── "); + line = indent + branch + path.filename().string(); + } + if (fs::is_directory(path)) { + line.push_back(static_cast(DIRECTORY_MARKER)); + } + lines.push_back(line); + + if (fs::is_directory(path)) { + // Collect and sort the children. + std::vector children; + for (auto& entry : fs::directory_iterator(path)) { + std::string filename = entry.path().filename().string(); + if (entry.is_directory() && should_ignore_dir(filename)) + continue; + if (!entry.is_directory() && should_ignore_file(filename)) + continue; + children.push_back(entry.path()); + } + std::sort(children.begin(), children.end(), [](const fs::path& a, + const fs::path& b) { + return a.filename().string() < b.filename().string(); + }); + + // Recurse into children. + for (size_t i = 0; i < children.size(); i++) { + bool last = (i == children.size() - 1); + // New indent: if we are not at the root then extend the current indent. + std::string newIndent = indent; + if (!indent.empty()) { + newIndent += isLast ? " " : u8"│ "; + } + traverse(children[i], newIndent, last); + } + } + }; + + // Start the traversal with an empty indent. + traverse(start, "", true); + return lines; } -// -// UPDATED: Instead of processing the lines “on the fly,” we now build the tree and then create the project. -// This also allows the same parsing logic to be reused by create_structure_from_string(). -// -void TreeStructurer::create_structure_from_file(const std::string& filepath, const std::string& target_path) { +// ----------------------------------------------------------------------------- +// Structure Creation from a Tree-like File or String +// ----------------------------------------------------------------------------- + +void TreeStructurer::create_structure_from_file(const std::string& filepath, + const std::string& target_path) { std::vector lines = read_structure_file(filepath); validate_structure(lines); TreeNode root = build_tree_from_lines(lines); - create_node(root, target_path); + create_node(root, fs::path(target_path)); } -void TreeStructurer::create_structure_from_string(const std::string& structure, const std::string& target_path) { - std::istringstream iss(structure); +void TreeStructurer::create_structure_from_string(const std::string& structure, + const std::string& target_path) { std::vector lines; + std::istringstream iss(structure); std::string line; - while (std::getline(iss, line)) { if (!line.empty()) { lines.push_back(line); } } - validate_structure(lines); TreeNode root = build_tree_from_lines(lines); - create_node(root, target_path); + create_node(root, fs::path(target_path)); } -// -// UPDATED: Now using a Unicode‑aware (token‑based) indent calculation. -// -void TreeStructurer::validate_structure(const std::vector& lines) { - if (lines.empty()) { - throw std::runtime_error("Empty structure provided"); - } - - int prev_indent = -1; - for (const auto& line : lines) { - int current_indent = static_cast(get_indent_level(line)); - if (current_indent > prev_indent + 1) { - throw std::runtime_error("Invalid indentation level in structure"); - } - prev_indent = current_indent; - } -} - -// -// UPDATED: Parse the indent level by looking for 4‑character tokens. -// -size_t TreeStructurer::get_indent_level(const std::string& line) { - size_t level = 0; - size_t pos = 0; - // Look for “blank” or vertical bar segments (each 4 characters) - while (pos + 4 <= line.size() && (line.compare(pos, 4, " ") == 0 || line.compare(pos, 4, "│ ") == 0)) { - level++; - pos += 4; - } - // Then look for the branch indicator at this level - if (pos + 4 <= line.size() && (line.compare(pos, 4, "└── ") == 0 || line.compare(pos, 4, "├── ") == 0)) { - level++; - } - return level; -} - -// -// UPDATED: Build a tree of nodes from the structure lines using the same token‐based parsing. -// The first (root) line is expected to be a directory (with a trailing '/'). -TreeStructurer::TreeNode TreeStructurer::build_tree_from_lines(const std::vector& lines) { - if (lines.empty()) { - throw std::runtime_error("Empty structure provided"); - } - - // Process the first line as the root. - std::string first_line = lines[0]; - size_t pos = 0; - // Skip any indent tokens (if any) - while (pos + 4 <= first_line.size() && (first_line.compare(pos, 4, " ") == 0 || first_line.compare(pos, 4, "│ ") == 0)) { - pos += 4; - } - if (pos + 4 <= first_line.size() && (first_line.compare(pos, 4, "└── ") == 0 || first_line.compare(pos, 4, "├── ") == 0)) { - pos += 4; - } - std::string root_name = first_line.substr(pos); - if (root_name.empty() || root_name.back() != DIRECTORY_MARKER) { - throw std::runtime_error("Root must be a directory (ending with '" + std::string(1, DIRECTORY_MARKER) + "')"); - } - root_name.pop_back(); // Remove trailing '/' - - TreeNode root{root_name, false, {}}; - std::vector stack; - stack.push_back(&root); - - // Process remaining lines. - for (size_t i = 1; i < lines.size(); ++i) { - const std::string& line = lines[i]; - if (line.empty()) continue; - size_t pos = 0; - size_t current_level = 0; - // Process any “blank” or vertical indent segments (4 characters each) - while (pos + 4 <= line.size() && (line.compare(pos, 4, " ") == 0 || line.compare(pos, 4, "│ ") == 0)) { - current_level++; - pos += 4; - } - // Process the branch token if present - if (pos + 4 <= line.size() && (line.compare(pos, 4, "└── ") == 0 || line.compare(pos, 4, "├── ") == 0)) { - current_level++; - pos += 4; - } - std::string name = line.substr(pos); - if (name.empty()) continue; - bool is_file = true; - if (name.back() == DIRECTORY_MARKER) { - is_file = false; - name.pop_back(); - } - name = sanitize_path(name); - if (name.empty()) continue; - - // Adjust the stack to the proper level. - while (stack.size() > current_level) { - stack.pop_back(); - } - if (stack.empty()) { - throw std::runtime_error("Invalid indentation structure in the file."); - } - TreeNode new_node{name, is_file, {}}; - stack.back()->children.push_back(new_node); - if (!is_file) { - // For a directory, push the new node onto the stack. - stack.push_back(&stack.back()->children.back()); - } - } - - return root; -} - -void TreeStructurer::create_node(const TreeNode& node, const std::filesystem::path& current_path) { - std::filesystem::path new_path = current_path / node.name; - - if (node.is_file) { - // Create file - std::filesystem::path parent_path = new_path.parent_path(); - if (!std::filesystem::exists(parent_path)) { - std::filesystem::create_directories(parent_path); - } - create_file(new_path); - } else { - // Create directory - create_directory(new_path); - // Recursively create children - for (const auto& child : node.children) { - create_node(child, new_path); - } - } -} - -void TreeStructurer::create_directory(const std::filesystem::path& path) { - if (!std::filesystem::exists(path)) { - std::filesystem::create_directories(path); - } -} - -void TreeStructurer::create_file(const std::filesystem::path& path) { - if (!std::filesystem::exists(path)) { - std::ofstream file(path); - if (!file.is_open()) { - throw std::runtime_error("Failed to create file: " + path.string()); - } - file.close(); - } -} - -std::string TreeStructurer::sanitize_path(const std::string& path) { - std::string result; - for (char c : path) { - // Allow alphanumeric, common punctuation, the directory separator, and our tree tokens. - if (std::isalnum(static_cast(c)) || - c == '.' || c == '_' || c == '-' || c == '/' || - c == TREE_PIPE || c == TREE_BRANCH || c == TREE_CORNER || c == TREE_DASH) { - result += c; - } - } - return result; -} - -TreeStructurer::TreeNode TreeStructurer::parse_structure_line(const std::string& line, size_t indent_level) { - size_t name_start = line.find_first_not_of(" │├└─"); - if (name_start == std::string::npos) { - return TreeNode{"", true, {}}; - } - - std::string name = line.substr(name_start); - name = sanitize_path(name); - - bool is_file = !name.empty() && name.back() != DIRECTORY_MARKER; - if (!is_file) { - name.pop_back(); - } - - return TreeNode{name, is_file, {}}; -} - -bool TreeStructurer::is_directory_marker(const std::string& line) { - return !line.empty() && line.back() == DIRECTORY_MARKER; -} +// ----------------------------------------------------------------------------- +// Private Helper Functions (no duplicates) +// ----------------------------------------------------------------------------- +// Returns a string consisting of (level * 4) spaces. std::string TreeStructurer::create_indent(size_t level) { return std::string(level * 4, ' '); } +// Determines the "indent level" of a line by scanning it in 4‑character groups. +// Recognizes either a blank indent (" " or "│ ") or a branch marker ("├── " or "└── "). +size_t TreeStructurer::get_indent_level(const std::string& line) { + size_t indent = 0; + size_t pos = 0; + while (pos + 4 <= line.size()) { + std::string group = line.substr(pos, 4); + if (group == " " || group == u8"│ ") { + indent++; + pos += 4; + continue; + } + if (group == u8"├── " || group == u8"└── ") { + indent++; + break; + } + break; + } + return indent; +} + +// Parses a single line of the structure (after knowing its indent level) and returns a TreeNode. +// The function "consumes" indent groups until the branch marker. +TreeStructurer::TreeNode TreeStructurer::parse_structure_line(const std::string& line, + size_t indent_level) { + size_t pos = 0; + size_t groups = 0; + while (pos + 4 <= line.size() && groups < indent_level) { + std::string group = line.substr(pos, 4); + if (group == " " || group == u8"│ ") { + pos += 4; + groups++; + continue; + } + if (group == u8"├── " || group == u8"└── ") { + pos += 4; + groups++; + break; + } + break; + } + std::string name = line.substr(pos); + name = sanitize_path(name); + bool is_file = true; + if (!name.empty() && name.back() == static_cast(DIRECTORY_MARKER)) { + is_file = false; + name.pop_back(); // Remove the directory marker. + } + return TreeNode{name, is_file, {}}; +} + +// Builds a tree (with TreeNode nodes) from the vector of structure lines. +// The first line is assumed to be the root. +TreeStructurer::TreeNode TreeStructurer::build_tree_from_lines(const std::vector& lines) { + if (lines.empty()) { + throw std::runtime_error("Empty structure provided"); + } + // Process the first line as the root. + TreeNode root = parse_structure_line(lines[0], 0); + if (root.is_file) { + throw std::runtime_error("Root must be a directory"); + } + std::vector stack; + stack.push_back(&root); + + // Process each subsequent line. + for (size_t i = 1; i < lines.size(); ++i) { + size_t indent = get_indent_level(lines[i]); + TreeNode node = parse_structure_line(lines[i], indent); + if (indent > stack.size()) { + throw std::runtime_error("Invalid indentation structure in the file"); + } + while (stack.size() > indent) { + stack.pop_back(); + } + if (stack.empty()) { + throw std::runtime_error("Invalid indentation structure in the file"); + } + stack.back()->children.push_back(node); + if (!node.is_file) { + // Push a pointer to the newly added child. + stack.push_back(&stack.back()->children.back()); + } + } + return root; +} + +// Recursively creates directories and files on disk according to the tree. +void TreeStructurer::create_node(const TreeNode& node, const fs::path& current_path) { + fs::path new_path = current_path / node.name; + try { + if (node.is_file) { + // Ensure the parent directory exists. + fs::path parent = new_path.parent_path(); + if (!fs::exists(parent)) { + fs::create_directories(parent); + } + create_file(new_path); + } else { + create_directory(new_path); + for (const auto& child : node.children) { + create_node(child, new_path); + } + } + } catch (const fs::filesystem_error& e) { + throw std::runtime_error("Failed to create path '" + new_path.string() + "': " + e.what()); + } +} + +// Returns true if the given line’s last character is a directory marker. +bool TreeStructurer::is_directory_marker(const std::string& line) { + return (!line.empty() && line.back() == static_cast(DIRECTORY_MARKER)); +} + +// Creates a directory (and any necessary parent directories). +void TreeStructurer::create_directory(const fs::path& path) { + if (!fs::exists(path)) { + fs::create_directories(path); + } +} + +// Creates an empty file. +void TreeStructurer::create_file(const fs::path& path) { + if (!fs::exists(path)) { + std::ofstream ofs(path); + if (!ofs.is_open()) { + throw std::runtime_error("Failed to create file: " + path.string()); + } + ofs.close(); + } +} + +// Reads a structure file into a vector of non-empty lines. std::vector TreeStructurer::read_structure_file(const std::string& filepath) { std::vector lines; std::ifstream file(filepath); - if (!file.is_open()) { throw std::runtime_error("Failed to open file: " + filepath); } - std::string line; while (std::getline(file, line)) { if (!line.empty()) { lines.push_back(line); } } - return lines; } + +// Checks the structure for obvious mistakes (e.g. a jump in indentation). +void TreeStructurer::validate_structure(const std::vector& lines) { + if (lines.empty()) { + throw std::runtime_error("Empty structure provided"); + } + size_t prev_indent = 0; + for (const auto& line : lines) { + if (line.empty()) continue; + size_t indent = get_indent_level(line); + std::cout << "Line: \"" << line << "\" Indent level: " << indent << std::endl; + if (indent > prev_indent + 1) { + throw std::runtime_error("Invalid indentation structure in the file"); + } + prev_indent = indent; + } +} + +// Removes any disallowed characters from a node name (here we allow printable ASCII and '/'). +std::string TreeStructurer::sanitize_path(const std::string& path) { + std::string result; + for (char c : path) { + if ((c >= 32 && c <= 126) || c == '/') { + result.push_back(c); + } + } + return result; +} diff --git a/src/prodir/cpp/tree_structurer.hpp b/src/prodir/cpp/tree_structurer.hpp index c9e29c0..95ec860 100644 --- a/src/prodir/cpp/tree_structurer.hpp +++ b/src/prodir/cpp/tree_structurer.hpp @@ -38,9 +38,10 @@ private: void validate_structure(const std::vector& lines); std::string sanitize_path(const std::string& path); - static const char DIRECTORY_MARKER = '/'; - static const char TREE_PIPE = '│'; - static const char TREE_BRANCH = '├'; - static const char TREE_CORNER = '└'; - static const char TREE_DASH = '─'; -}; \ No newline at end of file + // Update the constants to use wide characters + static const wchar_t DIRECTORY_MARKER = L'/'; + static const wchar_t TREE_PIPE = L'│'; + static const wchar_t TREE_BRANCH = L'├'; + static const wchar_t TREE_CORNER = L'└'; + static const wchar_t TREE_DASH = L'─'; +}; From 37654490645a1b42c84f2d4aa54463e2341891c4 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Sat, 8 Feb 2025 20:27:49 +0100 Subject: [PATCH 6/6] working generation script to generate proejct dirs with cli command create --- .vscode/settings.json | 5 +- pyproject.toml | 4 +- setup.py | 2 +- src/prodir/__init__.py | 6 +- src/prodir/__main__.py | 50 ++---- src/prodir/cpp/bindings.cpp | 24 --- src/prodir/cpp/tree_structurer.cpp | 264 ++++++++++++++++++----------- src/prodir/cpp/tree_structurer.hpp | 2 - 8 files changed, 192 insertions(+), 165 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1c29706..5dc5c07 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -67,6 +67,9 @@ "xmemory": "cpp", "xstring": "cpp", "xtr1common": "cpp", - "xutility": "cpp" + "xutility": "cpp", + "fstream": "cpp", + "iostream": "cpp", + "codecvt": "cpp" } } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 6a57252..4f0aa46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "prodir" -version = "0.0.6" -description = "A module for analyzing directory structures" +version = "0.0.7" +description = "A module for analyzing and creating directory structures" scripts = {prodir = "prodir.__main__:main"} dependencies = [] diff --git a/setup.py b/setup.py index 79f7bc5..152e1bb 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ tree_structurer_module = Extension( setup( name='prodir', - version='0.0.6', + version='0.0.7', description='A module for analyzing directory structures', ext_modules=[tree_structurer_module], packages=find_packages(where="src"), diff --git a/src/prodir/__init__.py b/src/prodir/__init__.py index defd188..84442b3 100644 --- a/src/prodir/__init__.py +++ b/src/prodir/__init__.py @@ -1,13 +1,11 @@ from prodir._tree_structurer import ( create_tree_structurer, get_structure, - create_structure_from_file, - create_structure_from_string + create_structure_from_file ) __all__ = [ 'create_tree_structurer', 'get_structure', - 'create_structure_from_file', - 'create_structure_from_string' + 'create_structure_from_file' ] \ No newline at end of file diff --git a/src/prodir/__main__.py b/src/prodir/__main__.py index ecb4aa6..d1cc593 100644 --- a/src/prodir/__main__.py +++ b/src/prodir/__main__.py @@ -4,18 +4,17 @@ import argparse from prodir import ( create_tree_structurer, get_structure, - create_structure_from_file, - create_structure_from_string + create_structure_from_file ) def main(): parser = argparse.ArgumentParser( description='Directory structure tool: Display and create directory structures' ) - + # Create subparsers for different commands subparsers = parser.add_subparsers(dest='command', help='Available commands') - + # Display command display_parser = subparsers.add_parser('display', help='Display directory structure') display_parser.add_argument( @@ -29,17 +28,13 @@ def main(): action='store_true', help='Show more detailed output' ) - + # Create command create_parser = subparsers.add_parser('create', help='Create directory structure') create_parser.add_argument( - '-f', '--file', + 'file', help='File containing the directory structure' ) - create_parser.add_argument( - '-s', '--string', - help='String containing the directory structure' - ) create_parser.add_argument( '-o', '--output', default=os.getcwd(), @@ -50,53 +45,40 @@ def main(): action='store_true', help='Show more detailed output' ) - + args = parser.parse_args() - + # If no command is specified, default to display if not args.command: args.command = 'display' args.path = os.getcwd() args.verbose = False - + try: create_tree_structurer() - + if args.command == 'display': if args.verbose: print(f"Analyzing directory: {args.path}") - + structure = get_structure(args.path) for line in structure: print(line) - + elif args.command == 'create': - if not args.file and not args.string: - parser.error("Either --file or --string must be specified") - - if args.file and args.string: - parser.error("Cannot specify both --file and --string") - if args.verbose: print(f"Creating directory structure in: {args.output}") - - if args.file: - if args.verbose: - print(f"Using structure from file: {args.file}") - create_structure_from_file(args.file, args.output) - - elif args.string: - if args.verbose: - print("Creating structure from provided string") - create_structure_from_string(args.string, args.output) - + print(f"Using structure from file: {args.file}") + + create_structure_from_file(args.file, args.output) + if args.verbose: print("Structure created successfully") print("\nResulting structure:") structure = get_structure(args.output) for line in structure: print(line) - + except Exception as e: error_msg = str(e) if "Directory does not exist" in error_msg: diff --git a/src/prodir/cpp/bindings.cpp b/src/prodir/cpp/bindings.cpp index 141b82e..affbede 100644 --- a/src/prodir/cpp/bindings.cpp +++ b/src/prodir/cpp/bindings.cpp @@ -66,28 +66,6 @@ static PyObject* create_structure_from_file(PyObject* self, PyObject* args) { } } -static PyObject* create_structure_from_string(PyObject* self, PyObject* args) { - const char* structure_str = nullptr; - const char* target_path = "."; // Default to current directory - - if (!PyArg_ParseTuple(args, "s|s", &structure_str, &target_path)) { - return NULL; - } - - if (g_tree_structurer == nullptr) { - PyErr_SetString(TreeStructurerError, "TreeStructurer not initialized"); - return NULL; - } - - try { - g_tree_structurer->create_structure_from_string(structure_str, target_path); - Py_RETURN_NONE; - } 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"}, @@ -95,8 +73,6 @@ static PyMethodDef TreeStructurerMethods[] = { "Get the directory structure for the given path"}, {"create_structure_from_file", create_structure_from_file, METH_VARARGS, "Create directory structure from a file containing the structure description"}, - {"create_structure_from_string", create_structure_from_string, METH_VARARGS, - "Create directory structure from a string containing the structure description"}, {NULL, NULL, 0, NULL} }; diff --git a/src/prodir/cpp/tree_structurer.cpp b/src/prodir/cpp/tree_structurer.cpp index 0558e06..2302cce 100644 --- a/src/prodir/cpp/tree_structurer.cpp +++ b/src/prodir/cpp/tree_structurer.cpp @@ -65,6 +65,7 @@ std::string TreeStructurer::get_relative_path(const fs::path& path, const fs::pa return rel.string(); } + std::vector TreeStructurer::get_filtered_paths(const fs::path& start) { std::vector paths; fs::directory_options options = fs::directory_options::skip_permission_denied; @@ -115,95 +116,93 @@ std::vector TreeStructurer::get_filtered_paths(const fs::path& start) return paths; } + // ----------------------------------------------------------------------------- // Directory Structure Generation (tree printing) // ----------------------------------------------------------------------------- std::vector TreeStructurer::get_directory_structure(const std::string& startpath) { - std::vector lines; - fs::path start(startpath); - if (!fs::exists(start)) { - throw std::runtime_error("Path does not exist: " + startpath); + std::vector 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()) { + throw std::runtime_error("No valid files or directories found in: " + start.string()); + } + std::vector 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())); } - // Recursive lambda that traverses the filesystem. - std::function traverse; - traverse = [&](const fs::path& path, const std::string& indent, bool isLast) { - std::string line; - if (indent.empty()) { - // For the root we simply output the name (appending a directory marker if needed) - line = path.filename().string(); - } else { - // If not at the root, prepend a branch marker. - std::string branch = isLast ? - (std::string(1, static_cast(TREE_CORNER)) + "── ") : - (std::string(1, static_cast(TREE_BRANCH)) + "── "); - line = indent + branch + path.filename().string(); - } - if (fs::is_directory(path)) { - line.push_back(static_cast(DIRECTORY_MARKER)); - } - lines.push_back(line); - - if (fs::is_directory(path)) { - // Collect and sort the children. - std::vector children; - for (auto& entry : fs::directory_iterator(path)) { - std::string filename = entry.path().filename().string(); - if (entry.is_directory() && should_ignore_dir(filename)) - continue; - if (!entry.is_directory() && should_ignore_file(filename)) - continue; - children.push_back(entry.path()); - } - std::sort(children.begin(), children.end(), [](const fs::path& a, - const fs::path& b) { - return a.filename().string() < b.filename().string(); - }); - - // Recurse into children. - for (size_t i = 0; i < children.size(); i++) { - bool last = (i == children.size() - 1); - // New indent: if we are not at the root then extend the current indent. - std::string newIndent = indent; - if (!indent.empty()) { - newIndent += isLast ? " " : u8"│ "; - } - traverse(children[i], newIndent, last); - } - } - }; - - // Start the traversal with an empty indent. - traverse(start, "", true); - return lines; + return result; } // ----------------------------------------------------------------------------- // Structure Creation from a Tree-like File or String // ----------------------------------------------------------------------------- - void TreeStructurer::create_structure_from_file(const std::string& filepath, - const std::string& target_path) { + const std::string& target_path) { std::vector lines = read_structure_file(filepath); validate_structure(lines); TreeNode root = build_tree_from_lines(lines); - create_node(root, fs::path(target_path)); -} - -void TreeStructurer::create_structure_from_string(const std::string& structure, - const std::string& target_path) { - std::vector lines; - std::istringstream iss(structure); - std::string line; - while (std::getline(iss, line)) { - if (!line.empty()) { - lines.push_back(line); + + fs::path target(target_path); + + // Check if the root directory name matches the target directory name + if (root.name == target.filename().string()) { + // If names match, create the children directly in the target directory + for (const auto& child : root.children) { + create_node(child, target); } + } else { + // If names don't match, create the full structure including root + create_node(root, target); } - validate_structure(lines); - TreeNode root = build_tree_from_lines(lines); - create_node(root, fs::path(target_path)); } // ----------------------------------------------------------------------------- @@ -217,55 +216,115 @@ std::string TreeStructurer::create_indent(size_t level) { // Determines the "indent level" of a line by scanning it in 4‑character groups. // Recognizes either a blank indent (" " or "│ ") or a branch marker ("├── " or "└── "). + size_t TreeStructurer::get_indent_level(const std::string& line) { size_t indent = 0; size_t pos = 0; - while (pos + 4 <= line.size()) { - std::string group = line.substr(pos, 4); - if (group == " " || group == u8"│ ") { + + while (pos < line.length()) { + if (pos >= line.length()) break; + + // Check for basic space indent (4 spaces) + if (pos + 3 < line.length() && line.substr(pos, 4) == " ") { indent++; pos += 4; continue; } - if (group == u8"├── " || group == u8"└── ") { + + // Check for │ (vertical line) followed by 3 spaces + // Bytes: E2 94 82 20 20 20 + if (pos + 5 < line.length() && + static_cast(line[pos]) == 0xE2 && + static_cast(line[pos + 1]) == 0x94 && + static_cast(line[pos + 2]) == 0x82 && + line[pos + 3] == ' ' && + line[pos + 4] == ' ' && + line[pos + 5] == ' ') { indent++; - break; + pos += 6; + continue; } + + // Check for ├── or └── (branch or corner followed by dashes and space) + // ├ = E2 94 9C + // └ = E2 94 94 + // ─ = E2 94 80 + if (pos + 8 < line.length() && + static_cast(line[pos]) == 0xE2 && + static_cast(line[pos + 1]) == 0x94 && + (static_cast(line[pos + 2]) == 0x9C || // ├ + static_cast(line[pos + 2]) == 0x94) && // └ + static_cast(line[pos + 3]) == 0xE2 && + static_cast(line[pos + 4]) == 0x94 && + static_cast(line[pos + 5]) == 0x80 && // ─ + static_cast(line[pos + 6]) == 0xE2 && + static_cast(line[pos + 7]) == 0x94 && + static_cast(line[pos + 8]) == 0x80 && // ─ + (pos + 9 >= line.length() || line[pos + 9] == ' ')) { + indent++; + break; // We've found our indent marker, stop here + } + + // If we get here without finding a valid indent pattern, we're done break; } + return indent; } - // Parses a single line of the structure (after knowing its indent level) and returns a TreeNode. // The function "consumes" indent groups until the branch marker. -TreeStructurer::TreeNode TreeStructurer::parse_structure_line(const std::string& line, - size_t indent_level) { +TreeStructurer::TreeNode TreeStructurer::parse_structure_line(const std::string& line, size_t indent_level) { size_t pos = 0; - size_t groups = 0; - while (pos + 4 <= line.size() && groups < indent_level) { - std::string group = line.substr(pos, 4); - if (group == " " || group == u8"│ ") { + size_t current_indent = 0; + + // Skip through indentation patterns + while (current_indent < indent_level && pos < line.length()) { + // Check for basic space indent + if (pos + 3 < line.length() && line.substr(pos, 4) == " ") { pos += 4; - groups++; + current_indent++; continue; } - if (group == u8"├── " || group == u8"└── ") { - pos += 4; - groups++; + + // Check for │ followed by spaces + if (pos + 5 < line.length() && + static_cast(line[pos]) == 0xE2 && + static_cast(line[pos + 1]) == 0x94 && + static_cast(line[pos + 2]) == 0x82 && + line[pos + 3] == ' ' && + line[pos + 4] == ' ' && + line[pos + 5] == ' ') { + pos += 6; + current_indent++; + continue; + } + + // Check for ├── or └── pattern + if (pos + 9 < line.length() && + static_cast(line[pos]) == 0xE2 && + static_cast(line[pos + 1]) == 0x94 && + (static_cast(line[pos + 2]) == 0x9C || + static_cast(line[pos + 2]) == 0x94)) { + pos += 10; // Skip the entire pattern including space + current_indent++; break; } - break; + + pos++; } + + // Extract the name (everything after the indentation) std::string name = line.substr(pos); name = sanitize_path(name); + bool is_file = true; - if (!name.empty() && name.back() == static_cast(DIRECTORY_MARKER)) { + if (!name.empty() && name.back() == '/') { is_file = false; - name.pop_back(); // Remove the directory marker. + name.pop_back(); } + return TreeNode{name, is_file, {}}; } - // Builds a tree (with TreeNode nodes) from the vector of structure lines. // The first line is assumed to be the root. TreeStructurer::TreeNode TreeStructurer::build_tree_from_lines(const std::vector& lines) { @@ -350,31 +409,42 @@ void TreeStructurer::create_file(const fs::path& path) { // Reads a structure file into a vector of non-empty lines. std::vector TreeStructurer::read_structure_file(const std::string& filepath) { std::vector lines; - std::ifstream file(filepath); + // Open file in binary mode to avoid Windows CRLF conversion + std::ifstream file(filepath, std::ios::binary); if (!file.is_open()) { throw std::runtime_error("Failed to open file: " + filepath); } + std::string line; while (std::getline(file, line)) { + // Remove carriage return if present (Windows files) + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + if (!line.empty()) { lines.push_back(line); } } return lines; } - // Checks the structure for obvious mistakes (e.g. a jump in indentation). void TreeStructurer::validate_structure(const std::vector& lines) { if (lines.empty()) { throw std::runtime_error("Empty structure provided"); } size_t prev_indent = 0; - for (const auto& line : lines) { + for (size_t i = 0; i < lines.size(); i++) { + const auto& line = lines[i]; if (line.empty()) continue; - size_t indent = get_indent_level(line); - std::cout << "Line: \"" << line << "\" Indent level: " << indent << std::endl; + + size_t indent = get_indent_level(line); if (indent > prev_indent + 1) { - throw std::runtime_error("Invalid indentation structure in the file"); + throw std::runtime_error( + "Invalid indentation jump at line " + std::to_string(i + 1) + + ": from level " + std::to_string(prev_indent) + + " to " + std::to_string(indent) + ); } prev_indent = indent; } diff --git a/src/prodir/cpp/tree_structurer.hpp b/src/prodir/cpp/tree_structurer.hpp index 95ec860..d1b800e 100644 --- a/src/prodir/cpp/tree_structurer.hpp +++ b/src/prodir/cpp/tree_structurer.hpp @@ -11,9 +11,7 @@ public: TreeStructurer() = default; std::vector get_directory_structure(const std::string& startpath = std::filesystem::current_path().string()); - void create_structure_from_file(const std::string& filepath, const std::string& target_path = std::filesystem::current_path().string()); - void create_structure_from_string(const std::string& structure, const std::string& target_path = std::filesystem::current_path().string()); private: struct TreeNode {