1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-05 08:34:22 +03:00
inform7/inpolicy/Chapter 2/Version Numbering.w
2019-03-16 17:36:11 +00:00

378 lines
13 KiB
OpenEdge ABL

[Inversion::] Version Numbering.
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 {
struct text_stream *sync_line;
struct project *sync_to;
int manual_updating;
struct text_stream *web;
struct filename *contents_file;
struct filename *versions_file;
struct linked_list *versions; /* of |version| */
struct version *current_version;
struct text_stream *purpose;
struct text_stream *conts;
int next_is_version;
MEMORY_MANAGEMENT
} project;
typedef struct version {
struct text_stream *name;
struct text_stream *number;
struct text_stream *build_code;
struct text_stream *date;
struct text_stream *notes;
MEMORY_MANAGEMENT
} version;
@ And here we take a filename or pathname, which might be to a web, with
or without a versions file; or to an extension; or to a website template;
or to the original Inform 6 source code. These all store their version
numbering differently, so we need code which is something of a Swiss army
knife.
=
project *Inversion::read(text_stream *web, int silently) {
project *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->versions = NEW_LINKED_LIST(version);
P->current_version = NULL;
P->conts = Str::new();
P->purpose = Str::new();
P->next_is_version = FALSE;
if (Str::ends_with_wide_string(web, L".i7x")) {
P->versions_file = NULL;
P->contents_file = NULL;
@<Read in the extension file@>;
} else {
P->versions_file = Filenames::in_folder(Pathnames::from_text(web), I"versions.txt");
P->contents_file = Filenames::in_folder(Pathnames::from_text(web), I"Contents.w");
if (TextFiles::exists(P->contents_file)) {
@<Read in the contents file@>;
if (TextFiles::exists(P->versions_file) == FALSE)
@<Read version from the contents file@>;
}
if (TextFiles::exists(P->versions_file)) @<Read in the versions file@>;
filename *I6_vn = Filenames::in_folder(
Pathnames::subfolder(Pathnames::from_text(web), I"inform6"), I"header.h");
if (TextFiles::exists(I6_vn)) @<Read in I6 source header file@>;
filename *template_vn = Filenames::in_folder(Pathnames::from_text(web), I"(manifest).txt");
if (TextFiles::exists(template_vn)) @<Read in template manifest file@>;
}
@<Print the current version number@>;
return P;
}
@<Print the current version number@> =
if ((P->current_version) && (!silently))
PRINT("%S: %S %S (build %S)\n", web,
P->current_version->name, P->current_version->number, P->current_version->build_code);
@<Read in the extension file@> =
TextFiles::read(Filenames::from_text(web), FALSE, "unable to read extension", TRUE,
&Inversion::extension_harvester, NULL, P);
@<Read in the contents file@> =
TextFiles::read(P->contents_file, FALSE, "unable to read contents section", TRUE,
&Inversion::contents_harvester, NULL, P);
@<Read version from the contents file@> =
TextFiles::read(P->contents_file, FALSE, "unable to read contents section", TRUE,
&Inversion::contents_version_harvester, NULL, 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);
@<Read in I6 source header file@> =
TextFiles::read(I6_vn, FALSE, "unable to read header file from I6 source", TRUE,
&Inversion::header_harvester, NULL, P);
@<Read in template manifest file@> =
TextFiles::read(template_vn, FALSE, "unable to read manifest file from website template", TRUE,
&Inversion::template_harvester, NULL, P);
@ The format for the contents section of a web is documented in Inweb.
=
void Inversion::extension_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" *Version (%c*?) of %c*begins here. *")) {
@<Ensure a current version exists@>;
P->current_version->number = Str::duplicate(mr.exp[0]);
}
Regexp::dispose_of(&mr);
}
@ The format for the contents section of a web is documented in Inweb.
=
void Inversion::contents_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" *Purpose: *(%c*?) *")) {
P->purpose = Str::duplicate(mr.exp[0]);
}
Regexp::dispose_of(&mr);
}
void Inversion::contents_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" *Version Number: *(%c*?) *")) {
@<Ensure a current version exists@>;
P->current_version->number = Str::duplicate(mr.exp[0]);
}
Regexp::dispose_of(&mr);
}
@ 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], TRUE);
P->manual_updating = FALSE;
} else if (Regexp::match(&mr, text, L"(%c*?)\t+(%c*?)\t+(%c*?)\t+(%c*?)\t+(%c*)")) {
version *V = CREATE(version);
V->name = Str::duplicate(mr.exp[0]);
V->number = Str::duplicate(mr.exp[1]);
V->build_code = Str::duplicate(mr.exp[2]);
V->date = Str::duplicate(mr.exp[3]);
V->notes = Str::duplicate(mr.exp[4]);
if (Str::get_first_char(V->build_code) == '*') {
Str::delete_first_character(V->build_code);
P->current_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);
}
@ Explicit code to read from |header.h| in the Inform 6 repository.
=
void Inversion::header_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"#define RELEASE_NUMBER (%c*?) *")) {
@<Ensure a current version exists@>;
P->current_version->number = Str::duplicate(mr.exp[0]);
}
if (Regexp::match(&mr, text, L"#define RELEASE_DATE \"(%c*?)\" *")) {
@<Ensure a current version exists@>;
P->current_version->name = Str::duplicate(mr.exp[0]);
P->current_version->date = Str::duplicate(mr.exp[0]);
}
Regexp::dispose_of(&mr);
}
@ Explicit code to read from the manifest file of a website template.
=
void Inversion::template_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"%[INTERPRETERVERSION%]")) {
P->next_is_version = TRUE;
} else if (P->next_is_version) {
@<Ensure a current version exists@>;
P->current_version->name = Str::duplicate(text);
P->next_is_version = FALSE;
}
Regexp::dispose_of(&mr);
}
@ And many of the above use this, which assumes there will be just one single
version number known for a program.
@<Ensure a current version exists@> =
if (P->current_version == NULL) {
version *V = CREATE(version);
V->name = NULL;
V->number = NULL;
V->build_code = I"9Z99";
V->date = NULL;
V->notes = NULL;
ADD_TO_LINKED_LIST(V, version, P->versions);
P->current_version = V;
}
@ 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);
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) {
if (P->current_version == NULL) return FALSE;
char *monthname[12] = { "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" };
WRITE_TO(dateline, "%d %s %d",
the_present->tm_mday, monthname[the_present->tm_mon], the_present->tm_year+1900);
int rv = FALSE;
if (Str::eq(dateline, P->current_version->date)) rv = TRUE;
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;
if (Str::len(T) != 4) Errors::with_text("version number malformed: %S", T);
else {
int N = Str::get_at(T, 0) - '0';
int L = Str::get_at(T, 1);
int M1 = Str::get_at(T, 2) - '0';
int M2 = Str::get_at(T, 3) - '0';
if ((N < 0) || (N > 9) || (L < 'A') || (L > 'Z') ||
(M1 < 0) || (M1 > 9) || (M2 < 0) || (M2 > 9)) {
Errors::with_text("version number malformed: %S", T);
} else {
M2++;
if (M2 == 10) { M2 = 0; M1++; }
if (M1 == 10) { M1 = 0; M2 = 1; L++; }
if ((L == 'I') || (L == 'O')) L++;
if (L > 'Z') { L = 'A'; N++; }
if (N == 10) Errors::with_text("version number overflowed: %S", T);
else {
Str::clear(T);
WRITE_TO(T, "%d%c%d%d", N, L, M1, M2);
PRINT("Build advanced to %S\n", T);
}
}
}
}
@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) {
TextFiles::read(P->contents_file, FALSE, "unable to read web contents", TRUE,
&Inversion::impose_helper, NULL, P);
text_stream vr_stream;
text_stream *OUT = &vr_stream;
if (Streams::open_to_file(OUT, P->contents_file, UTF8_ENC) == FALSE)
Errors::fatal_with_file("unable to write web contents", P->contents_file);
WRITE("%S", P->conts);
Streams::close(OUT);
}
void Inversion::impose_helper(text_stream *text, text_file_position *tfp, void *state) {
project *P = (project *) state;
project *SP = P;
if (P->sync_to) SP = P->sync_to;
text_stream *OUT = P->conts;
match_results mr = Regexp::create_mr();
if ((P->current_version) && (Regexp::match(&mr, text, L"Build Date:%c*"))) {
WRITE("Build Date: %S\n", SP->current_version->date);
} else if ((P->current_version) && (Regexp::match(&mr, text, L"Build Number:%c*"))) {
WRITE("Build Number: %S\n", SP->current_version->build_code);
} else if ((P->current_version) && (Regexp::match(&mr, text, L"Version Number:%c*"))) {
WRITE("Version Number: %S\n", P->current_version->number);
} else if ((P->current_version) && (Regexp::match(&mr, text, L"Version Name:%c*"))) {
WRITE("Version Name: %S\n", P->current_version->name);
} else {
WRITE("%S\n", text);
}
Regexp::dispose_of(&mr);
}
@h Daily build maintenance.
=
void Inversion::maintain(text_stream *web) {
project *P = Inversion::read(web, FALSE);
if (Inversion::needs_update(P)) {
Inversion::write(P);
Inversion::impose(P);
}
}
@ =
int Inversion::needs_update(project *P) {
int rv = FALSE;
if ((P->manual_updating == FALSE) && (P->current_version)) {
project *SP = P->sync_to;
if (SP) {
if (SP->current_version) {
if (Str::ne(P->current_version->date, SP->current_version->date)) {
rv = TRUE; Str::copy(P->current_version->date, SP->current_version->date);
}
if (Str::ne(P->current_version->build_code, SP->current_version->build_code)) {
rv = TRUE; Str::copy(P->current_version->build_code, SP->current_version->build_code);
}
}
} else {
TEMPORARY_TEXT(dateline);
if (Inversion::dated_today(P, dateline) == FALSE) {
if (P->sync_to == NULL) Inversion::increment(P);
Str::copy(P->current_version->date, dateline);
rv = TRUE;
}
DISCARD_TEXT(dateline);
}
}
return rv;
}