From e2334387a905f0dc5f75a25150a50605749b0225 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 15 Jul 2020 16:57:14 -0700 Subject: [PATCH 1/5] blueprint: write blueprints to blueprints/ subdir to enable writing to a subdir that may not exist, blueprint now automatically creates folder trees. E.g. ``blueprint 30 30 1 rooms/dining dig`` will create the file ``blueprints/rooms/dining-dig.csv``). Previously it would fail if the ``blueprints/rooms/`` directory didn't already exist. --- docs/Authors.rst | 1 + docs/changelog.txt | 2 + plugins/CMakeLists.txt | 3 +- plugins/blueprint.cpp | 87 ++++++++++++++++++++++++++++++------------ 4 files changed, 67 insertions(+), 26 deletions(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index 6701b67a0..3c2777a7d 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -100,6 +100,7 @@ Milo Christiansen milochristiansen MithrilTuxedo MithrilTuxedo mizipzor mizipzor moversti moversti +Myk Taylor myk002 napagokc napagokc Neil Little nmlittle Nick Rart nickrart comestible diff --git a/docs/changelog.txt b/docs/changelog.txt index dfda78c0b..e43abd188 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,6 +41,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `RemoteFortressReader`: fixed a couple crashes that could result from decoding invalid enum items (``site_realization_building_type`` and ``improvement_type``) ## Misc Improvements +- `blueprint`: now writes blueprints to the ``blueprints/`` subfolder instead of the df root folder +- `blueprint`: now automatically creates folder trees when organizing blueprints into subfolders (e.g. ``blueprint 30 30 1 rooms/dining dig`` will create the file ``blueprints/rooms/dining-dig.csv``); previously it would fail if the ``blueprints/rooms/`` directory didn't already exist - `confirm`: added a confirmation dialog for convicting dwarves of crimes ## Ruby diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 678b7eec2..3dc461ee9 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -93,7 +93,8 @@ if(BUILD_SUPPORTED) dfhack_plugin(automaterial automaterial.cpp) dfhack_plugin(automelt automelt.cpp) dfhack_plugin(autotrade autotrade.cpp) - dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) + # stdc++fs required for std::experimental::filesystem; once we move to c++17 this can be removed since it's in the STL there + dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua stdc++fs) dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua) dfhack_plugin(building-hacks building-hacks.cpp LINK_LIBRARIES lua) dfhack_plugin(buildingplan buildingplan.cpp LINK_LIBRARIES buildingplan-lib) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 9742b16e7..d7ce19334 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -3,6 +3,8 @@ //Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort #include +#include // This is just in c++17, but we're currently compiling with c++11 +#include #include #include @@ -33,6 +35,8 @@ using std::pair; using namespace DFHack; using namespace df::enums; +namespace filesystem = std::experimental::filesystem; + DFHACK_PLUGIN("blueprint"); enum phase {DIG=1, BUILD=2, PLACE=4, QUERY=8}; @@ -62,9 +66,10 @@ command_result help(color_ostream &out) << " defaults to generating all blueprints" << endl << endl << "blueprint translates a portion of your fortress into blueprints suitable for" << endl - << " digfort/fortplan/quickfort. Blueprints are created in the DF folder with names" << endl - << " following a \"name-phase.csv\" pattern. Translation starts at the current" << endl - << " cursor location and includes all tiles in the range specified." << endl; + << " digfort/fortplan/quickfort. Blueprints are created in the \"blueprints\"" << endl + << " subdirectory of the DF folder with names following a \"name-phase.csv\" pattern." << endl + << " Translation starts at the current cursor location and includes all tiles in the" << endl + << " range specified." << endl; return CR_OK; } @@ -557,32 +562,52 @@ string get_tile_query(df::building* b) return " "; } -command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t phases) +void init_stream(ofstream &out, filesystem::path basename, std::string target) +{ + filesystem::path out_path(basename); + out_path += "-"; + out_path += target; + out_path += ".csv"; + out.open(out_path, ofstream::trunc); + out << "#" << target << endl; +} + +command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t phases, std::ostringstream &err) { ofstream dig, build, place, query; + + filesystem::path basename = "blueprints"; + basename /= name; + basename.make_preferred(); + + // create output directory if it doesn't already exist + std::error_code ec; + if (!filesystem::create_directories(basename.parent_path(), ec) && ec) + { + err << "could not create output directory: " << basename.parent_path() + << " (" << ec.message() << ")"; + return CR_FAILURE; + } + if (phases & QUERY) { //query = ofstream((name + "-query.csv").c_str(), ofstream::trunc); - query.open(name+"-query.csv", ofstream::trunc); - query << "#query" << endl; + init_stream(query, basename, "query"); } if (phases & PLACE) { //place = ofstream(name + "-place.csv", ofstream::trunc); - place.open(name+"-place.csv", ofstream::trunc); - place << "#place" << endl; + init_stream(place, basename, "place"); } if (phases & BUILD) { //build = ofstream(name + "-build.csv", ofstream::trunc); - build.open(name+"-build.csv", ofstream::trunc); - build << "#build" << endl; + init_stream(build, basename, "build"); } if (phases & DIG) { //dig = ofstream(name + "-dig.csv", ofstream::trunc); - dig.open(name+"-dig.csv", ofstream::trunc); - dig << "#dig" << endl; + init_stream(dig, basename, "dig"); } if (start.x > end.x) { @@ -675,18 +700,27 @@ command_result blueprint(color_ostream &out, vector ¶meters) } DFCoord start (x, y, z); DFCoord end (x + stoi(parameters[0]), y + stoi(parameters[1]), z + stoi(parameters[2])); - if (parameters.size() == 4) - return do_transform(start, end, parameters[3], DIG | BUILD | PLACE | QUERY); uint32_t option = 0; - if (cmd_option_exists(parameters, "dig")) - option |= DIG; - if (cmd_option_exists(parameters, "build")) - option |= BUILD; - if (cmd_option_exists(parameters, "place")) - option |= PLACE; - if (cmd_option_exists(parameters, "query")) - option |= QUERY; - return do_transform(start, end, parameters[3], option); + if (parameters.size() == 4) + { + option = DIG | BUILD | PLACE | QUERY; + } + else + { + if (cmd_option_exists(parameters, "dig")) + option |= DIG; + if (cmd_option_exists(parameters, "build")) + option |= BUILD; + if (cmd_option_exists(parameters, "place")) + option |= PLACE; + if (cmd_option_exists(parameters, "query")) + option |= QUERY; + } + std::ostringstream err; + DFHack::command_result result = do_transform(start, end, parameters[3], option, err); + if (result != CR_OK) + out.printerr("%s\n", err.str().c_str()); + return result; } static int create(lua_State *L, uint32_t options) { @@ -701,9 +735,12 @@ static int create(lua_State *L, uint32_t options) { luaL_argerror(L, 2, "invalid end position"); string filename(lua_tostring(L, 3)); - lua_pushboolean(L, do_transform(start, end, filename, options)); + std::ostringstream err; + DFHack::command_result result = do_transform(start, end, filename, options, err); + if (result != CR_OK) + luaL_error(L, "%s", err.str().c_str()); + lua_pushboolean(L, result); return 1; - } static int dig(lua_State *L) { From c19fc1f34903f15e915fa730af6d9ed53f5fc93c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 15 Jul 2020 21:35:21 -0700 Subject: [PATCH 2/5] gcc-4.8 compatibility --- library/LuaApi.cpp | 1 + library/include/modules/Filesystem.h | 1 + library/modules/Filesystem.cpp | 31 ++++++++++++++++++++++++++++ plugins/CMakeLists.txt | 3 +-- plugins/blueprint.cpp | 31 ++++++++++++++-------------- 5 files changed, 50 insertions(+), 17 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 1d127930e..b1dc3de0b 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2365,6 +2365,7 @@ static const LuaWrapper::FunctionReg dfhack_filesystem_module[] = { WRAPM(Filesystem, getcwd), WRAPM(Filesystem, chdir), WRAPM(Filesystem, mkdir), + WRAPM(Filesystem, mkdir_recursive), WRAPM(Filesystem, rmdir), WRAPM(Filesystem, exists), WRAPM(Filesystem, isfile), diff --git a/library/include/modules/Filesystem.h b/library/include/modules/Filesystem.h index bc7a02337..bc0953d27 100644 --- a/library/include/modules/Filesystem.h +++ b/library/include/modules/Filesystem.h @@ -149,6 +149,7 @@ namespace DFHack { DFHACK_EXPORT bool chdir (std::string path); DFHACK_EXPORT std::string getcwd (); DFHACK_EXPORT bool mkdir (std::string path); + DFHACK_EXPORT bool mkdir_recursive (std::string path); DFHACK_EXPORT bool rmdir (std::string path); DFHACK_EXPORT bool stat (std::string path, STAT_STRUCT &info); DFHACK_EXPORT bool exists (std::string path); diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 430e6351e..373b403c8 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -82,6 +82,37 @@ bool Filesystem::mkdir (std::string path) return fail == 0; } +static bool mkdir_recursive_impl (std::string path) +{ + size_t last_slash = path.find_last_of("/"); + if (last_slash != std::string::npos) + { + std::string parent_path = path.substr(0, last_slash); + bool parent_exists = mkdir_recursive_impl(parent_path); + if (!parent_exists) + { + return false; + } + } + return Filesystem::mkdir(path) || errno == EEXIST; +} + +bool Filesystem::mkdir_recursive (std::string path) +{ +#ifdef _WIN32 + // normalize to forward slashes + std::replace(path.begin(), path.end(), '\\', '/'); +#endif + + if (path.size() > PATH_MAX) + { + // path too long + return false; + } + + return mkdir_recursive_impl(path); +} + bool Filesystem::rmdir (std::string path) { int fail; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 3dc461ee9..678b7eec2 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -93,8 +93,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(automaterial automaterial.cpp) dfhack_plugin(automelt automelt.cpp) dfhack_plugin(autotrade autotrade.cpp) - # stdc++fs required for std::experimental::filesystem; once we move to c++17 this can be removed since it's in the STL there - dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua stdc++fs) + dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua) dfhack_plugin(building-hacks building-hacks.cpp LINK_LIBRARIES lua) dfhack_plugin(buildingplan buildingplan.cpp LINK_LIBRARIES buildingplan-lib) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index d7ce19334..4653b469c 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -3,7 +3,6 @@ //Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort #include -#include // This is just in c++17, but we're currently compiling with c++11 #include #include @@ -11,6 +10,7 @@ #include "LuaTools.h" #include "modules/Buildings.h" +#include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/MapCache.h" @@ -35,8 +35,6 @@ using std::pair; using namespace DFHack; using namespace df::enums; -namespace filesystem = std::experimental::filesystem; - DFHACK_PLUGIN("blueprint"); enum phase {DIG=1, BUILD=2, PLACE=4, QUERY=8}; @@ -562,13 +560,11 @@ string get_tile_query(df::building* b) return " "; } -void init_stream(ofstream &out, filesystem::path basename, std::string target) +void init_stream(ofstream &out, std::string basename, std::string target) { - filesystem::path out_path(basename); - out_path += "-"; - out_path += target; - out_path += ".csv"; - out.open(out_path, ofstream::trunc); + std::ostringstream out_path; + out_path << basename << "-" << target << ".csv"; + out.open(out_path.str(), ofstream::trunc); out << "#" << target << endl; } @@ -576,16 +572,21 @@ command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t ph { ofstream dig, build, place, query; - filesystem::path basename = "blueprints"; - basename /= name; - basename.make_preferred(); + std::string basename = "blueprints/" + name; + +#ifdef _WIN32 + // normalize to forward slashes + std::replace(basename.begin(), basename.end(), '\\', '/'); +#endif + + size_t last_slash = basename.find_last_of("/"); + std::string parent_path = basename.substr(0, last_slash); // create output directory if it doesn't already exist std::error_code ec; - if (!filesystem::create_directories(basename.parent_path(), ec) && ec) + if (!Filesystem::mkdir_recursive(parent_path)) { - err << "could not create output directory: " << basename.parent_path() - << " (" << ec.message() << ")"; + err << "could not create output directory: '" << parent_path << "'"; return CR_FAILURE; } From 1ae341b637375432c94aa29cf828c41a47d0322c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 15 Jul 2020 22:02:12 -0700 Subject: [PATCH 3/5] Check that we created a dir before we return true --- library/modules/Filesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 373b403c8..87001f6dd 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -94,7 +94,7 @@ static bool mkdir_recursive_impl (std::string path) return false; } } - return Filesystem::mkdir(path) || errno == EEXIST; + return (Filesystem::mkdir(path) || errno == EEXIST) && Filesystem::isdir(path); } bool Filesystem::mkdir_recursive (std::string path) From bb1fcff410a5af6964fc5d7c3fb2f69dc2bccbcb Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 16 Jul 2020 08:33:23 -0700 Subject: [PATCH 4/5] use FILENAME_MAX instead of PATH_MAX for compat --- library/modules/Filesystem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 87001f6dd..227bb57b6 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -104,7 +104,7 @@ bool Filesystem::mkdir_recursive (std::string path) std::replace(path.begin(), path.end(), '\\', '/'); #endif - if (path.size() > PATH_MAX) + if (path.size() > FILENAME_MAX) { // path too long return false; From 0afbe9d9319c6026025dbdf0ddf86e350699dfbb Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Thu, 16 Jul 2020 08:42:39 -0700 Subject: [PATCH 5/5] include algorithm on windows for std::replace --- library/modules/Filesystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index 227bb57b6..ddc091525 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -45,6 +45,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include #include #include "modules/Filesystem.h"