Compare commits
15 Commits
790ac3d039
...
22874c4c61
Author | SHA1 | Date |
---|---|---|
|
22874c4c61 | |
|
59957781c0 | |
|
048c275b55 | |
|
eec232ae53 | |
|
2a034e9c0e | |
|
e230128be0 | |
|
64596eaf46 | |
|
88ac87a0b6 | |
|
1a3f6cb451 | |
|
9b4742334a | |
|
53c3025856 | |
|
359d29ca68 | |
|
7fe3e8a4c2 | |
|
a08519ded0 | |
|
b4f284eddf |
|
@ -1,4 +1,4 @@
|
||||||
name: Gitea Actions For Tree-Structurer
|
name: Gitea Actions For prodir
|
||||||
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
|
||||||
on: [push]
|
on: [push]
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,8 @@
|
||||||
"xutility": "cpp",
|
"xutility": "cpp",
|
||||||
"fstream": "cpp",
|
"fstream": "cpp",
|
||||||
"iostream": "cpp",
|
"iostream": "cpp",
|
||||||
"codecvt": "cpp"
|
"codecvt": "cpp",
|
||||||
|
"map": "cpp",
|
||||||
|
"xtree": "cpp"
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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
|
|
@ -4,7 +4,13 @@ build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "prodir"
|
name = "prodir"
|
||||||
version = "1.0.0"
|
version = "1.0.1"
|
||||||
description = "A module for analyzing and creating directory structures"
|
description = "A module for analyzing and creating directory structures"
|
||||||
scripts = {prodir = "prodir.__main__:main"}
|
scripts = {prodir = "prodir.__main__:main"}
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|
||||||
|
license = {text = "Creative Commons Attribution 4.0 International"}
|
||||||
|
|
||||||
|
authors = [
|
||||||
|
{ name="Falko Habel", email="falko.habel@fabelous.app" }
|
||||||
|
]
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -20,7 +20,7 @@ tree_structurer_module = Extension(
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='prodir',
|
name='prodir',
|
||||||
version='0.1.0',
|
version='1.0.1',
|
||||||
description='A module for analyzing directory structures',
|
description='A module for analyzing directory structures',
|
||||||
ext_modules=[tree_structurer_module],
|
ext_modules=[tree_structurer_module],
|
||||||
packages=find_packages(where="src"),
|
packages=find_packages(where="src"),
|
||||||
|
|
|
@ -69,10 +69,9 @@ def main():
|
||||||
create_tree_structurer()
|
create_tree_structurer()
|
||||||
|
|
||||||
if args.command == 'display':
|
if args.command == 'display':
|
||||||
if args.verbose:
|
|
||||||
print(f"Analyzing directory: {args.path}")
|
|
||||||
try:
|
try:
|
||||||
structure = get_structure(args.path)
|
structure = get_structure(args.path)
|
||||||
|
print(f"Analyzing directory: {args.path}")
|
||||||
for line in structure:
|
for line in structure:
|
||||||
print(line)
|
print(line)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#include "tree_structurer.hpp"
|
#include "tree_structurer.hpp"
|
||||||
|
#include <map>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
@ -123,58 +123,67 @@ std::vector<fs::path> TreeStructurer::get_filtered_paths(const fs::path& start)
|
||||||
|
|
||||||
std::vector<std::string> TreeStructurer::get_directory_structure(const std::string& startpath) {
|
std::vector<std::string> TreeStructurer::get_directory_structure(const std::string& startpath) {
|
||||||
std::vector<std::string> result;
|
std::vector<std::string> result;
|
||||||
|
|
||||||
// Normalize the input path by removing ./ or .\ prefix if present
|
|
||||||
std::string normalized_path = startpath;
|
std::string normalized_path = startpath;
|
||||||
if (startpath.substr(0, 2) == ".\\" || startpath.substr(0, 2) == "./") {
|
if (normalized_path.size() >= 2 &&
|
||||||
normalized_path = startpath.substr(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);
|
fs::path start = normalized_path.empty() ? fs::current_path() : fs::path(normalized_path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto paths = get_filtered_paths(start);
|
if (!fs::exists(start)) {
|
||||||
if (paths.empty()) {
|
throw std::runtime_error("Directory does not exist: " + start.string());
|
||||||
throw std::runtime_error("No valid files or directories found in: " + start.string());
|
|
||||||
}
|
}
|
||||||
std::vector<bool> is_last_at_level(256, false);
|
|
||||||
|
|
||||||
|
if (!fs::is_directory(start)) {
|
||||||
|
throw std::runtime_error("Path is not a directory: " + start.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<fs::path> paths = get_filtered_paths(start);
|
||||||
|
std::vector<bool> is_last_per_level;
|
||||||
|
|
||||||
|
// Skip the first path as it's the root
|
||||||
for (size_t i = 1; i < paths.size(); ++i) {
|
for (size_t i = 1; i < paths.size(); ++i) {
|
||||||
const auto& path = paths[i];
|
fs::path relative = fs::relative(paths[i], start);
|
||||||
std::string rel_path = get_relative_path(path, start);
|
std::vector<std::string> components;
|
||||||
|
for (const auto& comp : relative) {
|
||||||
int level = std::count(rel_path.begin(), rel_path.end(), fs::path::preferred_separator);
|
components.push_back(comp.string());
|
||||||
|
|
||||||
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;
|
// 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;
|
std::string line;
|
||||||
for (int j = 0; j < level; ++j) {
|
for (size_t j = 0; j < level; ++j) {
|
||||||
if (j == level - 1) {
|
if (j == level - 1) {
|
||||||
line += is_last ? "└── " : "├── ";
|
line += is_last ? "└── " : "├── ";
|
||||||
} else {
|
} else {
|
||||||
line += is_last_at_level[j] ? " " : "│ ";
|
line += is_last_per_level[j] ? " " : "│ ";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
line += path.filename().string();
|
// Add the file/directory name
|
||||||
if (fs::is_directory(path)) {
|
line += components.back();
|
||||||
|
if (fs::is_directory(paths[i])) {
|
||||||
line += "/";
|
line += "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
result.push_back(line);
|
result.push_back(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const fs::filesystem_error& e) {
|
} catch (const fs::filesystem_error& e) {
|
||||||
throw std::runtime_error("Failed to access directory: " + std::string(e.what()));
|
throw std::runtime_error("Failed to access directory: " + std::string(e.what()));
|
||||||
}
|
}
|
||||||
|
@ -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<std::string> TreeStructurer::read_structure_file(const std::string& filepath) {
|
std::vector<std::string> TreeStructurer::read_structure_file(const std::string& filepath) {
|
||||||
std::vector<std::string> lines;
|
std::vector<std::string> lines;
|
||||||
// Open file in binary mode to avoid Windows CRLF conversion
|
// Open file in binary mode to avoid Windows CRLF conversion
|
||||||
|
@ -416,16 +425,38 @@ std::vector<std::string> TreeStructurer::read_structure_file(const std::string&
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
while (std::getline(file, line)) {
|
while (std::getline(file, line)) {
|
||||||
// Remove carriage return if present (Windows files)
|
// Remove carriage return if present (Windows files)
|
||||||
if (!line.empty() && line.back() == '\r') {
|
if (!line.empty() && line.back() == '\r') {
|
||||||
line.pop_back();
|
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()) {
|
if (!line.empty()) {
|
||||||
lines.push_back(line);
|
lines.push_back(line);
|
||||||
|
} else {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
// Checks the structure for obvious mistakes (e.g. a jump in indentation).
|
// Checks the structure for obvious mistakes (e.g. a jump in indentation).
|
||||||
|
|
|
@ -67,16 +67,50 @@ def test_display_verbose(tmp_path):
|
||||||
# Only check if we got any output, ignoring encoding errors
|
# Only check if we got any output, ignoring encoding errors
|
||||||
assert stdout != "" or stderr != ""
|
assert stdout != "" or stderr != ""
|
||||||
|
|
||||||
|
|
||||||
|
# ... rest of code here ...
|
||||||
|
|
||||||
|
# ... rest of code here ...
|
||||||
|
|
||||||
def test_create_directory_from_file(tmp_path):
|
def test_create_directory_from_file(tmp_path):
|
||||||
structure_file = tmp_path / 'structure.txt'
|
# Define the desired directory structure in a string with proper indentation
|
||||||
structure_content = "dir1/\n file1.txt"
|
structure_content = """data/
|
||||||
structure_file.write_text(structure_content)
|
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 = tmp_path / 'output'
|
||||||
output_dir.mkdir()
|
output_dir.mkdir()
|
||||||
|
|
||||||
stdout, stderr = run_prodir(['create', str(structure_file), '-o', str(output_dir)])
|
stdout, stderr = run_prodir(['create', "example.md" '-o'])
|
||||||
assert os.path.exists(output_dir / 'dir1' / 'file1.txt')
|
|
||||||
|
# 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):
|
def test_create_verbose(tmp_path):
|
||||||
structure_file = tmp_path / 'structure.txt'
|
structure_file = tmp_path / 'structure.txt'
|
||||||
|
@ -86,9 +120,9 @@ def test_create_verbose(tmp_path):
|
||||||
output_dir = tmp_path / 'output'
|
output_dir = tmp_path / 'output'
|
||||||
output_dir.mkdir()
|
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
|
# 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():
|
def test_display_invalid_path():
|
||||||
# Use an absolute path with some random UUID to ensure it doesn't exist
|
# Use an absolute path with some random UUID to ensure it doesn't exist
|
||||||
|
|
Loading…
Reference in New Issue