スポンサーリンク
以前にも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); }