diff --git a/hecl/blender/BlenderConnection.cpp b/hecl/blender/BlenderConnection.cpp index 86b7fb46e..bb0ca8156 100644 --- a/hecl/blender/BlenderConnection.cpp +++ b/hecl/blender/BlenderConnection.cpp @@ -294,7 +294,7 @@ BlenderConnection::~BlenderConnection() bool BlenderConnection::createBlend(const SystemString& path) { - _writeLine(("CREATE" + path).c_str()); + _writeLine(("CREATE \"" + path + "\"").c_str()); char lineBuf[256]; _readLine(lineBuf, sizeof(lineBuf)); if (!strcmp(lineBuf, "FINISHED")) @@ -307,7 +307,7 @@ bool BlenderConnection::createBlend(const SystemString& path) bool BlenderConnection::openBlend(const SystemString& path) { - _writeLine(("OPEN" + path).c_str()); + _writeLine(("OPEN \"" + path + "\"").c_str()); char lineBuf[256]; _readLine(lineBuf, sizeof(lineBuf)); if (!strcmp(lineBuf, "FINISHED")) diff --git a/hecl/blender/blendershell.py b/hecl/blender/blendershell.py index 1754160e8..e95dd579c 100644 --- a/hecl/blender/blendershell.py +++ b/hecl/blender/blendershell.py @@ -1,4 +1,6 @@ -import bpy, sys, os +import bpy, sys, os, re + +ARGS_PATTERN = re.compile(r'''(?:"([^"]+)"|'([^']+)'|(\S+))''') # Extract pipe file descriptors from arguments print(sys.argv) @@ -44,25 +46,32 @@ if ackbytes != b'ACK': # Command loop while True: - cmdline = readpipeline().split(b' ') + cmdline = readpipeline() + if cmdline == b'': + print('HECL connection lost') + bpy.ops.wm.quit_blender() + cmdargs = [] + for match in ARGS_PATTERN.finditer(cmdline.decode()): + cmdargs.append(match.group(match.lastindex)) + print(cmdargs) - if cmdline[0] == b'QUIT': + if cmdargs[0] == 'QUIT': quitblender() - elif cmdline[0] == b'OPEN': - if 'FINISHED' in bpy.ops.wm.open_mainfile(filepath=cmdline[1].decode()): + elif cmdargs[0] == 'OPEN': + if 'FINISHED' in bpy.ops.wm.open_mainfile(filepath=cmdargs[1]): writepipeline(b'FINISHED') else: writepipeline(b'CANCELLED') - elif cmdline[0] == b'CREATE': + elif cmdargs[0] == 'CREATE': bpy.context.user_preferences.filepaths.save_version = 0 - if 'FINISHED' in bpy.ops.wm.save_as_mainfile(filepath=cmdline[1].decode()): + if 'FINISHED' in bpy.ops.wm.save_as_mainfile(filepath=cmdargs[1]): writepipeline(b'FINISHED') else: writepipeline(b'CANCELLED') - elif cmdline[0] == b'PYBEGIN': + elif cmdargs[0] == 'PYBEGIN': writepipeline(b'READY') globals = dict() locals = dict() @@ -72,16 +81,17 @@ while True: if line == b'PYEND': writepipeline(b'DONE') break - co = compile(line+'\n', '', 'single') + co = compile(line+b'\n', '', 'single') exec(co, globals, locals) except Exception as e: writepipeline(b'EXCEPTION') + raise break writepipeline(b'OK') - elif cmdline[0] == b'PYEND': + elif cmdargs[0] == 'PYEND': writepipeline(b'ERROR') else: - hecl.command(cmdline, writepipeline, writepipebuf) + hecl.command(cmdargs, writepipeline, writepipebuf) diff --git a/hecl/driver/ToolExtract.hpp b/hecl/driver/ToolExtract.hpp index 60a0a0644..ba40b8485 100644 --- a/hecl/driver/ToolExtract.hpp +++ b/hecl/driver/ToolExtract.hpp @@ -18,6 +18,8 @@ class ToolExtract final : public ToolBase }; std::vector m_specPasses; std::vector m_reps; + std::unique_ptr m_fallbackProj; + HECL::Database::Project* m_useProj; public: ToolExtract(const ToolPassInfo& info) : ToolBase(info) @@ -28,14 +30,30 @@ public: if (!info.project) { /* Get name from input file and init project there */ - std::string baseFile = info.args[0]; + HECL::SystemString baseFile = info.args[0]; size_t slashPos = baseFile.rfind(_S('/')); if (slashPos == HECL::SystemString::npos) slashPos = baseFile.rfind(_S('\\')); if (slashPos != HECL::SystemString::npos) - baseFile.assign(baseFile.g - LogModule.report(LogVisor::FatalError, "hecl extract must be ran within a project directory"); + baseFile.assign(baseFile.begin() + slashPos + 1, baseFile.end()); + size_t dotPos = baseFile.rfind(_S('.')); + if (dotPos != HECL::SystemString::npos) + baseFile.assign(baseFile.begin(), baseFile.begin() + dotPos); + + if (baseFile.empty()) + LogModule.report(LogVisor::FatalError, "hecl extract must be ran within a project directory"); + + size_t ErrorRef = LogVisor::ErrorCount; + HECL::ProjectRootPath newProjRoot(baseFile); + newProjRoot.makeDir(); + m_fallbackProj.reset(new HECL::Database::Project(newProjRoot)); + if (LogVisor::ErrorCount > ErrorRef) + LogModule.report(LogVisor::FatalError, "unable to init project at '%s'", baseFile.c_str()); + LogModule.report(LogVisor::Info, _S("initialized project at '%s/.hecl'"), baseFile.c_str()); + m_useProj = m_fallbackProj.get(); } + else + m_useProj = info.project; m_einfo.srcpath = m_info.args[0]; m_einfo.extractArgs.reserve(info.args.size() - 1); @@ -47,10 +65,10 @@ public: for (const HECL::Database::DataSpecEntry* entry : HECL::Database::DATA_SPEC_REGISTRY) { - HECL::Database::IDataSpec* ds = entry->m_factory(HECL::Database::TOOL_EXTRACT); + HECL::Database::IDataSpec* ds = entry->m_factory(*m_useProj, HECL::Database::TOOL_EXTRACT); if (ds) { - if (ds->canExtract(*m_info.project, m_einfo, m_reps)) + if (ds->canExtract(*m_useProj, m_einfo, m_reps)) m_specPasses.emplace_back(entry, ds); else delete ds; @@ -166,7 +184,7 @@ public: #endif int lineIdx = 0; - ds.m_instance->doExtract(*m_info.project, m_einfo, + ds.m_instance->doExtract(*m_useProj, m_einfo, [&lineIdx](const HECL::SystemChar* message, int lidx, float factor) { #ifndef _WIN32 @@ -229,6 +247,7 @@ public: if (XTERM_COLOR) HECL::Printf(_S("" SHOW_CURSOR "")); #endif + fflush(stdout); }); HECL::Printf(_S("\n\n")); } diff --git a/hecl/driver/ToolInit.hpp b/hecl/driver/ToolInit.hpp index a343f3268..6263968fe 100644 --- a/hecl/driver/ToolInit.hpp +++ b/hecl/driver/ToolInit.hpp @@ -45,7 +45,6 @@ public: return -1; size_t ErrorRef = LogVisor::ErrorCount; HECL::Database::Project proj((HECL::ProjectRootPath(*m_dir))); - proj.enableDataSpecs({_S("hecl-little")}); if (LogVisor::ErrorCount > ErrorRef) return -1; LogModule.report(LogVisor::Info, _S("initialized project at '%s/.hecl'"), m_dir->c_str()); diff --git a/hecl/extern/RetroCommon b/hecl/extern/RetroCommon index 80cc3cc27..28985165f 160000 --- a/hecl/extern/RetroCommon +++ b/hecl/extern/RetroCommon @@ -1 +1 @@ -Subproject commit 80cc3cc277c57d86d9bc8ef2784f68ad95028e9c +Subproject commit 28985165ffa189dc6d7b352b650bc94ddc999887 diff --git a/hecl/include/HECL/Database.hpp b/hecl/include/HECL/Database.hpp index 53f5cc75a..0e584986c 100644 --- a/hecl/include/HECL/Database.hpp +++ b/hecl/include/HECL/Database.hpp @@ -283,10 +283,10 @@ struct DataSpecEntry { const SystemChar* m_name; const SystemChar* m_desc; - std::function m_factory; + std::function m_factory; DataSpecEntry(const SystemChar* name, const SystemChar* desc, - std::function&& factory) + std::function&& factory) : m_name(std::move(name)), m_desc(std::move(desc)), m_factory(std::move(factory)) { DATA_SPEC_REGISTRY.push_back(this); diff --git a/hecl/include/HECL/HECL.hpp b/hecl/include/HECL/HECL.hpp index 1f0f34a74..41309eb08 100644 --- a/hecl/include/HECL/HECL.hpp +++ b/hecl/include/HECL/HECL.hpp @@ -122,17 +122,27 @@ inline std::string operator+(const char* lhs, const SystemStringView& rhs) {retu typedef struct stat Sstat; #endif -static inline void MakeDir(const SystemString& dir) +static inline void MakeDir(const SystemChar* dir) { #if _WIN32 HRESULT err; - if (!CreateDirectory(dir.c_str(), NULL)) + if (!CreateDirectory(dir, NULL)) if ((err = GetLastError()) != ERROR_ALREADY_EXISTS) - LogModule.report(LogVisor::FatalError, _S("MakeDir: %s"), dir.c_str()); + LogModule.report(LogVisor::FatalError, _S("MakeDir(%s)"), dir); #else - if (mkdir(dir.c_str(), 0755)) + if (mkdir(dir, 0755)) if (errno != EEXIST) - LogModule.report(LogVisor::FatalError, "MakeDir %s: %s", dir.c_str(), strerror(errno)); + LogModule.report(LogVisor::FatalError, "MakeDir(%s): %s", dir, strerror(errno)); +#endif +} + +static inline void MakeLink(const SystemChar* target, const SystemChar* linkPath) +{ +#if _WIN32 +#else + if (symlink(target, linkPath)) + if (errno != EEXIST) + LogModule.report(LogVisor::FatalError, "MakeLink(%s, %s): %s", target, linkPath, strerror(errno)); #endif } @@ -384,15 +394,29 @@ protected: #endif } public: + /** + * @brief Empty constructor + * + * Used to preallocate ProjectPath for later population using assign() + */ + ProjectPath() {} + + /** + * @brief Tests for non-empty project path + */ + operator bool() const {return m_absPath.size() != 0;} + /** * @brief Construct a project subpath representation within another subpath * @param parentPath previously constructed ProjectPath which ultimately connects to a ProjectRootPath * @param path valid filesystem-path (relative or absolute) to subpath */ - ProjectPath(const ProjectPath& parentPath, const SystemString& path); + ProjectPath(const ProjectPath& parentPath, const SystemString& path) {assign(parentPath, path);} + void assign(const ProjectPath& parentPath, const SystemString& path); #if HECL_UCS2 - ProjectPath(const ProjectPath& parentPath, const std::string& path); + ProjectPath(const ProjectPath& parentPath, const std::string& path) {assign(parentPath, path);} + void assign(const ProjectPath& parentPath, const std::string& path); #endif /** @@ -474,7 +498,27 @@ public: */ void getGlobResults(std::vector& outPaths) const; - inline void makeDir() const {MakeDir(m_absPath);} + /** + * @brief Create directory at path + * + * Fatal log report is issued if directory is not able to be created or doesn't already exist. + * If directory already exists, no action taken. + */ + inline void makeDir() const {MakeDir(m_absPath.c_str());} + + /** + * @brief Create relative symbolic link at calling path targeting another path + * @param target Path to target + */ + inline void makeLinkTo(const ProjectPath& target) const + { + SystemString relTarget; + for (SystemChar ch : m_relPath) + if (ch == _S('/') || ch == _S('\\')) + relTarget += _S("../"); + relTarget += target.m_relPath; + MakeLink(relTarget.c_str(), m_absPath.c_str()); + } /** * @brief HECL-specific blowfish hash diff --git a/hecl/lib/ProjectPath.cpp b/hecl/lib/ProjectPath.cpp index d521e1afe..054fe091f 100644 --- a/hecl/lib/ProjectPath.cpp +++ b/hecl/lib/ProjectPath.cpp @@ -55,9 +55,9 @@ static SystemString canonRelPath(const SystemString& path) return _S("."); } -ProjectPath::ProjectPath(const ProjectPath& parentPath, const SystemString& path) -: m_projRoot(parentPath.m_projRoot) +void ProjectPath::assign(const ProjectPath& parentPath, const SystemString& path) { + m_projRoot = parentPath.m_projRoot; m_relPath = canonRelPath(parentPath.m_relPath + _S('/') + path); m_absPath = parentPath.m_projRoot + _S('/') + m_relPath; m_hash = Hash(m_relPath); @@ -68,9 +68,9 @@ ProjectPath::ProjectPath(const ProjectPath& parentPath, const SystemString& path } #if HECL_UCS2 -ProjectPath::ProjectPath(const ProjectPath& parentPath, const std::string& path) -: m_projRoot(parentPath.m_projRoot) +void ProjectPath::assign(const ProjectPath& parentPath, const std::string& path) { + m_projRoot = parentPath.m_projRoot; std::wstring wpath = UTF8ToWide(path); m_relPath = canonRelPath(parentPath.m_relPath + _S('/') + wpath); m_absPath = parentPath.m_projRoot + _S('/') + m_relPath;