Merge pull request #2336 from myk002/myk_blueprint_group

[blueprint] support "group" file split strategy
develop
Myk 2022-10-14 13:15:24 -07:00 committed by GitHub
commit 5f5b4691a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 78 additions and 37 deletions

@ -0,0 +1,6 @@
#query label(query)
,,"{givename name=""foo dumper""}"
,,"{givename name=""foo""}"
Can't render this file because it has a wrong number of fields in line 5.

@ -43,6 +43,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- `blueprint`: record stockpile/building/zone names in blueprints
- `blueprint`: record room sizes in blueprints
- `blueprint`: generate meta blueprints to reduce the number of blueprints you have to apply
- `blueprint`: support splitting the output file into phases grouped by when they can be applied
- `blueprint`: when splitting output files, number them so they sort into the order you should apply them in
- `ls`: indent tag listings and wrap them in the right column for better readability
- `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output.
- `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down

@ -146,5 +146,10 @@ The ``--splitby`` flag can take any of the following values:
``none``
Writes all blueprints into a single file. This is the standard format for
quickfort fortress blueprint bundles and is the default.
``group``
Creates one file per group of blueprints that can be played back at the same
time (without have to unpause the game and let dwarves fulfill jobs between
blueprint runs).
``phase``
Creates a separate file for each phase.
Creates a separate file for each phase. Implies ``--nometa`` since meta
blueprints can't combine blueprints that are in separate files.

@ -5,8 +5,6 @@
* Written by cdombroski.
*/
#include <algorithm>
#include <regex>
#include <sstream>
#include <unordered_map>
@ -1011,11 +1009,23 @@ static const char * get_tile_zone(const df::coord &pos,
return add_expansion_syntax(zone, get_zone_keys(zone));
}
static string csv_sanitize(const string &str) {
static const std::regex pattern("\"");
static const string replacement("\"\"");
// surrounds the given string in quotes and replaces internal double quotes (")
// with double double quotes ("") (as per the csv spec)
static string csv_quote(const string &str) {
std::ostringstream outstr;
outstr << "\"";
size_t start = 0;
auto end = str.find('"');
while (end != std::string::npos) {
outstr << str.substr(start, end - start);
outstr << "\"\"";
start = end + 1;
end = str.find('"', start);
}
outstr << str.substr(start, end) << "\"";
return std::regex_replace(str, pattern, replacement);
return outstr.str();
}
static const char * get_tile_query(const df::coord &pos,
@ -1042,11 +1052,11 @@ static const char * get_tile_query(const df::coord &pos,
std::ostringstream str;
if (bld_name.size())
str << "{givename name=\"" << csv_sanitize(bld_name) << "\"}";
str << "{givename name=" + csv_quote(bld_name) + "}";
if (zone_name.size())
str << "{namezone name=\"" << csv_sanitize(zone_name) << "\"}";
str << "{namezone name=" + csv_quote(zone_name) + "}";
return cache(str);
return cache(csv_quote(str.str()));
}
static const char * get_tile_rooms(const df::coord &, const tile_context &ctx) {
@ -1095,11 +1105,12 @@ static bool create_output_dir(color_ostream &out,
static bool get_filename(string &fname,
color_ostream &out,
blueprint_options opts, // copy because we can't const
const string &phase) {
const string &phase,
int32_t ordinal) {
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!lua_checkstack(L, 3) ||
if (!lua_checkstack(L, 4) ||
!Lua::PushModulePublic(
out, L, "plugins.blueprint", "get_filename")) {
out.printerr("Failed to load blueprint Lua code\n");
@ -1108,8 +1119,9 @@ static bool get_filename(string &fname,
Lua::Push(L, &opts);
Lua::Push(L, phase);
Lua::Push(L, ordinal);
if (!Lua::SafeCall(out, L, 2, 1)) {
if (!Lua::SafeCall(out, L, 3, 1)) {
out.printerr("Failed Lua call to get_filename\n");
return false;
}
@ -1228,9 +1240,9 @@ static bool write_blueprint(color_ostream &out,
std::map<string, ofstream*> &output_files,
const blueprint_options &opts,
const blueprint_processor &processor,
bool pretty) {
bool pretty, int32_t ordinal) {
string fname;
if (!get_filename(fname, out, opts, processor.phase))
if (!get_filename(fname, out, opts, processor.phase, ordinal))
return false;
if (!output_files.count(fname))
output_files[fname] = new ofstream(fname, ofstream::trunc);
@ -1249,9 +1261,10 @@ static bool write_blueprint(color_ostream &out,
static void write_meta_blueprint(color_ostream &out,
std::map<string, ofstream*> &output_files,
const blueprint_options &opts,
const std::vector<string> & meta_phases) {
const std::vector<string> & meta_phases,
int32_t ordinal) {
string fname;
get_filename(fname, out, opts, meta_phases.front());
get_filename(fname, out, opts, meta_phases.front(), ordinal);
ofstream &ofile = *output_files[fname];
ofile << "#meta label(";
@ -1285,7 +1298,7 @@ static void add_processor(vector<blueprint_processor> &processors,
static bool do_transform(color_ostream &out,
const df::coord &start, const df::coord &end,
blueprint_options &opts,
blueprint_options opts, // copy so we can munge it
vector<string> &filenames) {
// empty map instances to pass to emplace() below
static const bp_area EMPTY_AREA;
@ -1368,16 +1381,26 @@ static bool do_transform(color_ostream &out,
if (meta_phases.size() <= 1)
opts.nometa = true;
bool in_meta = false;
int32_t ordinal = 0;
std::map<string, ofstream*> output_files;
for (blueprint_processor &processor : processors) {
if (processor.mapdata.empty() && !processor.force_create)
continue;
if (!write_blueprint(out, output_files, opts, processor, pretty))
bool meta_phase = is_meta_phase(out, opts, processor.phase);
if (!in_meta)
++ordinal;
if (in_meta && !meta_phase) {
write_meta_blueprint(out, output_files, opts, meta_phases, ordinal);
++ordinal;
}
in_meta = meta_phase;
if (!write_blueprint(out, output_files, opts, processor, pretty,
ordinal))
break;
}
if (!opts.nometa) {
write_meta_blueprint(out, output_files, opts, meta_phases);
}
if (in_meta)
write_meta_blueprint(out, output_files, opts, meta_phases, ordinal);
for (auto &it : output_files) {
filenames.push_back(it.first);

@ -31,6 +31,7 @@ valid_formats = utils.invert(valid_formats_list)
local valid_split_strategies_list = {
'none',
'group',
'phase',
}
valid_split_strategies = utils.invert(valid_split_strategies_list)
@ -143,6 +144,10 @@ local function process_args(opts, args)
return
end
if opts.split_strategy == 'phase' then
opts.nometa = true
end
return positionals
end
@ -200,7 +205,7 @@ function is_meta_phase(opts, phase)
end
-- returns the name of the output file for the given context
function get_filename(opts, phase)
function get_filename(opts, phase, ordinal)
local fullname = 'blueprints/' .. opts.name
local _,_,basename = fullname:find('/([^/]+)/?$')
if not basename then
@ -210,14 +215,13 @@ function get_filename(opts, phase)
if fullname:endswith('/') then
fullname = fullname .. basename
end
if opts.split_strategy == 'phase' then
if is_meta_phase(opts, phase) then
phase = 'meta'
end
return ('%s-%s.csv'):format(fullname, phase)
if opts.split_strategy == 'none' then
return ('%s.csv'):format(fullname)
end
if is_meta_phase(opts, phase) then
phase = 'meta'
end
-- no splitting
return ('%s.csv'):format(fullname)
return ('%s-%d-%s.csv'):format(fullname, ordinal, phase)
end
-- compatibility with old exported API.

@ -1 +1 @@
Subproject commit 6f0d7ea22edcd68a1716d843e3ff439a75f23c35
Subproject commit 0be5cc0eaf625029ef26392224db5c3d2822a63d

@ -101,7 +101,7 @@ function test.parse_gui_commandline()
opts = {}
b.parse_gui_commandline(opts, {'--splitby', 'phase'})
expect.table_eq({auto_phase=true, format='minimal', split_strategy='phase',
name='blueprint'},
name='blueprint', nometa=true},
opts)
expect.error_match('unknown split_strategy',
@ -241,16 +241,16 @@ end
function test.get_filename()
local opts = {name='a', split_strategy='none'}
expect.eq('blueprints/a.csv', b.get_filename(opts, 'dig'))
expect.eq('blueprints/a.csv', b.get_filename(opts, 'dig', 1))
opts = {name='a/', split_strategy='none'}
expect.eq('blueprints/a/a.csv', b.get_filename(opts, 'dig'))
expect.eq('blueprints/a/a.csv', b.get_filename(opts, 'dig', 1))
opts = {name='a', split_strategy='phase'}
expect.eq('blueprints/a-dig.csv', b.get_filename(opts, 'dig'))
expect.eq('blueprints/a-1-dig.csv', b.get_filename(opts, 'dig', 1))
opts = {name='a/', split_strategy='phase'}
expect.eq('blueprints/a/a-dig.csv', b.get_filename(opts, 'dig'))
expect.eq('blueprints/a/a-5-dig.csv', b.get_filename(opts, 'dig', 5))
expect.error_match('could not parse basename', function()
b.get_filename({name='', split_strategy='none'})

@ -244,7 +244,7 @@ end
local function run_blueprint(basename, spec, pos)
local args = {tostring(spec.width), tostring(spec.height),
tostring(-spec.depth), output_dir..basename,
get_cursor_arg(pos), '-tphase', '--nometa'}
get_cursor_arg(pos), '-tphase'}
local playback_start_arg = get_playback_start_arg(spec.start)
if playback_start_arg then
table.insert(args, playback_start_arg)