The inprint build subcommand builds a file tree from a blueprint.
enumerate BUILD_CLSUB
enumerate IN_CLSW
enumerate ARCHIVES_CLSW
void InprintBuild::cli(void) { CommandLine::begin_subcommand(BUILD_CLSUB, U"build"); CommandLine::declare_heading( U"Usage: inprint build FILE [-in DIRECTORY]\n\n" U"Builds a file tree from the blueprint in FILE in the current working directory, " U"or else inside DIRECTORY if '-in' is specified."); CommandLine::declare_switch(IN_CLSW, U"in", 2, U"directory in which to build file tree"); CommandLine::declare_switch(ARCHIVES_CLSW, U"archives", 2, U"directory in which to find blueprint"); CommandLine::end_subcommand(); }
typedef struct inprint_build_settings { struct pathname *in; struct pathname *archives; } inprint_build_settings; void InprintBuild::initialise(inprint_build_settings *bs) { bs->in = NULL; bs->archives = NULL; } int InprintBuild::switch(inprint_instructions *ins, int id, int val, text_stream *arg) { inprint_build_settings *bs = &(ins->build_settings); switch (id) { case IN_CLSW: bs->in = Pathnames::from_text(arg); return TRUE; case ARCHIVES_CLSW: bs->archives = Pathnames::from_text(arg); return TRUE; } return FALSE; }
- The structure inprint_build_settings is private to this section.
void InprintBuild::run(inprint_instructions *ins) { inprint_build_settings *bs = &(ins->build_settings); if (ins->temp_path_setting) Errors::fatal_with_path("this is a directory, not a blueprint file", ins->temp_path_setting); if (ins->temp_file_setting == NULL) Errors::fatal("no blueprint file given"); if (bs->archives) { ins->temp_file_setting = Filenames::in(bs->archives, Filenames::get_leafname(ins->temp_file_setting)); } build_reader br; Initialise the build reader state (except for file_out)3.2; TextFiles::read(ins->temp_file_setting, TRUE, "unable to open blueprint", TRUE, InprintBuild::reader, NULL, (void *) &br); Check sanity of the final state3.3; }
§3.1. We will walk through the blueprint one line at a time, constructing contents in our directory to match. That means we need to keep track of what we're doing: this is the state preserved between lines.
typedef struct build_reader { int begin_found; int end_found; struct text_stream *report; struct filename *file_being_written; int lines_written; struct text_stream file_out; /* note: not a pointer */ int lc; struct pathname *to; } build_reader;
- The structure build_reader is accessed in 4/fsm, 5/mrk, 5/mpi2, 5/mr, 5/im, 2/ids and here.
§3.2. Initialise the build reader state (except for file_out)3.2 =
br.begin_found = FALSE; br.end_found = FALSE; br.report = STDOUT; if (silent_mode) br.report = NULL; br.file_being_written = NULL; br.lines_written = 0; br.lc = 0; br.to = bs->in;
- This code is used in §3.
§3.3. Check sanity of the final state3.3 =
if (br.file_being_written) Errors::at_position("ended in mid-file", ins->temp_file_setting, br.lc); else if (br.end_found == FALSE) Errors::at_position("ended without 'end'", ins->temp_file_setting, br.lc);
- This code is used in §3.
void InprintBuild::reader(text_stream *line, text_file_position *tfp, void *void_br) { build_reader *br = (build_reader *) void_br; text_stream *OUT = br->report; br->lc++; int from = -1; if (Str::get_at(line, 0) == '\t') from = 1; else if (Str::begins_with(line, I" ")) from = 4; else if ((Str::is_whitespace(line)) && (br->file_being_written)) from = 0; if (from >= 0) This is a tab-indented line and must be textual file contents4.1 else { Str::trim_all_white_space_at_end(line); if (br->file_being_written) Close the current textual file being written4.2; Manage the begin and end markers4.3; match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, line, U"directory: *(%c+)")) { Create a subdirectory4.4; } else if (Regexp::match(&mr, line, U"opaque file: *(%c+)")) { Create a dummy file to represent something opaque4.5; } else if (Regexp::match(&mr, line, U"file: *(%c+)")) { Open a new textual file to be written4.6; } else { Errors::in_text_file("unknown command in blueprint file", tfp); return; } Regexp::dispose_of(&mr); } }
§4.1. This is a tab-indented line and must be textual file contents4.1 =
if (br->file_being_written) { if (br->lines_written > 0) PUT_TO(&(br->file_out), '\n'); for (int i=from; i<Str::len(line); i++) PUT_TO(&(br->file_out), Str::get_at(line, i)); br->lines_written++; } else { Errors::in_text_file("file content occurs outside of file", tfp); return; }
- This code is used in §4.
§4.2. Close the current textual file being written4.2 =
STREAM_CLOSE(&(br->file_out)); WRITE("%d line(s) written to %f\n", br->lines_written, br->file_being_written); br->file_being_written = NULL; br->lines_written = 0;
- This code is used in §4.
§4.3. Manage the begin and end markers4.3 =
if (br->end_found) { if (Str::len(line) == 0) return; Errors::in_text_file("material after end", tfp); return; } if (Str::eq(line, I"begin")) { if (br->begin_found) { Errors::in_text_file("begin occurs twice", tfp); return; } br->begin_found = TRUE; return; } if (Str::eq(line, I"end")) { if (br->end_found) { Errors::in_text_file("end occurs twice", tfp); return; } br->end_found = TRUE; return; } if (br->begin_found == FALSE) { Errors::in_text_file("material without begin", tfp); return; }
- This code is used in §4.
§4.4. Create a subdirectory4.4 =
pathname *P = Pathnames::from_text_relative(br->to, mr.exp[0]); if (Pathnames::create_in_file_system(P)) WRITE("created directory %p\n", P); else Errors::fatal_with_path("unable to create directory", P);
- This code is used in §4.
§4.5. Obviously, if the blueprint tells us only that there should be a file
called icon.jpg, say, then we can't know what to put in it. So we create
a small bogus file of the right name. This at least means that inprint build
is a one-sided inverse to inprint draw: building from a blueprint and
then drawing a new blueprint from the result recreates the original blueprint.
Create a dummy file to represent something opaque4.5 =
filename *F = Filenames::from_text_relative(br->to, mr.exp[0]); text_stream *TO = &(br->file_out); if (STREAM_OPEN_TO_FILE(TO, F, UTF8_ENC) == FALSE) Errors::fatal_with_file("unable to write opaque file", F); WRITE_TO(TO, "This is a dummy file '%S' created by inprint.\n", mr.exp[0]); STREAM_CLOSE(&(br->file_out)); WRITE("wrote opaque file with dummy contents %f\n", F);
- This code is used in §4.
§4.6. Open a new textual file to be written4.6 =
br->file_being_written = Filenames::from_text_relative(br->to, mr.exp[0]); text_stream *TO = &(br->file_out); if (STREAM_OPEN_TO_FILE(TO, br->file_being_written, UTF8_ENC) == FALSE) Errors::fatal_with_file("unable to write file", br->file_being_written);
- This code is used in §4.