1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-06-30 22:14:58 +03:00

Tidied up inpolicy

This commit is contained in:
Graham Nelson 2019-02-27 10:22:00 +00:00
parent 5af2ac1396
commit 3271eae5f6
6 changed files with 246 additions and 112 deletions

View file

@ -6,11 +6,14 @@ this plan out.
@h Main routine.
@e SILENCE_CLSW
@e VERBOSE_CLSW
@e PROBLEMS_CLSW
@e ADVANCE_CLSW
=
int return_happy = TRUE, silence_mode = FALSE;
pathname *path_to_inpolicy = NULL; /* where we are installed */
pathname *path_to_inpolicy_materials = NULL; /* the materials pathname */
int return_happy = TRUE, silence_mode = FALSE, verbose_mode = FALSE;
int main(int argc, char **argv) {
Foundation::start();
@ -19,15 +22,22 @@ int main(int argc, char **argv) {
L"[[Purpose]]\n\n"
L"usage: inpolicy [options]\n");
CommandLine::declare_boolean_switch(SILENCE_CLSW, L"silence", 1,
L"print nothing unless there's something wrong");
CommandLine::declare_switch(PROBLEMS_CLSW, L"check-problems", 1,
L"check problem test case coverage");
CommandLine::declare_switch(ADVANCE_CLSW, L"advance-build", 2,
L"increment daily build code for web X");
CommandLine::declare_boolean_switch(SILENCE_CLSW, L"silence", 1,
L"print nothing unless there's something wrong");
CommandLine::declare_boolean_switch(VERBOSE_CLSW, L"verbose", 1,
L"explain what inpolicy is doing");
CommandLine::read(argc, argv, NULL, &Main::respond, &Main::disallow);
path_to_inpolicy = Pathnames::installation_path("INPOLICY_PATH", I"inpolicy");
path_to_inpolicy_materials = Pathnames::subfolder(path_to_inpolicy, I"Materials");
if (verbose_mode) PRINT("Installation path is %p\n", path_to_inpolicy);
Foundation::end();
if (return_happy) return 0; else return 1;
}
@ -39,6 +49,8 @@ void Main::disallow(int id, text_stream *arg, void *state) {
@
@d RUNTEST(Routine)
path_to_inpolicy = Pathnames::installation_path("INPOLICY_PATH", I"inpolicy");
path_to_inpolicy_materials = Pathnames::subfolder(path_to_inpolicy, I"Materials");
if (silence_mode) {
if (Routine(NULL) == FALSE) { return_happy = FALSE; Routine(STDERR); }
} else {
@ -51,5 +63,6 @@ void Main::respond(int id, int val, text_stream *arg, void *state) {
case ADVANCE_CLSW: Inversion::maintain(arg); break;
case PROBLEMS_CLSW: RUNTEST(Coverage::check); break;
case SILENCE_CLSW: silence_mode = val; break;
case VERBOSE_CLSW: verbose_mode = val; break;
}
}

View file

@ -4,21 +4,25 @@ To see which problem messages have test cases and which are linked
to the documentation.
@h Observation.
Problem messages are identified by their code-names, e.g., |PM_MisplacedFrom|;
those names should be unique, but any number of problems can instead be
marked with one of three special names.
@d CASE_EXISTS_PCON 0x00000001
@d DOC_MENTIONS_PCON 0x00000002
@d CODE_MENTIONS_PCON 0x00000004
@d IMPOSSIBLE_PCON 0x00000008
@d UNTESTABLE_PCON 0x00000010
@d NAMELESS_PCON 0x00000020
Problems can be mentioned in the code, in the documentation, or in the
set of Inform test cases.
@d CASE_EXISTS_PCON 0x00000001 /* mentioned in test cases */
@d DOC_MENTIONS_PCON 0x00000002 /* mentioned in documentation */
@d CODE_MENTIONS_PCON 0x00000004 /* mentioned in source code */
@d IMPOSSIBLE_PCON 0x00000008 /* this is |BelievedImpossible| */
@d UNTESTABLE_PCON 0x00000010 /* this is |Untestable| */
@d NAMELESS_PCON 0x00000020 /* this is |...| */
=
dictionary *problems_dictionary = NULL;
typedef struct known_problem {
struct text_stream *name;
int contexts_observed;
int contexts_observed_multiple_times;
int contexts_observed; /* bitmap of the above bits */
int contexts_observed_multiple_times; /* bitmap of the above bits */
MEMORY_MANAGEMENT
} known_problem;
@ -26,6 +30,8 @@ typedef struct known_problem {
and augment its bitmap of known contexts:
=
dictionary *problems_dictionary = NULL;
void Coverage::observe_problem(text_stream *name, int context) {
if (problems_dictionary == NULL)
problems_dictionary = Dictionaries::new(1000, FALSE);
@ -92,23 +98,29 @@ void Coverage::xref_harvester(text_stream *text, text_file_position *tfp, void *
}
@h Problems generated in the I7 source.
Which is to say, actually existing problem messages.
Which is to say, actually existing problem messages. Ideally, this code
should find the modules included in Inform in some more sophisticated way.
=
void Coverage::which_problems_exist(pathname *Workspace) {
Coverage::which_problems_exist_inner(
Pathnames::from_text(I"inform7"), Workspace);
Coverage::which_problems_exist_inner(
Pathnames::from_text(I"inform7/core-module"), Workspace);
Coverage::which_problems_exist_inner(
Pathnames::from_text(I"inform7/if-module"), Workspace);
Coverage::which_problems_exist_inner(
Pathnames::from_text(I"inform7/multimedia-module"), Workspace);
Coverage::which_problems_exist_inner(
Pathnames::from_text(I"inter/codegen-module"), Workspace);
void Coverage::which_problems_exist(void) {
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inform7"));
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inform7/core-module"));
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inform7/if-module"));
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inform7/multimedia-module"));
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inter/codegen-module"));
}
void Coverage::which_problems_exist_inner(pathname *D, pathname *Workspace) {
@ So now we have to read the contents page of a web, to see what section
files it contains:
=
typedef struct existence_state {
struct pathname *web_path;
struct pathname *chapter_path;
struct filename *section;
} existence_state;
void Coverage::which_problems_exist_inner(pathname *D) {
filename *C = Filenames::in_folder(D, I"Contents.w");
existence_state es;
es.web_path = D;
@ -117,12 +129,6 @@ void Coverage::which_problems_exist_inner(pathname *D, pathname *Workspace) {
&Coverage::section_harvester, NULL, &es);
}
typedef struct existence_state {
struct pathname *web_path;
struct pathname *chapter_path;
struct filename *section;
} existence_state;
void Coverage::section_harvester(text_stream *text, text_file_position *tfp, void *state) {
existence_state *es = (existence_state *) state;
match_results mr = Regexp::create_mr();
@ -143,6 +149,11 @@ void Coverage::section_harvester(text_stream *text, text_file_position *tfp, voi
Regexp::dispose_of(&mr);
}
@ So now we're working through individual section files. The exclusion of
the case called |sigil| throws out a macro definition in the source code,
not a specific problem case.
=
void Coverage::existence_harvester(text_stream *text, text_file_position *tfp, void *state) {
existence_state *es = (existence_state *) state;
match_results mr = Regexp::create_mr();
@ -151,64 +162,78 @@ void Coverage::existence_harvester(text_stream *text, text_file_position *tfp, v
WRITE_TO(text, "%S%S", mr.exp[0], mr.exp[2]);
TEMPORARY_TEXT(name);
Str::copy(name, mr.exp[1]);
if (Str::eq_wide_string(name, L"sigil")) break;
if (Str::eq(name, I"sigil")) break;
int context = CODE_MENTIONS_PCON;
if (Str::eq_wide_string(name, L"BelievedImpossible")) { context = IMPOSSIBLE_PCON; @<Suffix location@> }
else if (Str::eq_wide_string(name, L"Untestable")) { context = UNTESTABLE_PCON; @<Suffix location@> }
else if (Str::eq_wide_string(name, L"...")) { context = NAMELESS_PCON; @<Suffix location@> }
if (Str::eq_wide_string(name, L"BelievedImpossible")) @<Suffix location@>
if (Str::eq(name, I"BelievedImpossible")) {
context = IMPOSSIBLE_PCON;
WRITE_TO(name, "_%f_line%d", es->section, tfp->line_count);
} else if (Str::eq(name, I"Untestable")) {
context = UNTESTABLE_PCON;
WRITE_TO(name, "_%f_line%d", es->section, tfp->line_count);
} else if (Str::eq(name, I"...")) {
context = NAMELESS_PCON;
WRITE_TO(name, "_%f_line%d", es->section, tfp->line_count);
}
Coverage::observe_problem(name, context);
DISCARD_TEXT(name);
}
Regexp::dispose_of(&mr);
}
@<Suffix location@> =
WRITE_TO(name, "_%f_line%d", es->section, tfp->line_count);
@h Checking.
So the actual policy-enforcement routine is here:
=
int observations_made = FALSE;
int Coverage::check(OUTPUT_STREAM) {
if (observations_made == FALSE) {
pathname *P =
Pathnames::subfolder(Pathnames::from_text(I"inpolicy"), I"Workspace");
Coverage::which_problems_have_test_cases(P);
Coverage::which_problems_are_referenced(P);
Coverage::which_problems_exist(P);
@<Perform the observations@>;
observations_made = TRUE;
}
int all_is_well = TRUE;
@<Report and decide how grave the situation is@>;
if (all_is_well) WRITE("All is well.\n");
else WRITE("This needs attention.\n");
WRITE("\n");
return all_is_well;
}
@<Perform the observations@> =
Coverage::which_problems_have_test_cases(path_to_inpolicy_materials);
Coverage::which_problems_are_referenced(path_to_inpolicy_materials);
Coverage::which_problems_exist();
@ Okay, so that's all of the scanning done; now to report on it.
@<Report and decide how grave the situation is@> =
WRITE("%d problem name(s) have been observed:\n", NUMBER_CREATED(known_problem)); INDENT;
WRITE("Problems actually existing (the source code refers to them):\n"); INDENT;
Coverage::cite(OUT, CODE_MENTIONS_PCON, 0, CODE_MENTIONS_PCON,
L"are named and in principle testable");
I"are named and in principle testable");
if (Coverage::cite(OUT, 0, CODE_MENTIONS_PCON, CODE_MENTIONS_PCON,
L"are named more than once:") > 0) {
I"are named more than once:") > 0) {
all_is_well = FALSE;
Coverage::list(OUT, 0, CODE_MENTIONS_PCON, CODE_MENTIONS_PCON);
}
Coverage::cite(OUT, IMPOSSIBLE_PCON, 0, IMPOSSIBLE_PCON,
L"are 'BelievedImpossible', that is, no known source text causes them");
I"are 'BelievedImpossible', that is, no known source text causes them");
Coverage::cite(OUT, UNTESTABLE_PCON, 0, UNTESTABLE_PCON,
L"are 'Untestable', that is, not mechanically testable");
I"are 'Untestable', that is, not mechanically testable");
Coverage::cite(OUT, NAMELESS_PCON, 0, NAMELESS_PCON,
L"are '...', that is, they need to be give a name and a test case");
I"are '...', that is, they need to be give a name and a test case");
OUTDENT;
WRITE("Problems which should have test cases:\n"); INDENT;
Coverage::cite(OUT, CASE_EXISTS_PCON+CODE_MENTIONS_PCON, 0, CASE_EXISTS_PCON+CODE_MENTIONS_PCON,
L"have test cases");
I"have test cases");
if (Coverage::cite(OUT, CASE_EXISTS_PCON+CODE_MENTIONS_PCON, 0, CODE_MENTIONS_PCON,
L"have no test case yet:") > 0) {
I"have no test case yet:") > 0) {
Coverage::list(OUT, CASE_EXISTS_PCON+CODE_MENTIONS_PCON, 0, CODE_MENTIONS_PCON);
}
if (Coverage::cite(OUT, CASE_EXISTS_PCON+CODE_MENTIONS_PCON, 0, CASE_EXISTS_PCON,
L"are spurious test cases, since no such problems exist:") > 0) {
I"are spurious test cases, since no such problems exist:") > 0) {
all_is_well = FALSE;
Coverage::list(OUT, CASE_EXISTS_PCON+CODE_MENTIONS_PCON, 0, CASE_EXISTS_PCON);
}
@ -216,45 +241,39 @@ int Coverage::check(OUTPUT_STREAM) {
WRITE("Problems which are cross-referenced in 'Writing with Inform':\n"); INDENT;
Coverage::cite(OUT, CODE_MENTIONS_PCON+DOC_MENTIONS_PCON, 0, CODE_MENTIONS_PCON+DOC_MENTIONS_PCON,
L"are cross-referenced");
I"are cross-referenced");
if (Coverage::cite(OUT, 0, DOC_MENTIONS_PCON, DOC_MENTIONS_PCON,
L"are cross-referenced more than once:") > 0) {
I"are cross-referenced more than once:") > 0) {
all_is_well = FALSE;
Coverage::list(OUT, 0, DOC_MENTIONS_PCON, DOC_MENTIONS_PCON);
}
if (Coverage::cite(OUT, CODE_MENTIONS_PCON+DOC_MENTIONS_PCON, 0, DOC_MENTIONS_PCON,
L"are spurious references, since no such problems exist:") > 0) {
I"are spurious references, since no such problems exist:") > 0) {
all_is_well = FALSE;
Coverage::list(OUT, CODE_MENTIONS_PCON+DOC_MENTIONS_PCON, 0, DOC_MENTIONS_PCON);
}
OUTDENT;
OUTDENT;
if (all_is_well) WRITE("All is well.\n");
else WRITE("This needs attention.\n");
WRITE("\n");
return all_is_well;
}
int Coverage::cite(OUTPUT_STREAM, int mask, int mask2, int val, wchar_t *message) {
@ =
int Coverage::cite(OUTPUT_STREAM, int mask, int mask2, int val, text_stream *message) {
int N = 0;
known_problem *KP;
LOOP_OVER(KP, known_problem) {
if ((KP->contexts_observed & mask) == val) N++;
if ((KP->contexts_observed_multiple_times & mask2) == val) N++;
}
if ((N>0) && (message)) WRITE("%d problem(s) %w\n", N, message);
if ((N>0) && (message)) WRITE("%d problem(s) %S\n", N, message);
return N;
}
void Coverage::list(OUTPUT_STREAM, int mask, int mask2, int val) {
INDENT;
known_problem *KP;
LOOP_OVER(KP, known_problem) {
LOOP_OVER(KP, known_problem)
if (((KP->contexts_observed & mask) == val) ||
((KP->contexts_observed_multiple_times & mask2) == val)) {
WRITE("%S\n", KP->name);
}
}
OUTDENT;
}

View file

@ -3,6 +3,10 @@
To update the build number(s) and versions for the intools.
@h The build-numbers file.
The scheme here is that each project can optionally contain a UTF-8 encoded
text file called |versions.txt|, which lists all of its version history;
a line of that file corresponds to a "version". Out of these versions, one
must be marked as the current version.
=
typedef struct project {
@ -11,7 +15,7 @@ typedef struct project {
int manual_updating;
struct text_stream *web;
struct filename *versions_file;
struct version *first_version;
struct linked_list *versions; /* of |version| */
struct version *current_version;
struct text_stream *conts;
MEMORY_MANAGEMENT
@ -23,42 +27,58 @@ typedef struct version {
struct text_stream *build_code;
struct text_stream *date;
struct text_stream *notes;
int unstable;
struct version *next_version;
MEMORY_MANAGEMENT
} version;
@ And here we turn a named web into its |project| structure. We print its
current version number when we first load a project in:
=
project *Inversion::read(text_stream *web) {
project *P;
LOOP_OVER(P, project)
if (Str::eq(web, P->web))
return P;
LOOP_OVER(P, project) if (Str::eq(web, P->web)) return P;
P = CREATE(project);
P->sync_line = Str::new();
P->sync_to = NULL;
P->manual_updating = TRUE;
P->web = Str::duplicate(web);
P->first_version = NULL;
P->versions = NEW_LINKED_LIST(version);
P->current_version = NULL;
P->versions_file = Filenames::in_folder(Pathnames::from_text(web), I"versions.txt");
TextFiles::read(P->versions_file, FALSE, "unable to read roster of version numbers", TRUE,
&Inversion::version_harvester, NULL, P);
P->conts = Str::new();
P->versions_file = Filenames::in_folder(Pathnames::from_text(web), I"versions.txt");
@<Read in the versions file@>;
@<Print the current version number@>;
return P;
}
@<Print the current version number@> =
if (P->current_version == NULL) {
Errors::with_text("warning: no version marked as current", web);
} else {
PRINT("%S: %S %S (build %S)\n", web,
P->current_version->name, P->current_version->number, P->current_version->build_code);
}
return P;
}
@<Read in the versions file@> =
TextFiles::read(P->versions_file, FALSE, "unable to read roster of version numbers", TRUE,
&Inversion::version_harvester, NULL, P);
@ A version file contains lines which can either be a special command, or
give details of a version. The commands are |Automatic| or |Manual| (the
latter is the default), or |Sync to W|, where |W| is another project.
(All of this is infrastructure left over from when the Inform tools were
syncing version numbers to the main Inform 7 version number: with the
transition to Github, this scheme was dropped.)
=
void Inversion::version_harvester(text_stream *text, text_file_position *tfp, void *state) {
project *P = (project *) state;
match_results mr = Regexp::create_mr();
if (Str::len(text) == 0) return;
if (Regexp::match(&mr, text, L"Automatic")) {
P->manual_updating = FALSE;
} else if (Regexp::match(&mr, text, L"Manual")) {
P->manual_updating = TRUE;
} else if (Regexp::match(&mr, text, L"Sync to (%c*)")) {
P->sync_to = Inversion::read(mr.exp[0]);
P->manual_updating = FALSE;
@ -70,39 +90,39 @@ void Inversion::version_harvester(text_stream *text, text_file_position *tfp, vo
V->date = Str::duplicate(mr.exp[3]);
V->notes = Str::duplicate(mr.exp[4]);
if (Str::get_first_char(V->build_code) == '*') {
V->unstable = TRUE;
Str::delete_first_character(V->build_code);
P->current_version = V;
} else V->unstable = FALSE;
if (P->first_version == NULL) P->first_version = V;
else {
version *W = P->first_version;
while ((W) && (W->next_version)) W = W->next_version;
W->next_version = V;
}
ADD_TO_LINKED_LIST(V, version, P->versions);
} else {
Errors::in_text_file("can't parse version line", tfp);
}
Regexp::dispose_of(&mr);
}
@ The following then writes back the versions file, following a version
increment:
=
void Inversion::write(project *P) {
text_stream vr_stream;
text_stream *OUT = &vr_stream;
if (Streams::open_to_file(OUT, P->versions_file, UTF8_ENC) == FALSE)
Errors::fatal_with_file("can't write versions file", P->versions_file);
if (P->sync_to) WRITE("Sync to %S\n", P->sync_to->web);
for (version *V = P->first_version; V; V = V->next_version) {
WRITE("%S\t%S\t",
V->name, V->number);
if (V->unstable) WRITE("*");
WRITE("%S\t%S\t%S\n",
V->build_code, V->date, V->notes);
else if (P->manual_updating) WRITE("Manual\n");
else WRITE("Automatic\n");
version *V;
LOOP_OVER_LINKED_LIST(V, version, P->versions) {
WRITE("%S\t%S\t", V->name, V->number);
if (V == P->current_version) WRITE("*");
WRITE("%S\t%S\t%S\n", V->build_code, V->date, V->notes);
}
Streams::close(OUT);
}
@h Updating.
The standard date format we use is "26 February 2018".
=
int Inversion::dated_today(project *P, text_stream *dateline) {
@ -116,6 +136,13 @@ int Inversion::dated_today(project *P, text_stream *dateline) {
return rv;
}
@ Here we read the Inform four-character code, e.g., |3Q27|, and increase it
by one. The two-digit code at the back is incremented, but rolls around from
|99| to |01|, in which case the letter is advanced, except that |I| and |O|
are skipped, and if the letter passes |Z| then it rolls back around to |A|
and the initial digit is incremented.
=
void Inversion::increment(project *P) {
if (P->current_version == NULL) return;
text_stream *T = P->current_version->build_code;
@ -145,6 +172,8 @@ void Inversion::increment(project *P) {
}
@h Imposition.
When we impose a new version number on a web that has a contents page, we
update the metadata in that contents page.
=
void Inversion::impose(project *P) {
@ -179,6 +208,27 @@ void Inversion::impose_helper(text_stream *text, text_file_position *tfp, void *
Regexp::dispose_of(&mr);
}
@h Daily build maintenance.
=
void Inversion::maintain(text_stream *web) {
project *P = Inversion::read(web);
if (Inversion::needs_update(P)) {
Inversion::write(P);
Inversion::impose(P);
if ((Str::eq(web, I"inform7")) && (P->current_version)) {
filename *F = Filenames::from_text(I"build-code.mk");
text_stream as_stream;
text_stream *OUT = &as_stream;
if (Streams::open_to_file(OUT, F, UTF8_ENC) == FALSE)
Errors::fatal_with_file("unable to write archive settings", F);
WRITE("BUILDCODE = %S\n", P->current_version->build_code);
Streams::close(OUT);
}
}
}
@ =
int Inversion::needs_update(project *P) {
int rv = FALSE;
if ((P->manual_updating == FALSE) && (P->current_version)) {
@ -204,23 +254,3 @@ int Inversion::needs_update(project *P) {
}
return rv;
}
@h Daily build maintenance.
=
void Inversion::maintain(text_stream *web) {
project *P = Inversion::read(web);
if (Inversion::needs_update(P)) {
Inversion::write(P);
Inversion::impose(P);
if ((Str::eq(web, I"inform7")) && (P->current_version)) {
filename *F = Filenames::from_text(I"build-code.mk");
text_stream as_stream;
text_stream *OUT = &as_stream;
if (Streams::open_to_file(OUT, F, UTF8_ENC) == FALSE)
Errors::fatal_with_file("unable to write archive settings", F);
WRITE("BUILDCODE = %S\n", P->current_version->build_code);
Streams::close(OUT);
}
}
}

View file

@ -12,8 +12,13 @@ Build Date: 10 February 2019
Import: foundation
Chapter 1: What It Does
Preliminaries
Using Inpolicy
Chapter 1: Setting Up
Basics
Main
Chapter 2: Policies
Problem Coverage
Version Numbering

View file

@ -0,0 +1,66 @@
Using Inpolicy.
A very short guide to a very small program.
@h What Inpolicy is.
Inpolicy is a command-line tool whose sole purpose is to help keep the
Inform 7 source code tidy. Unlike Inweb, Intest and Indoc, this tool
can't sensibly be used for any project other than Inform.
If you have compiled the standard distribution of the command-line tools
for Inform then the Inpolicy executable will be at |inpolicy/Tangled/inpolicy|.
Usage is very simple:
|$ inpolicy/Tangled/inpolicy POLICY|
where |POLICY| is whatever we want to check. There are very few at present;
in some ways this program is a placeholder for future tightening-up of the
style rules.
@ When it runs, Inpolicy needs to know where it is installed in the file
system. There is no completely foolproof, cross-platform way to know this
(on some Unixes, a program cannot determine its own location), so Inpolicy
decides by the following set of rules:
(a) If the user, at the command line, specified |-at P|, for some path
|P|, then we use that.
(b) Otherwise, if the host operating system can indeed tell us where the
executable is, we use that. This is currently implemented only on MacOS,
Windows and Linux.
(c) Otherwise, if the environment variable |$INPOLICY_PATH| exists and is
non-empty, we use that.
(d) And if all else fails, we assume that the location is |inpolicy|, with
respect to the current working directory.
If you're not sure what Inpolicy has decided and suspect it may be wrong,
running Inpolicy with the |-verbose| switch will cause it to print its belief
about its location as it starts up.
@h Policies.
|-check-problems| makes a survey of (a) all of the Problem messages issued
within the Inform 7 compiler, (b) all of the Problem test cases, and (c) all
of the advisory references to Problems in the Inform documentation, and
attempts to match these up. It prints out a report, and concludes with either
"All is well" or a recommendation for changes. For example:
|1009 problem name(s) have been observed:|
| Problems actually existing (the source code refers to them):|
| 906 problem(s) are named and in principle testable|
| 81 problem(s) are 'BelievedImpossible', that is, no known source text causes them|
| 14 problem(s) are 'Untestable', that is, not mechanically testable|
| 8 problem(s) are '...', that is, they need to be give a name and a test case|
| Problems which should have test cases:|
| 904 problem(s) have test cases|
| 2 problem(s) have no test case yet:|
| PM_SuperfluousOf|
| PM_MisplacedFrom|
| Problems which are cross-referenced in 'Writing with Inform':|
| 483 problem(s) are cross-referenced|
|All is well.|
As this example report shows, small sins are forgiven.
@ |-advance-build W| tries to advance the build code for the web |W|. In
practice, this is used only when the web is |inform7|. The build code is
the familiar (to Inform users) four-character version number, such as
|3K27| or |6P98|.

View file

@ -345,6 +345,7 @@ endef
pages:
$(INWEBX) inblorb -weave-docs -weave-into docs/inblorb
$(INWEBX) indoc -weave-docs -weave-into docs/indoc
$(INWEBX) inpolicy -weave-docs -weave-into docs/inpolicy
# -----------------------------------------------------------------------------
# Target "clean"