From 37654490645a1b42c84f2d4aa54463e2341891c4 Mon Sep 17 00:00:00 2001 From: Falko Habel Date: Sat, 8 Feb 2025 20:27:49 +0100 Subject: [PATCH] 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 {