mirror of
https://github.com/ganelson/inform.git
synced 2024-07-02 23:14:57 +03:00
Tidied up inpolicy
This commit is contained in:
parent
5af2ac1396
commit
3271eae5f6
|
@ -6,11 +6,14 @@ this plan out.
|
||||||
@h Main routine.
|
@h Main routine.
|
||||||
|
|
||||||
@e SILENCE_CLSW
|
@e SILENCE_CLSW
|
||||||
|
@e VERBOSE_CLSW
|
||||||
@e PROBLEMS_CLSW
|
@e PROBLEMS_CLSW
|
||||||
@e ADVANCE_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) {
|
int main(int argc, char **argv) {
|
||||||
Foundation::start();
|
Foundation::start();
|
||||||
|
@ -19,15 +22,22 @@ int main(int argc, char **argv) {
|
||||||
L"[[Purpose]]\n\n"
|
L"[[Purpose]]\n\n"
|
||||||
L"usage: inpolicy [options]\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,
|
CommandLine::declare_switch(PROBLEMS_CLSW, L"check-problems", 1,
|
||||||
L"check problem test case coverage");
|
L"check problem test case coverage");
|
||||||
CommandLine::declare_switch(ADVANCE_CLSW, L"advance-build", 2,
|
CommandLine::declare_switch(ADVANCE_CLSW, L"advance-build", 2,
|
||||||
L"increment daily build code for web X");
|
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);
|
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();
|
Foundation::end();
|
||||||
if (return_happy) return 0; else return 1;
|
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)
|
@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 (silence_mode) {
|
||||||
if (Routine(NULL) == FALSE) { return_happy = FALSE; Routine(STDERR); }
|
if (Routine(NULL) == FALSE) { return_happy = FALSE; Routine(STDERR); }
|
||||||
} else {
|
} 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 ADVANCE_CLSW: Inversion::maintain(arg); break;
|
||||||
case PROBLEMS_CLSW: RUNTEST(Coverage::check); break;
|
case PROBLEMS_CLSW: RUNTEST(Coverage::check); break;
|
||||||
case SILENCE_CLSW: silence_mode = val; break;
|
case SILENCE_CLSW: silence_mode = val; break;
|
||||||
|
case VERBOSE_CLSW: verbose_mode = val; break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,21 +4,25 @@ To see which problem messages have test cases and which are linked
|
||||||
to the documentation.
|
to the documentation.
|
||||||
|
|
||||||
@h Observation.
|
@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
|
Problems can be mentioned in the code, in the documentation, or in the
|
||||||
@d DOC_MENTIONS_PCON 0x00000002
|
set of Inform test cases.
|
||||||
@d CODE_MENTIONS_PCON 0x00000004
|
|
||||||
@d IMPOSSIBLE_PCON 0x00000008
|
@d CASE_EXISTS_PCON 0x00000001 /* mentioned in test cases */
|
||||||
@d UNTESTABLE_PCON 0x00000010
|
@d DOC_MENTIONS_PCON 0x00000002 /* mentioned in documentation */
|
||||||
@d NAMELESS_PCON 0x00000020
|
@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 {
|
typedef struct known_problem {
|
||||||
struct text_stream *name;
|
struct text_stream *name;
|
||||||
int contexts_observed;
|
int contexts_observed; /* bitmap of the above bits */
|
||||||
int contexts_observed_multiple_times;
|
int contexts_observed_multiple_times; /* bitmap of the above bits */
|
||||||
MEMORY_MANAGEMENT
|
MEMORY_MANAGEMENT
|
||||||
} known_problem;
|
} known_problem;
|
||||||
|
|
||||||
|
@ -26,6 +30,8 @@ typedef struct known_problem {
|
||||||
and augment its bitmap of known contexts:
|
and augment its bitmap of known contexts:
|
||||||
|
|
||||||
=
|
=
|
||||||
|
dictionary *problems_dictionary = NULL;
|
||||||
|
|
||||||
void Coverage::observe_problem(text_stream *name, int context) {
|
void Coverage::observe_problem(text_stream *name, int context) {
|
||||||
if (problems_dictionary == NULL)
|
if (problems_dictionary == NULL)
|
||||||
problems_dictionary = Dictionaries::new(1000, FALSE);
|
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.
|
@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) {
|
void Coverage::which_problems_exist(void) {
|
||||||
Coverage::which_problems_exist_inner(
|
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inform7"));
|
||||||
Pathnames::from_text(I"inform7"), Workspace);
|
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inform7/core-module"));
|
||||||
Coverage::which_problems_exist_inner(
|
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inform7/if-module"));
|
||||||
Pathnames::from_text(I"inform7/core-module"), Workspace);
|
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inform7/multimedia-module"));
|
||||||
Coverage::which_problems_exist_inner(
|
Coverage::which_problems_exist_inner(Pathnames::from_text(I"inter/codegen-module"));
|
||||||
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_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");
|
filename *C = Filenames::in_folder(D, I"Contents.w");
|
||||||
existence_state es;
|
existence_state es;
|
||||||
es.web_path = D;
|
es.web_path = D;
|
||||||
|
@ -117,12 +129,6 @@ void Coverage::which_problems_exist_inner(pathname *D, pathname *Workspace) {
|
||||||
&Coverage::section_harvester, NULL, &es);
|
&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) {
|
void Coverage::section_harvester(text_stream *text, text_file_position *tfp, void *state) {
|
||||||
existence_state *es = (existence_state *) state;
|
existence_state *es = (existence_state *) state;
|
||||||
match_results mr = Regexp::create_mr();
|
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);
|
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) {
|
void Coverage::existence_harvester(text_stream *text, text_file_position *tfp, void *state) {
|
||||||
existence_state *es = (existence_state *) state;
|
existence_state *es = (existence_state *) state;
|
||||||
match_results mr = Regexp::create_mr();
|
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]);
|
WRITE_TO(text, "%S%S", mr.exp[0], mr.exp[2]);
|
||||||
TEMPORARY_TEXT(name);
|
TEMPORARY_TEXT(name);
|
||||||
Str::copy(name, mr.exp[1]);
|
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;
|
int context = CODE_MENTIONS_PCON;
|
||||||
if (Str::eq_wide_string(name, L"BelievedImpossible")) { context = IMPOSSIBLE_PCON; @<Suffix location@> }
|
if (Str::eq(name, I"BelievedImpossible")) {
|
||||||
else if (Str::eq_wide_string(name, L"Untestable")) { context = UNTESTABLE_PCON; @<Suffix location@> }
|
context = IMPOSSIBLE_PCON;
|
||||||
else if (Str::eq_wide_string(name, L"...")) { context = NAMELESS_PCON; @<Suffix location@> }
|
WRITE_TO(name, "_%f_line%d", es->section, tfp->line_count);
|
||||||
if (Str::eq_wide_string(name, L"BelievedImpossible")) @<Suffix location@>
|
} 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);
|
Coverage::observe_problem(name, context);
|
||||||
DISCARD_TEXT(name);
|
DISCARD_TEXT(name);
|
||||||
}
|
}
|
||||||
Regexp::dispose_of(&mr);
|
Regexp::dispose_of(&mr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@<Suffix location@> =
|
|
||||||
WRITE_TO(name, "_%f_line%d", es->section, tfp->line_count);
|
|
||||||
|
|
||||||
@h Checking.
|
@h Checking.
|
||||||
|
So the actual policy-enforcement routine is here:
|
||||||
|
|
||||||
=
|
=
|
||||||
int observations_made = FALSE;
|
int observations_made = FALSE;
|
||||||
int Coverage::check(OUTPUT_STREAM) {
|
int Coverage::check(OUTPUT_STREAM) {
|
||||||
if (observations_made == FALSE) {
|
if (observations_made == FALSE) {
|
||||||
pathname *P =
|
@<Perform the observations@>;
|
||||||
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);
|
|
||||||
observations_made = TRUE;
|
observations_made = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
int all_is_well = 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("%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;
|
WRITE("Problems actually existing (the source code refers to them):\n"); INDENT;
|
||||||
Coverage::cite(OUT, CODE_MENTIONS_PCON, 0, CODE_MENTIONS_PCON,
|
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,
|
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;
|
all_is_well = FALSE;
|
||||||
Coverage::list(OUT, 0, CODE_MENTIONS_PCON, CODE_MENTIONS_PCON);
|
Coverage::list(OUT, 0, CODE_MENTIONS_PCON, CODE_MENTIONS_PCON);
|
||||||
}
|
}
|
||||||
Coverage::cite(OUT, IMPOSSIBLE_PCON, 0, IMPOSSIBLE_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,
|
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,
|
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;
|
OUTDENT;
|
||||||
|
|
||||||
WRITE("Problems which should have test cases:\n"); INDENT;
|
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,
|
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,
|
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);
|
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,
|
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;
|
all_is_well = FALSE;
|
||||||
Coverage::list(OUT, CASE_EXISTS_PCON+CODE_MENTIONS_PCON, 0, CASE_EXISTS_PCON);
|
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;
|
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,
|
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,
|
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;
|
all_is_well = FALSE;
|
||||||
Coverage::list(OUT, 0, DOC_MENTIONS_PCON, DOC_MENTIONS_PCON);
|
Coverage::list(OUT, 0, DOC_MENTIONS_PCON, DOC_MENTIONS_PCON);
|
||||||
}
|
}
|
||||||
if (Coverage::cite(OUT, CODE_MENTIONS_PCON+DOC_MENTIONS_PCON, 0, 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;
|
all_is_well = FALSE;
|
||||||
Coverage::list(OUT, CODE_MENTIONS_PCON+DOC_MENTIONS_PCON, 0, DOC_MENTIONS_PCON);
|
Coverage::list(OUT, CODE_MENTIONS_PCON+DOC_MENTIONS_PCON, 0, DOC_MENTIONS_PCON);
|
||||||
}
|
}
|
||||||
OUTDENT;
|
OUTDENT;
|
||||||
|
|
||||||
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;
|
int N = 0;
|
||||||
known_problem *KP;
|
known_problem *KP;
|
||||||
LOOP_OVER(KP, known_problem) {
|
LOOP_OVER(KP, known_problem) {
|
||||||
if ((KP->contexts_observed & mask) == val) N++;
|
if ((KP->contexts_observed & mask) == val) N++;
|
||||||
if ((KP->contexts_observed_multiple_times & mask2) == 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;
|
return N;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Coverage::list(OUTPUT_STREAM, int mask, int mask2, int val) {
|
void Coverage::list(OUTPUT_STREAM, int mask, int mask2, int val) {
|
||||||
INDENT;
|
INDENT;
|
||||||
known_problem *KP;
|
known_problem *KP;
|
||||||
LOOP_OVER(KP, known_problem) {
|
LOOP_OVER(KP, known_problem)
|
||||||
if (((KP->contexts_observed & mask) == val) ||
|
if (((KP->contexts_observed & mask) == val) ||
|
||||||
((KP->contexts_observed_multiple_times & mask2) == val)) {
|
((KP->contexts_observed_multiple_times & mask2) == val)) {
|
||||||
WRITE("%S\n", KP->name);
|
WRITE("%S\n", KP->name);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
OUTDENT;
|
OUTDENT;
|
||||||
}
|
}
|
|
@ -3,6 +3,10 @@
|
||||||
To update the build number(s) and versions for the intools.
|
To update the build number(s) and versions for the intools.
|
||||||
|
|
||||||
@h The build-numbers file.
|
@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 {
|
typedef struct project {
|
||||||
|
@ -11,7 +15,7 @@ typedef struct project {
|
||||||
int manual_updating;
|
int manual_updating;
|
||||||
struct text_stream *web;
|
struct text_stream *web;
|
||||||
struct filename *versions_file;
|
struct filename *versions_file;
|
||||||
struct version *first_version;
|
struct linked_list *versions; /* of |version| */
|
||||||
struct version *current_version;
|
struct version *current_version;
|
||||||
struct text_stream *conts;
|
struct text_stream *conts;
|
||||||
MEMORY_MANAGEMENT
|
MEMORY_MANAGEMENT
|
||||||
|
@ -23,42 +27,58 @@ typedef struct version {
|
||||||
struct text_stream *build_code;
|
struct text_stream *build_code;
|
||||||
struct text_stream *date;
|
struct text_stream *date;
|
||||||
struct text_stream *notes;
|
struct text_stream *notes;
|
||||||
int unstable;
|
|
||||||
struct version *next_version;
|
|
||||||
MEMORY_MANAGEMENT
|
MEMORY_MANAGEMENT
|
||||||
} version;
|
} 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 *Inversion::read(text_stream *web) {
|
||||||
project *P;
|
project *P;
|
||||||
LOOP_OVER(P, project)
|
LOOP_OVER(P, project) if (Str::eq(web, P->web)) return P;
|
||||||
if (Str::eq(web, P->web))
|
|
||||||
return P;
|
|
||||||
P = CREATE(project);
|
P = CREATE(project);
|
||||||
P->sync_line = Str::new();
|
P->sync_line = Str::new();
|
||||||
P->sync_to = NULL;
|
P->sync_to = NULL;
|
||||||
P->manual_updating = TRUE;
|
P->manual_updating = TRUE;
|
||||||
P->web = Str::duplicate(web);
|
P->web = Str::duplicate(web);
|
||||||
P->first_version = NULL;
|
P->versions = NEW_LINKED_LIST(version);
|
||||||
P->current_version = NULL;
|
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->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) {
|
if (P->current_version == NULL) {
|
||||||
Errors::with_text("warning: no version marked as current", web);
|
Errors::with_text("warning: no version marked as current", web);
|
||||||
} else {
|
} else {
|
||||||
PRINT("%S: %S %S (build %S)\n", web,
|
PRINT("%S: %S %S (build %S)\n", web,
|
||||||
P->current_version->name, P->current_version->number, P->current_version->build_code);
|
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) {
|
void Inversion::version_harvester(text_stream *text, text_file_position *tfp, void *state) {
|
||||||
project *P = (project *) state;
|
project *P = (project *) state;
|
||||||
match_results mr = Regexp::create_mr();
|
match_results mr = Regexp::create_mr();
|
||||||
if (Str::len(text) == 0) return;
|
if (Str::len(text) == 0) return;
|
||||||
if (Regexp::match(&mr, text, L"Automatic")) {
|
if (Regexp::match(&mr, text, L"Automatic")) {
|
||||||
P->manual_updating = FALSE;
|
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*)")) {
|
} else if (Regexp::match(&mr, text, L"Sync to (%c*)")) {
|
||||||
P->sync_to = Inversion::read(mr.exp[0]);
|
P->sync_to = Inversion::read(mr.exp[0]);
|
||||||
P->manual_updating = FALSE;
|
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->date = Str::duplicate(mr.exp[3]);
|
||||||
V->notes = Str::duplicate(mr.exp[4]);
|
V->notes = Str::duplicate(mr.exp[4]);
|
||||||
if (Str::get_first_char(V->build_code) == '*') {
|
if (Str::get_first_char(V->build_code) == '*') {
|
||||||
V->unstable = TRUE;
|
|
||||||
Str::delete_first_character(V->build_code);
|
Str::delete_first_character(V->build_code);
|
||||||
P->current_version = V;
|
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 {
|
} else {
|
||||||
Errors::in_text_file("can't parse version line", tfp);
|
Errors::in_text_file("can't parse version line", tfp);
|
||||||
}
|
}
|
||||||
Regexp::dispose_of(&mr);
|
Regexp::dispose_of(&mr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ The following then writes back the versions file, following a version
|
||||||
|
increment:
|
||||||
|
|
||||||
|
=
|
||||||
void Inversion::write(project *P) {
|
void Inversion::write(project *P) {
|
||||||
text_stream vr_stream;
|
text_stream vr_stream;
|
||||||
text_stream *OUT = &vr_stream;
|
text_stream *OUT = &vr_stream;
|
||||||
if (Streams::open_to_file(OUT, P->versions_file, UTF8_ENC) == FALSE)
|
if (Streams::open_to_file(OUT, P->versions_file, UTF8_ENC) == FALSE)
|
||||||
Errors::fatal_with_file("can't write versions file", P->versions_file);
|
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);
|
if (P->sync_to) WRITE("Sync to %S\n", P->sync_to->web);
|
||||||
for (version *V = P->first_version; V; V = V->next_version) {
|
else if (P->manual_updating) WRITE("Manual\n");
|
||||||
WRITE("%S\t%S\t",
|
else WRITE("Automatic\n");
|
||||||
V->name, V->number);
|
version *V;
|
||||||
if (V->unstable) WRITE("*");
|
LOOP_OVER_LINKED_LIST(V, version, P->versions) {
|
||||||
WRITE("%S\t%S\t%S\n",
|
WRITE("%S\t%S\t", V->name, V->number);
|
||||||
V->build_code, V->date, V->notes);
|
if (V == P->current_version) WRITE("*");
|
||||||
|
WRITE("%S\t%S\t%S\n", V->build_code, V->date, V->notes);
|
||||||
}
|
}
|
||||||
Streams::close(OUT);
|
Streams::close(OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@h Updating.
|
@h Updating.
|
||||||
|
The standard date format we use is "26 February 2018".
|
||||||
|
|
||||||
=
|
=
|
||||||
int Inversion::dated_today(project *P, text_stream *dateline) {
|
int Inversion::dated_today(project *P, text_stream *dateline) {
|
||||||
|
@ -116,6 +136,13 @@ int Inversion::dated_today(project *P, text_stream *dateline) {
|
||||||
return rv;
|
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) {
|
void Inversion::increment(project *P) {
|
||||||
if (P->current_version == NULL) return;
|
if (P->current_version == NULL) return;
|
||||||
text_stream *T = P->current_version->build_code;
|
text_stream *T = P->current_version->build_code;
|
||||||
|
@ -145,6 +172,8 @@ void Inversion::increment(project *P) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@h Imposition.
|
@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) {
|
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);
|
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 Inversion::needs_update(project *P) {
|
||||||
int rv = FALSE;
|
int rv = FALSE;
|
||||||
if ((P->manual_updating == FALSE) && (P->current_version)) {
|
if ((P->manual_updating == FALSE) && (P->current_version)) {
|
||||||
|
@ -204,23 +254,3 @@ int Inversion::needs_update(project *P) {
|
||||||
}
|
}
|
||||||
return rv;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,8 +12,13 @@ Build Date: 10 February 2019
|
||||||
|
|
||||||
Import: foundation
|
Import: foundation
|
||||||
|
|
||||||
Chapter 1: What It Does
|
Preliminaries
|
||||||
|
Using Inpolicy
|
||||||
|
|
||||||
|
Chapter 1: Setting Up
|
||||||
Basics
|
Basics
|
||||||
Main
|
Main
|
||||||
|
|
||||||
|
Chapter 2: Policies
|
||||||
Problem Coverage
|
Problem Coverage
|
||||||
Version Numbering
|
Version Numbering
|
||||||
|
|
66
inpolicy/Preliminaries/Using Inpolicy.w
Executable file
66
inpolicy/Preliminaries/Using Inpolicy.w
Executable 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|.
|
|
@ -345,6 +345,7 @@ endef
|
||||||
pages:
|
pages:
|
||||||
$(INWEBX) inblorb -weave-docs -weave-into docs/inblorb
|
$(INWEBX) inblorb -weave-docs -weave-into docs/inblorb
|
||||||
$(INWEBX) indoc -weave-docs -weave-into docs/indoc
|
$(INWEBX) indoc -weave-docs -weave-into docs/indoc
|
||||||
|
$(INWEBX) inpolicy -weave-docs -weave-into docs/inpolicy
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Target "clean"
|
# Target "clean"
|
||||||
|
|
Loading…
Reference in a new issue