diff --git a/.vscode/settings.json b/.vscode/settings.json index 5dc5c07..c145493 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -70,6 +70,8 @@ "xutility": "cpp", "fstream": "cpp", "iostream": "cpp", - "codecvt": "cpp" + "codecvt": "cpp", + "map": "cpp", + "xtree": "cpp" } } \ No newline at end of file diff --git a/example.md b/example.md new file mode 100644 index 0000000..c2a870c --- /dev/null +++ b/example.md @@ -0,0 +1,16 @@ +my_project/ +├── src/ +│ ├── __init__.py # init file +│ ├── main.py +│ └── utils.py +├── tests/ +│ ├── __init__.py +│ ├── test_main.py +│ └── test_utils.py +├── data/ +│ └── sample_data.csv +├── docs/ +│ └── README.md +├── .gitignore +├── requirements.txt +└── setup.py \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0d7ded4..830f41b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "prodir" -version = "1.0.0" +version = "1.0.1" description = "A module for analyzing and creating directory structures" scripts = {prodir = "prodir.__main__:main"} dependencies = [] diff --git a/setup.py b/setup.py index 190d430..a06be42 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ tree_structurer_module = Extension( setup( name='prodir', - version='1.0.0', + version='1.0.1', description='A module for analyzing directory structures', ext_modules=[tree_structurer_module], packages=find_packages(where="src"), diff --git a/src/prodir/__main__.py b/src/prodir/__main__.py index f6beed9..6c8f796 100644 --- a/src/prodir/__main__.py +++ b/src/prodir/__main__.py @@ -69,10 +69,9 @@ def main(): create_tree_structurer() if args.command == 'display': - if args.verbose: - print(f"Analyzing directory: {args.path}") try: structure = get_structure(args.path) + print(f"Analyzing directory: {args.path}") for line in structure: print(line) except FileNotFoundError: diff --git a/src/prodir/cpp/tree_structurer.cpp b/src/prodir/cpp/tree_structurer.cpp index 2302cce..ad33960 100644 --- a/src/prodir/cpp/tree_structurer.cpp +++ b/src/prodir/cpp/tree_structurer.cpp @@ -1,5 +1,5 @@ #include "tree_structurer.hpp" - +#include #include #include #include @@ -123,62 +123,71 @@ 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); + if (normalized_path.size() >= 2 && + (normalized_path.substr(0, 2) == ".\\" || normalized_path.substr(0, 2) == "./")) { + normalized_path = normalized_path.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()); + if (!fs::exists(start)) { + throw std::runtime_error("Directory does not exist: " + start.string()); } - std::vector is_last_at_level(256, false); + if (!fs::is_directory(start)) { + throw std::runtime_error("Path is not a directory: " + start.string()); + } + + std::vector paths = get_filtered_paths(start); + std::vector is_last_per_level; + + // Skip the first path as it's the root 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; - } + fs::path relative = fs::relative(paths[i], start); + std::vector components; + for (const auto& comp : relative) { + components.push_back(comp.string()); } - - is_last_at_level[level] = is_last; - + + // Calculate the current level + size_t level = components.size() - 1; + + // Adjust is_last_per_level vector size + while (is_last_per_level.size() <= level) { + is_last_per_level.push_back(false); + } + + // Determine if this is the last item at its level + bool is_last = (i == paths.size() - 1) || + (i + 1 < paths.size() && + fs::relative(paths[i + 1], start).begin()->string() != components[0]); + + is_last_per_level[level] = is_last; + + // Build the line prefix std::string line; - for (int j = 0; j < level; ++j) { + for (size_t j = 0; j < level; ++j) { if (j == level - 1) { line += is_last ? "└── " : "├── "; } else { - line += is_last_at_level[j] ? " " : "│ "; + line += is_last_per_level[j] ? " " : "│ "; } } - - line += path.filename().string(); - if (fs::is_directory(path)) { + + // Add the file/directory name + line += components.back(); + if (fs::is_directory(paths[i])) { line += "/"; } + result.push_back(line); } + } catch (const fs::filesystem_error& e) { throw std::runtime_error("Failed to access directory: " + std::string(e.what())); } - + return result; } @@ -406,7 +415,7 @@ void TreeStructurer::create_file(const fs::path& path) { } } -// Reads a structure file into a vector of non-empty lines. +// Reads a structure file into a vector of non-empty lines, ignoring comments. std::vector TreeStructurer::read_structure_file(const std::string& filepath) { std::vector lines; // Open file in binary mode to avoid Windows CRLF conversion @@ -416,16 +425,38 @@ std::vector TreeStructurer::read_structure_file(const std::string& } std::string line; + while (std::getline(file, line)) { // Remove carriage return if present (Windows files) if (!line.empty() && line.back() == '\r') { line.pop_back(); } - + + size_t hash_pos = line.find('#'); + size_t single_line_comment_pos = line.find("//"); + size_t multi_line_comment_start_pos = line.find("/*"); + + if (hash_pos != std::string::npos) { + // Trim the line at the hash comment + line = line.substr(0, hash_pos); + } else if (single_line_comment_pos != std::string::npos) { + // Trim the line at the single-line comment + line = line.substr(0, single_line_comment_pos); + } else if (multi_line_comment_start_pos != std::string::npos) { + // Trim the line at the multi-line comment start + line = line.substr(0, multi_line_comment_start_pos); + } + + // Remove leading and trailing whitespace + line.erase(0, line.find_first_not_of(" \t\n\r\f\v")); + line.erase(line.find_last_not_of(" \t\n\r\f\v") + 1); + if (!line.empty()) { lines.push_back(line); + } else { } } + return lines; } // Checks the structure for obvious mistakes (e.g. a jump in indentation). diff --git a/tests/test__main__.py b/tests/test__main__.py index 8f136b3..3badf19 100644 --- a/tests/test__main__.py +++ b/tests/test__main__.py @@ -67,16 +67,46 @@ def test_display_verbose(tmp_path): # Only check if we got any output, ignoring encoding errors assert stdout != "" or stderr != "" + def test_create_directory_from_file(tmp_path): - structure_file = tmp_path / 'structure.txt' - structure_content = "dir1/\n file1.txt" - structure_file.write_text(structure_content) - + # Define the desired directory structure in a string with proper indentation + structure_content = """data/ + sample_data.csv +docs/ + README.md +requirements.txt +setup.py +src/ + __init__.py + main.py + utils.py +tests/ + __init__.py + test_main.py + test_utils.py""" + + output_dir = tmp_path / 'output' output_dir.mkdir() - - stdout, stderr = run_prodir(['create', str(structure_file), '-o', str(output_dir)]) - assert os.path.exists(output_dir / 'dir1' / 'file1.txt') + + stdout, stderr = run_prodir(['create', "example.md"]) + + # Print the stdout and stderr for debugging + print("stdout:", stdout) + print("stderr:", stderr) + + + assert os.path.exists('my_project/data/sample_data.csv') + assert os.path.exists('my_project/docs/README.md') + assert os.path.exists('my_project/requirements.txt') + assert os.path.exists('my_project/setup.py') + assert os.path.exists('my_project/src/__init__.py') + assert os.path.exists('my_project/src/main.py') + assert os.path.exists('my_project/src/utils.py') + assert os.path.exists('my_project/tests/__init__.py') + assert os.path.exists('my_project/tests/test_main.py') + assert os.path.exists('my_project/tests/test_utils.py') + def test_create_verbose(tmp_path): structure_file = tmp_path / 'structure.txt' @@ -86,9 +116,9 @@ def test_create_verbose(tmp_path): output_dir = tmp_path / 'output' output_dir.mkdir() - stdout, stderr = run_prodir(['create', str(structure_file), '-o', str(output_dir), '-v']) + stdout, stderr = run_prodir(['create', "example.md" '-v']) # Only check if we got any output and the directory was created - assert os.path.exists(output_dir / 'dir1' / 'file1.txt') + assert os.path.exists( 'my_project/requirements.txt') def test_display_invalid_path(): # Use an absolute path with some random UUID to ensure it doesn't exist