working generation script to generate proejct dirs with cli command create

This commit is contained in:
Falko Victor Habel 2025-02-08 20:27:49 +01:00
parent 2a2c13bddf
commit 3765449064
8 changed files with 192 additions and 165 deletions

View File

@ -67,6 +67,9 @@
"xmemory": "cpp",
"xstring": "cpp",
"xtr1common": "cpp",
"xutility": "cpp"
"xutility": "cpp",
"fstream": "cpp",
"iostream": "cpp",
"codecvt": "cpp"
}
}

View File

@ -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 = []

View File

@ -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"),

View File

@ -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'
]

View File

@ -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:

View File

@ -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}
};

View File

@ -65,6 +65,7 @@ std::string TreeStructurer::get_relative_path(const fs::path& path, const fs::pa
return rel.string();
}
std::vector<fs::path> TreeStructurer::get_filtered_paths(const fs::path& start) {
std::vector<fs::path> paths;
fs::directory_options options = fs::directory_options::skip_permission_denied;
@ -115,95 +116,93 @@ std::vector<fs::path> TreeStructurer::get_filtered_paths(const fs::path& start)
return paths;
}
// -----------------------------------------------------------------------------
// Directory Structure Generation (tree printing)
// -----------------------------------------------------------------------------
std::vector<std::string> TreeStructurer::get_directory_structure(const std::string& startpath) {
std::vector<std::string> lines;
fs::path start(startpath);
if (!fs::exists(start)) {
throw std::runtime_error("Path does not exist: " + startpath);
std::vector<std::string> 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<bool> 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<void(const fs::path&, const std::string&, bool)> 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<char>(TREE_CORNER)) + "── ") :
(std::string(1, static_cast<char>(TREE_BRANCH)) + "── ");
line = indent + branch + path.filename().string();
}
if (fs::is_directory(path)) {
line.push_back(static_cast<char>(DIRECTORY_MARKER));
}
lines.push_back(line);
if (fs::is_directory(path)) {
// Collect and sort the children.
std::vector<fs::path> 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<std::string> 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<std::string> 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 4character 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<unsigned char>(line[pos]) == 0xE2 &&
static_cast<unsigned char>(line[pos + 1]) == 0x94 &&
static_cast<unsigned char>(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<unsigned char>(line[pos]) == 0xE2 &&
static_cast<unsigned char>(line[pos + 1]) == 0x94 &&
(static_cast<unsigned char>(line[pos + 2]) == 0x9C || // ├
static_cast<unsigned char>(line[pos + 2]) == 0x94) && // └
static_cast<unsigned char>(line[pos + 3]) == 0xE2 &&
static_cast<unsigned char>(line[pos + 4]) == 0x94 &&
static_cast<unsigned char>(line[pos + 5]) == 0x80 && // ─
static_cast<unsigned char>(line[pos + 6]) == 0xE2 &&
static_cast<unsigned char>(line[pos + 7]) == 0x94 &&
static_cast<unsigned char>(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<unsigned char>(line[pos]) == 0xE2 &&
static_cast<unsigned char>(line[pos + 1]) == 0x94 &&
static_cast<unsigned char>(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<unsigned char>(line[pos]) == 0xE2 &&
static_cast<unsigned char>(line[pos + 1]) == 0x94 &&
(static_cast<unsigned char>(line[pos + 2]) == 0x9C ||
static_cast<unsigned char>(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<char>(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<std::string>& 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<std::string> TreeStructurer::read_structure_file(const std::string& filepath) {
std::vector<std::string> 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<std::string>& 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;
}

View File

@ -11,9 +11,7 @@ public:
TreeStructurer() = default;
std::vector<std::string> 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 {