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