#pragma once #include #include #include "ToolBase.hpp" #include class ToolPackage final : public ToolBase { std::vector m_selectedItems; std::unique_ptr m_fallbackProj; hecl::Database::Project* m_useProj; const hecl::Database::DataSpecEntry* m_spec = nullptr; bool m_fast = false; void AddSelectedItem(const hecl::ProjectPath& path) { for (const hecl::ProjectPath& item : m_selectedItems) if (item == path) return; m_selectedItems.push_back(path); } void CheckFile(const hecl::ProjectPath& path) { auto lastComp = path.getLastComponent(); if (hecl::StringUtils::BeginsWith(lastComp, _SYS_STR("!world_")) && hecl::StringUtils::EndsWith(lastComp, _SYS_STR(".blend"))) AddSelectedItem(path); } void FindSelectedItems(const hecl::ProjectPath& path, bool checkGeneral) { if (path.isFile()) { CheckFile(path); return; } size_t origSize = m_selectedItems.size(); hecl::DirectoryEnumerator dEnum(path.getAbsolutePath(), hecl::DirectoryEnumerator::Mode::DirsThenFilesSorted, false, false, true); for (const auto& ent : dEnum) { hecl::ProjectPath childPath(path, ent.m_name); if (ent.m_isDir) FindSelectedItems(childPath, checkGeneral && childPath.getPathComponents().size() <= 2); else CheckFile(childPath); } /* Directory with 2 components not "Shared" or macOS app bundle * and no nested !world.blend files == General PAK */ if (checkGeneral && origSize == m_selectedItems.size()) { auto pathComps = path.getPathComponents(); if (pathComps.size() == 2 && pathComps[0] != _SYS_STR("out") && pathComps[1] != _SYS_STR("Shared") && pathComps[0].find(_SYS_STR(".app")) == hecl::SystemString::npos) AddSelectedItem(path); } } public: explicit ToolPackage(const ToolPassInfo& info) : ToolBase(info), m_useProj(info.project) { if (!info.project) LogModule.report(logvisor::Fatal, FMT_STRING("hecl package must be ran within a project directory")); /* Scan args */ if (info.args.size()) { /* See if project path is supplied via args and use that over the getcwd one */ m_selectedItems.reserve(info.args.size()); for (const hecl::SystemString& arg : info.args) { if (arg.empty()) continue; else if (arg == _SYS_STR("--fast")) { m_fast = true; continue; } else if (arg.size() >= 8 && !arg.compare(0, 7, _SYS_STR("--spec="))) { hecl::SystemString specName(arg.begin() + 7, arg.end()); for (const hecl::Database::DataSpecEntry* spec : hecl::Database::DATA_SPEC_REGISTRY) { if (!hecl::StrCaseCmp(spec->m_name.data(), specName.c_str())) { m_spec = spec; break; } } if (!m_spec) LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("unable to find data spec '{}'")), specName); continue; } else if (arg.size() >= 2 && arg[0] == _SYS_STR('-') && arg[1] == _SYS_STR('-')) continue; hecl::SystemString subPath; hecl::ProjectRootPath root = hecl::SearchForProject(MakePathArgAbsolute(arg, info.cwd), subPath); if (root) { if (!m_fallbackProj) { m_fallbackProj.reset(new hecl::Database::Project(root)); m_useProj = m_fallbackProj.get(); } else if (m_fallbackProj->getProjectRootPath() != root) LogModule.report(logvisor::Fatal, FMT_STRING(_SYS_STR("hecl package can only process multiple items in the same project; ") _SYS_STR("'{}' and '{}' are different projects")), m_fallbackProj->getProjectRootPath().getAbsolutePath(), root.getAbsolutePath()); FindSelectedItems({*m_useProj, subPath}, true); } } } if (!m_useProj) LogModule.report(logvisor::Fatal, FMT_STRING("hecl package must be ran within a project directory or " "provided a path within a project")); /* Default case: recursive at root */ if (m_selectedItems.empty()) FindSelectedItems({*m_useProj, _SYS_STR("")}, true); } static void Help(HelpOutput& help) { help.secHead(_SYS_STR("NAME")); help.beginWrap(); help.wrap(_SYS_STR("hecl-pack\n") _SYS_STR("hecl-package - Package objects within the project database\n")); help.endWrap(); help.secHead(_SYS_STR("SYNOPSIS")); help.beginWrap(); help.wrap(_SYS_STR("hecl package [--spec=] []\n")); help.endWrap(); help.secHead(_SYS_STR("DESCRIPTION")); help.beginWrap(); help.wrap(_SYS_STR("This command initiates a packaging pass on the project database. Packaging ") _SYS_STR("is analogous to linking in software development. All objects necessary to ") _SYS_STR( "generate a complete package are gathered, grouped, and indexed within a .upak file.\n")); help.endWrap(); help.secHead(_SYS_STR("OPTIONS")); help.optionHead(_SYS_STR("--spec="), _SYS_STR("data specification")); help.beginWrap(); help.wrap(_SYS_STR("Specifies a DataSpec to use when cooking and generating the package. ") _SYS_STR("This build of hecl supports the following values of :\n")); for (const hecl::Database::DataSpecEntry* spec : hecl::Database::DATA_SPEC_REGISTRY) { if (!spec->m_factory) continue; help.wrap(_SYS_STR(" ")); help.wrapBold(spec->m_name.data()); help.wrap(_SYS_STR("\n")); } help.endWrap(); help.secHead(_SYS_STR("OPTIONS")); help.optionHead(_SYS_STR(""), _SYS_STR("input directory")); help.beginWrap(); help.wrap(_SYS_STR("Specifies a project subdirectory to root the resulting package from. ") _SYS_STR("If any dependent files fall outside this subdirectory, they will be implicitly ") _SYS_STR("gathered and packaged.\n")); help.endWrap(); } hecl::SystemStringView toolName() const override { return _SYS_STR("package"sv); } int run() override { if (XTERM_COLOR) fmt::print(FMT_STRING(_SYS_STR("" GREEN BOLD "ABOUT TO PACKAGE:" NORMAL "\n"))); else fmt::print(FMT_STRING(_SYS_STR("ABOUT TO PACKAGE:\n"))); for (auto& item : m_selectedItems) fmt::print(FMT_STRING(_SYS_STR(" {}\n")), item.getRelativePath()); fflush(stdout); if (continuePrompt()) { hecl::MultiProgressPrinter printer(true); hecl::ClientProcess cp(&printer); for (const hecl::ProjectPath& path : m_selectedItems) { if (!m_useProj->packagePath(path, printer, m_fast, m_spec, &cp)) LogModule.report(logvisor::Error, FMT_STRING(_SYS_STR("Unable to package {}")), path.getAbsolutePath()); } cp.waitUntilComplete(); } return 0; } void cancel() override { m_useProj->interruptCook(); } };