スポンサーリンク

ディレクトリツリーを作成

以前にもstd::filesystemでディレクトリツリーを作成するコードをいつもより少しだけ真面目に書いた。

#include <iostream>

#include <vector>
#include <string>
#include <filesystem>

// SetConsoleOutputCP のために必要
#include <windows.h>

enum class FileType {
    Ignore,
    Directory,
    File
};
enum class ExceptionType {
    Success,
    // 子要素の収集に失敗(イテレータ取得に失敗)
    // あるノードにこのエラーがついていた場合、そのノードの子要素は取得できていない
    ChildrenIteratorError,
    NodeTypeError,
    NodeNameError,
    NotInit,
    Other,
};

using PathString = std::u8string;

inline PathString ToFilenameString(const std::filesystem::path& path) {

    std::filesystem::path tmp;
    if (path.filename().empty()) {
        tmp = path.parent_path();  // 末尾がスラッシュなら親ディレクトリを取得
    }
    else {
        tmp = path; // 末尾がスラッシュでないならそのまま
    }

    PathString fname = tmp.filename().u8string();
    return fname;

}

struct ErrorData {
    ExceptionType type;
    std::string message;
    std::filesystem::path path;
public:
    ErrorData() : type(ExceptionType::Success) {}
    ErrorData(ExceptionType type, const std::string& message) : type(type), message(message) {}
};

// 自作の例外クラス
class FileSystemTreeException : public std::exception {
private:
    std::string message;
    ErrorData error_data;
public:
    explicit FileSystemTreeException(const std::string& msg, const std::filesystem::path& _path_, const ExceptionType type) :
        message(msg) {
        error_data.type = type;
        error_data.message = msg;
        error_data.path = _path_;
    }

    const char* what() const noexcept override {
        return message.c_str();
    }

    const ErrorData& getErrorData() const {
        return error_data;
    }
};

// ノードの子ノード配列を扱うクラス
// FileSystemTreeで子ノードの読み込みに失敗した場合はerror_dataに内容が入っている
class
ChildrenNodes { std::vector<std::unique_ptr<class FileSystemTree>> children; ErrorData error_data; public: ChildrenNodes() {} ChildrenNodes& operator=(std::vector<std::unique_ptr<class FileSystemTree>>&& other) noexcept { children = std::move(other); return *this; } void push_back(std::unique_ptr<class FileSystemTree>&& node) { children.push_back(std::move(node)); } bool isValid()const { return error_data.type == ExceptionType::Success; } void setError(const ErrorData& err) { error_data = err; } const ErrorData& getError() const { return error_data; } std::vector<std::unique_ptr<class FileSystemTree>>::const_iterator cbegin() const { return children.cbegin(); } std::vector<std::unique_ptr<class FileSystemTree>>::const_iterator cend() const { return children.cend(); } std::vector<std::unique_ptr<class FileSystemTree>>::iterator begin() { return children.begin(); } std::vector<std::unique_ptr<class FileSystemTree>>::iterator end() { return children.end(); } std::vector<std::unique_ptr<class FileSystemTree>>::const_iterator begin() const { return children.cbegin(); } std::vector<std::unique_ptr<class FileSystemTree>>::const_iterator end() const { return children.cend(); } };

// ディレクトリツリーのクラス
class
FileSystemTree { PathString name; // ファイル名・ディレクトリ名 FileType type; // ファイルかディレクトリか ErrorData error_data; // このノードのエラー情報 ChildrenNodes children; // 子ノード一覧 ChildrenNodes error_children; // エラーが発生した子ノード一覧 public: FileSystemTree(const PathString& name, const FileType type) : name(name), type(type) {} FileSystemTree() :type(FileType::Ignore) {}; FileSystemTree(const ErrorData& err) : error_data(err), type(FileType::Ignore) {}
// このノードについて、子ノードの読み込みに失敗した場合に設定 // 注意:子ノード一つ一つではなく、イテレータの取得の段階で失敗したような場合に使用 void setChildrenError(const ExceptionType type, const std::string str) { children.setError(ErrorData(type, str)); }
// このノードの子ノード一覧を設定
// アクセスできない子を見つけた場合はerrorchildrenに入れて渡す
void setChildren( std::vector<std::unique_ptr<FileSystemTree>>&& children, std::vector<std::unique_ptr<FileSystemTree>>&& errorchildren) { this->children = std::move(children); this->error_children = std::move(errorchildren); } const ChildrenNodes& getChildren()const { return children; }// 子ノード一覧へアクセス void setError(const ErrorData& err) { error_data = err; }// このノードがエラーノードであることを設定(普通コンストラクタで指定可能なので使わない) bool isChildrenValid() const { return children.isValid(); }// 子ノード一覧の生成に成功したかどうか。たとえ要素0でも生成に成功していればtrue ChildrenNodes& getErrorChildren() { return error_children; }// 子ノード一一覧生成時、読み込めなかったときに発生したエラー一覧 const PathString& getName() const { return name; }// ノード名 FileType getType() const { return type; }// ファイルかディレクトリか bool isFailure() const { return error_data.type != ExceptionType::Success; }// このノードがエラーノードであるか const ErrorData& getError() const { return error_data; }// このノードのエラー情報 };

std::unique_ptr<FileSystemTree> MakeFilesystemNode(const std::filesystem::path& path) {

    FileType type;
    PathString name;
    try {
        // ノードの種類を特定
        type = std::filesystem::is_directory(path) ? FileType::Directory : FileType::File;
    }
    catch (const std::exception& e) {
        throw FileSystemTreeException(e.what(), path, ExceptionType::NodeTypeError);
    }

    try {
        // ノードの名前を取得
        name = ToFilenameString(path);
    }
    catch (const std::exception& e) {
        throw FileSystemTreeException(e.what(), path, ExceptionType::NodeNameError);
    }

    std::unique_ptr<FileSystemTree> the_node = std::make_unique<FileSystemTree>(name, type);
    return the_node;
}

// ディレクトリツリーを構築する関数
void buildDirTreeCore(std::unique_ptr<FileSystemTree>& this_directory, const std::filesystem::path& path) {

    if (this_directory->getType() == FileType::Directory) {
        // ノードがディレクトリの場合、子ノードを追加

        // pathの中身を取得するイテレータを作成
        std::filesystem::directory_iterator dir_itr;
        try {
            dir_itr = std::filesystem::directory_iterator(path);
        }
        catch (const std::filesystem::filesystem_error& e) {
            // 失敗した場合、このディレクトリ内のファイルを取得できないことを意味する
            this_directory->setChildrenError(ExceptionType::ChildrenIteratorError, e.what());
            return;
        }
        catch (const std::exception& e) {
            this_directory->setChildrenError(ExceptionType::Other, e.what());
            return;
        }

        std::vector<std::unique_ptr<FileSystemTree>> nowchildren; // this_directoryの子ノード一覧
        std::vector<std::unique_ptr<FileSystemTree>> errors; // エラーが発生した子ノード一覧

        // ディレクトリ内の項目を取得して追加
        for (const auto& entry : dir_itr) {
            std::unique_ptr<FileSystemTree> newnode;

            try {
                // 子要素のリストを作成
                newnode = MakeFilesystemNode(entry.path());
                nowchildren.emplace_back(std::move(newnode));
            }
            catch (const FileSystemTreeException& e) {
                // エラーが発生した場合、エラーノードを追加
                errors.push_back(std::make_unique<FileSystemTree>(e.getErrorData()));
            }

        }

        for (auto& subdirectory : nowchildren) {
            // ディレクトリのみ、サブディレクトリを再帰的に処理
            if (subdirectory->getType() == FileType::Directory) {

                const std::filesystem::path& subpath = path / subdirectory->getName();
                buildDirTreeCore(subdirectory, subpath);
            }
        }
        this_directory->setChildren(std::move(nowchildren), std::move(errors));
    }
}

std::unique_ptr<FileSystemTree> BuildTree(const std::filesystem::path& path) {
    std::unique_ptr<FileSystemTree> this_directory;
    auto entry = path.lexically_normal();
    this_directory = MakeFilesystemNode(entry); // パスからノードを作成
    buildDirTreeCore(this_directory, entry);
    return this_directory;
}
/////////////////////////////////////////////////////////////////////////////////////
// ファイルツリーを表示
void PrintTree(const std::unique_ptr<FileSystemTree>& node, int depth = 0) {


    auto to_string = [](const FileType type) {
        switch (type) {
        case FileType::Ignore:    return "*";
        case FileType::Directory: return "[DIR] ";
        case FileType::File:      return "[FILE] ";
        }
        return "Unknown";
        };

    std::string indent(depth * 2, ' ');
    if (node->isFailure()) {
        std::cout << indent << to_string(node->getType()) << (const char*)node->getName().c_str() << " : " << node->getError().message << "\n";
    }
    else {
        std::cout << indent << to_string(node->getType()) << (const char*)node->getName().c_str() << "\n";
    }
    if (node->isChildrenValid() == false) {
        std::cout << indent << "  ** Children Error: " << node->getChildren().getError().message << "\n";
    }
    else {
        for (const auto& child : node->getChildren()) {
            PrintTree(child, depth + 1);
        }
    }
    for (const auto& error : node->getErrorChildren()) {
        std::cout << indent << "  ** Error: " << error->getError().message << "\n";
    }
}
int main()
{
    // ロケール設定
    std::locale::global(std::locale("ja_JP.UTF-8"));
    std::cerr.imbue(std::locale("ja_JP.UTF-8"));
    std::cout.imbue(std::locale("ja_JP.UTF-8"));
    SetConsoleOutputCP(CP_UTF8);

    std::string path = R"(C:\test\myaccess)";

    std::unique_ptr<FileSystemTree> root = BuildTree(path);// ファイルパスからツリーを作成
    
    PrintTree(root);

}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)


この記事のトラックバックURL: