1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-05-17 08:28:42 +03:00
inform7/inform7/core-module/Chapter 1/Where Everything Lives.w
2019-03-16 13:12:11 +00:00

525 lines
22 KiB
OpenEdge ABL

[Locations::] Where Everything Lives.
To configure the many locations used in the host filing system.
@h Definitions.
@ This section of the Inform source is intended to give a single description
of where everything lives in the filing system. Very early in Inform's run,
it works out the filenames of everything it will ever need to refer to, and
these are stored in the following globals. Explanations are given below,
not here. First, some "areas":
@d NO_FS_AREAS 3
@d MATERIALS_FS_AREA 0 /* must match |ORIGIN_WAS_*| constants minus 1 */
@d EXTERNAL_FS_AREA 1
@d INTERNAL_FS_AREA 2
=
char *AREA_NAME[3] = { "from .materials", "installed", "built in" };
@ Now for the folders:
= (early code)
pathname *pathname_of_area[NO_FS_AREAS] = { NULL, NULL, NULL };
pathname *pathname_of_extensions[NO_FS_AREAS] = { NULL, NULL, NULL };
pathname *pathname_of_i6t_files[NO_FS_AREAS] = { NULL, NULL, NULL };
pathname *pathname_of_languages[NO_FS_AREAS] = { NULL, NULL, NULL };
pathname *pathname_of_website_templates[NO_FS_AREAS] = { NULL, NULL, NULL };
pathname *pathname_of_extension_docs = NULL;
pathname *pathname_of_extension_docs_inner = NULL;
pathname *pathname_of_HTML_models = NULL;
pathname *pathname_of_materials_figures = NULL;
pathname *pathname_of_materials_release = NULL;
pathname *pathname_of_materials_sounds = NULL;
pathname *pathname_of_project = NULL;
pathname *pathname_of_project_index_details_folder = NULL;
pathname *pathname_of_project_index_folder = NULL;
pathname *pathname_of_released_figures = NULL;
pathname *pathname_of_released_interpreter = NULL;
pathname *pathname_of_released_sounds = NULL;
pathname *pathname_of_transient_census_data = NULL;
pathname *pathname_of_transient_external_resources = NULL;
@ And secondly, the files:
= (early code)
filename *filename_of_blurb = NULL;
filename *filename_of_cblorb_report = NULL;
filename *filename_of_cblorb_report_model = NULL;
filename *filename_of_compiled_i6_code = NULL;
filename *filename_of_debugging_log = NULL;
filename *filename_of_documentation_snippets = NULL;
filename *filename_of_epsfile = NULL;
filename *filename_of_existing_story_file = NULL;
filename *filename_of_extensions_dictionary = NULL;
filename *filename_of_headings = NULL;
filename *filename_of_i7_source = NULL;
filename *filename_of_ifiction_record = NULL;
filename *filename_of_intro_booklet = NULL;
filename *filename_of_intro_postcard = NULL;
filename *filename_of_large_cover_art_jpeg = NULL;
filename *filename_of_large_cover_art_png = NULL;
filename *filename_of_large_default_cover_art = NULL;
filename *filename_of_manifest = NULL;
filename *filename_of_options = NULL;
filename *filename_of_parse_tree = NULL;
filename *filename_of_report = NULL;
filename *filename_of_small_cover_art_jpeg = NULL;
filename *filename_of_small_cover_art_png = NULL;
filename *filename_of_small_default_cover_art = NULL;
filename *filename_of_SR_module = NULL;
filename *filename_of_story_file = NULL;
filename *filename_of_telemetry = NULL;
filename *filename_of_uuid = NULL;
@h Command line settings.
The following are called when the command line is parsed.
=
void Locations::set_project(text_stream *loc) {
pathname_of_project = Pathnames::from_text(loc);
}
void Locations::set_internal(text_stream *loc) {
pathname_of_area[INTERNAL_FS_AREA] = Pathnames::from_text(loc);
}
void Locations::set_default_internal(pathname *P) {
if (pathname_of_area[INTERNAL_FS_AREA] == NULL)
pathname_of_area[INTERNAL_FS_AREA] = P;
}
void Locations::set_external(text_stream *loc) {
pathname_of_area[EXTERNAL_FS_AREA] = Pathnames::from_text(loc);
}
void Locations::set_transient(text_stream *loc) {
pathname_of_transient_external_resources = Pathnames::from_text(loc);
}
int Locations::set_I7_source(text_stream *loc) {
if (filename_of_i7_source) return FALSE;
filename_of_i7_source = Filenames::from_text(loc);
return TRUE;
}
int Locations::set_SR_module(text_stream *loc) {
if (filename_of_SR_module) return FALSE;
filename_of_SR_module = Filenames::from_text(loc);
return TRUE;
}
@h Establishing the defaults.
Inform's file access happens inside four different areas: the internal
resources area, usually inside the Inform application; the external resources
area, which is where the user (or the application acting on the user's behalf)
installs extensions; the project bundle, say |Example.inform|; and, alongside
that, the materials folder, |Example.materials|.
Inform can run in two modes: regular mode, when it's compiling source text,
and census mode, when it's scanning the file system for extensions.
=
int Locations::set_defaults(int census_mode) {
@<Internal resources@>;
@<External resources@>;
@<Project resources@>;
@<Materials resources@>;
if ((census_mode == FALSE) && (filename_of_i7_source == NULL))
Problems::Fatal::issue("Except in census mode, source text must be supplied");
if ((census_mode) && (filename_of_i7_source))
Problems::Fatal::issue("In census mode, no source text may be supplied");
return TRUE;
}
@h Internal resources.
Inform needs a whole pile of files to have been installed on the host computer
before it can run: everything from the Standard Rules to a PDF file explaining
what interactive fiction is. They're never written to, only read. They are
referred to as "internal" or "built-in", and they occupy a folder called the
"internal resources" folder.
Unfortunately we don't know where it is. Typically this compiler will be an
executable sitting somewhere inside a user interface application, and the
internal resources folder will be somewhere else inside it. But we don't
know how to find that folder, and we don't want to make any assumptions.
Inform therefore requires on every run that it be told via the |-internal|
switch where the internal resources folder is.
The main ingredients here are the "EILT" resources: extensions, I6T files,
language definitions, and website templates. The Standard Rules, for
example, live inside the Extensions part of this.
@<Internal resources@> =
if (pathname_of_area[INTERNAL_FS_AREA] == NULL)
Problems::Fatal::issue("Did not set -internal when calling");
Locations::EILT_at(INTERNAL_FS_AREA, pathname_of_area[INTERNAL_FS_AREA]);
@<Miscellaneous other stuff@>;
@ Most of these files are to help |cblorb| to perform a release. The
documentation models are used when making extension documentation; the
leafname is platform-dependent so that Windows can use different models
from everybody else.
The documentation snippets file is generated by |indoc| and contains
brief specifications of phrases, extracted from the manual "Writing with
Inform". This is used to generate the Phrasebook index.
@<Miscellaneous other stuff@> =
pathname *misc = Pathnames::subfolder(pathname_of_area[INTERNAL_FS_AREA], I"Miscellany");
filename_of_large_default_cover_art = Filenames::in_folder(misc, I"Cover.jpg");
filename_of_small_default_cover_art = Filenames::in_folder(misc, I"Small Cover.jpg");
filename_of_intro_postcard = Filenames::in_folder(misc, I"Postcard.pdf");
filename_of_intro_booklet = Filenames::in_folder(misc, I"IntroductionToIF.pdf");
pathname_of_HTML_models = Pathnames::subfolder(pathname_of_area[INTERNAL_FS_AREA], I"HTML");
filename_of_cblorb_report_model = Filenames::in_folder(pathname_of_HTML_models, I"CblorbModel.html");
filename_of_documentation_snippets = Filenames::in_folder(misc, I"definitions.html");
@h External resources.
This is where the user can install downloaded extensions, new interpreters,
website templates and so on; so-called "permanent" external resources, since
the user expects them to stay put once installed. But there is also a
"transient" external resources area, for more ephemeral content, such as
the mechanically generated extension documentation. On most platforms the
permanent and transient external areas will be the same, but some mobile
operating systems are aggressive about wanting to delete ephemeral files
used by applications.
The locations of the permanent and transient external folders can be set
using |-external| and |-transient| respectively. If no |-external| is
specified, the location depends on the platform settings: for example on
Mac OS X it will typically be
|/Library/Users/hclinton/Library/Inform|
If |-transient| is not specified, it's the same folder, i.e., Inform does
not distinguish between permanent and transient external resources.
@<External resources@> =
if (pathname_of_area[EXTERNAL_FS_AREA] == NULL) {
pathname_of_area[EXTERNAL_FS_AREA] = home_path;
char *subfolder_within = INFORM_FOLDER_RELATIVE_TO_HOME;
if (subfolder_within[0]) {
TEMPORARY_TEXT(SF);
WRITE_TO(SF, "%s", subfolder_within);
pathname_of_area[EXTERNAL_FS_AREA] = Pathnames::subfolder(home_path, SF);
DISCARD_TEXT(SF);
}
pathname_of_area[EXTERNAL_FS_AREA] =
Pathnames::subfolder(pathname_of_area[EXTERNAL_FS_AREA], I"Inform");
}
if (Pathnames::create_in_file_system(pathname_of_area[EXTERNAL_FS_AREA]) == 0) return FALSE;
@<Permanent external resources@>;
if (pathname_of_transient_external_resources == NULL)
pathname_of_transient_external_resources =
pathname_of_area[EXTERNAL_FS_AREA];
if (Pathnames::create_in_file_system(pathname_of_transient_external_resources) == 0) return FALSE;
@<Transient external resources@>;
@ The permanent resources are read-only as far as we are concerned. (The
user interface application, and the user directly, write to this area when
they (say) install new extensions. But the compiler only reads.)
Once again we have a set of EILT resources, but we also have a curiosity:
a useful little file to add source text to everything Inform compiles,
generally to set use options.
@<Permanent external resources@> =
Locations::EILT_at(EXTERNAL_FS_AREA, pathname_of_area[EXTERNAL_FS_AREA]);
filename_of_options =
Filenames::in_folder(pathname_of_area[EXTERNAL_FS_AREA], I"Options.txt");
@ The transient resources are all written by us.
@<Transient external resources@> =
@<Transient documentation@>;
@<Transient telemetry@>;
@ The documentation folder is in effect a little website of its own, generated
automatically by Inform. There'll be some files at the top level, and then
there are files on each extension, in suitable subfolders. The census data
subfolder is not browsable or linked to, but holds working files needed when
assembling all this.
@<Transient documentation@> =
pathname_of_extension_docs =
Pathnames::subfolder(pathname_of_transient_external_resources, I"Documentation");
if (Pathnames::create_in_file_system(pathname_of_extension_docs) == 0) return FALSE;
pathname_of_transient_census_data =
Pathnames::subfolder(pathname_of_extension_docs, I"Census");
if (Pathnames::create_in_file_system(pathname_of_transient_census_data) == 0) return FALSE;
filename_of_extensions_dictionary =
Filenames::in_folder(pathname_of_transient_census_data, I"Dictionary.txt");
pathname_of_extension_docs_inner =
Pathnames::subfolder(pathname_of_extension_docs, I"Extensions");
if (Pathnames::create_in_file_system(pathname_of_extension_docs_inner) == 0) return FALSE;
@ Telemetry is not as sinister as it sounds: the app isn't sending data out
on the Internet, only (if requested) logging what it's doing to a local file.
This was provided for classroom use, so that teachers can see what their
students have been getting stuck on.
@<Transient telemetry@> =
pathname *pathname_of_telemetry_data =
Pathnames::subfolder(pathname_of_transient_external_resources, I"Telemetry");
if (Pathnames::create_in_file_system(pathname_of_telemetry_data) == 0) return FALSE;
TEMPORARY_TEXT(leafname_of_telemetry);
int this_month = the_present->tm_mon + 1;
int this_day = the_present->tm_mday;
int this_year = the_present->tm_year + 1900;
WRITE_TO(leafname_of_telemetry,
"Telemetry %04d-%02d-%02d.txt", this_year, this_month, this_day);
filename_of_telemetry =
Filenames::in_folder(pathname_of_telemetry_data, leafname_of_telemetry);
Telemetry::locate_telemetry_file(filename_of_telemetry);
DISCARD_TEXT(leafname_of_telemetry);
@h Project resources.
Although on some platforms it may look like a single file, an Inform project
is a folder whose name has the dot-extension |.inform|. We'll call this the
"project folder", and it contains a whole bundle of useful files.
The UUID file records an ISBN-like identifying number for the project. This
is read-only for us.
The iFiction record, manifest and blurb file are all files that we generate
to give instructions to the releasing agent |cblorb|. This means that they
have no purpose unless Inform is in a release run (with |-release| set on
the command line), but they take no time to generate so we make them anyway.
@<Project resources@> =
@<The Source folder within the project@>;
@<The Build folder within the project@>;
@<The Index folder within the project@>;
filename_of_uuid = Filenames::in_folder(pathname_of_project, I"uuid.txt");
filename_of_ifiction_record = Filenames::in_folder(pathname_of_project, I"Metadata.iFiction");
filename_of_manifest = Filenames::in_folder(pathname_of_project, I"manifest.plist");
filename_of_blurb = Filenames::in_folder(pathname_of_project, I"Release.blurb");
@ This contains just the main source text for the project. Anachronistically,
this has the filename extension |.ni| for "natural Inform", which was the
working title for Inform 7 back in the early 2000s.
@<The Source folder within the project@> =
if (filename_of_i7_source == NULL)
if (pathname_of_project)
filename_of_i7_source =
Filenames::in_folder(
Pathnames::subfolder(pathname_of_project, I"Source"),
I"story.ni");
@ The build folder for a project contains all of the working files created
during the compilation process. The opening part here may be a surprise:
In extension census mode, Inform is running not to compile something but to
extract details of all the extensions installed. But it still needs somewhere
to write its temporary and debugging files, and there is no project bundle
to write into. To get round this, we use the census data area as if it
were indeed a project bundle.
Briefly: we aim to compile the source text to an Inform 6 program; we issue
an HTML report on our success or failure, listing problem messages if they
occurred; we track our progress in the debugging log. We don't produce the
story file ourselves, I6 will do that, but we do need to know what it's
called; and similarly for the report which the releasing tool |cblorb|
will produce if this is a Release run.
@<The Build folder within the project@> =
pathname *build_folder = pathname_of_transient_census_data;
if (census_mode == FALSE) {
build_folder = Pathnames::subfolder(pathname_of_project, I"Build");
if (Pathnames::create_in_file_system(build_folder) == 0) return FALSE;
}
filename_of_report = Filenames::in_folder(build_folder, I"Problems.html");
filename_of_debugging_log = Filenames::in_folder(build_folder, I"Debug log.txt");
filename_of_parse_tree = Filenames::in_folder(build_folder, I"Parse tree.txt");
filename_of_compiled_i6_code = Filenames::in_folder(build_folder, I"auto.inf");
TEMPORARY_TEXT(story_file_leafname);
WRITE_TO(story_file_leafname, "output.%S", story_filename_extension);
filename_of_story_file = Filenames::in_folder(build_folder, story_file_leafname);
DISCARD_TEXT(story_file_leafname);
filename_of_cblorb_report = Filenames::in_folder(build_folder, I"StatusCblorb.html");
@ We're going to write into the Index folder, so we must ensure it exists.
The main index files (|Phrasebook.html| and so on) live at the top level,
details on actions live in the subfolder |Details|: see below.
An oddity in the Index folder is an XML file recording where the headings
are in the source text: this is for the benefit of the user interface
application, if it wants it, but is not linked to or used by the HTML of
the index as seen by the user.
@<The Index folder within the project@> =
pathname_of_project_index_folder =
Pathnames::subfolder(pathname_of_project, I"Index");
pathname_of_project_index_details_folder =
Pathnames::subfolder(pathname_of_project_index_folder, I"Details");
if (census_mode == FALSE)
if ((Pathnames::create_in_file_system(pathname_of_project_index_folder) == 0) ||
(Pathnames::create_in_file_system(pathname_of_project_index_details_folder) == 0))
return FALSE;
filename_of_headings =
Filenames::in_folder(pathname_of_project_index_folder, I"Headings.xml");
@h Materials resources.
The materials folder sits alongside the project folder and has the same name,
but ending |.materials| instead of |.inform|.
For the third and final time, there are EILT resources.
@<Materials resources@> =
if (pathname_of_project) {
TEMPORARY_TEXT(mf);
WRITE_TO(mf, "%S", Pathnames::directory_name(pathname_of_project));
int i = Str::len(mf)-1;
while ((i>0) && (Str::get_at(mf, i) != '.')) i--;
if (i>0) {
Str::truncate(mf, i);
WRITE_TO(mf, ".materials");
}
pathname_of_area[MATERIALS_FS_AREA] =
Pathnames::subfolder(pathname_of_project->pathname_of_parent, mf);
DISCARD_TEXT(mf);
if (Pathnames::create_in_file_system(pathname_of_area[MATERIALS_FS_AREA]) == 0) return FALSE;
} else {
pathname_of_area[MATERIALS_FS_AREA] = Pathnames::from_text(I"inform.materials");
}
Locations::EILT_at(MATERIALS_FS_AREA, pathname_of_area[MATERIALS_FS_AREA]);
@<Figures and sounds@>;
@<The Release folder@>;
@<Existing story file@>;
@ This is where cover art lives: it could have either the file extension |.jpg|
or |.png|, and we generate both sets of filenames, even though at most one will
actually work. This is also where we generate the EPS file of the map, if
so requested; a bit anomalously, it's the only file in Materials but outside
Release which we write to.
This is also where the originals (not the released copies) of the Figures
and Sounds, if any, live: in their own subfolders.
@<Figures and sounds@> =
pathname_of_materials_figures = Pathnames::subfolder(pathname_of_area[MATERIALS_FS_AREA], I"Figures");
pathname_of_materials_sounds = Pathnames::subfolder(pathname_of_area[MATERIALS_FS_AREA], I"Sounds");
filename_of_large_cover_art_jpeg = Filenames::in_folder(pathname_of_area[MATERIALS_FS_AREA], I"Cover.jpg");
filename_of_large_cover_art_png = Filenames::in_folder(pathname_of_area[MATERIALS_FS_AREA], I"Cover.png");
filename_of_small_cover_art_jpeg = Filenames::in_folder(pathname_of_area[MATERIALS_FS_AREA], I"Small Cover.jpg");
filename_of_small_cover_art_png = Filenames::in_folder(pathname_of_area[MATERIALS_FS_AREA], I"Small Cover.png");
filename_of_epsfile = Filenames::in_folder(pathname_of_area[MATERIALS_FS_AREA], I"Inform Map.eps");
@ On a release run, |cblorb| will populate the Release subfolder of Materials;
figures and sounds will be copied into the relevant subfolders. The principle
is that everything in Release can always be thrown away without loss, because
it can all be generated again.
@<The Release folder@> =
pathname_of_materials_release = Pathnames::subfolder(pathname_of_area[MATERIALS_FS_AREA], I"Release");
pathname_of_released_interpreter = Pathnames::subfolder(pathname_of_materials_release, I"interpreter");
pathname_of_released_figures = Pathnames::subfolder(pathname_of_materials_release, I"Figures");
pathname_of_released_sounds = Pathnames::subfolder(pathname_of_materials_release, I"Sounds");
@ Inform is occasionally run in a mode where it performs a release on an
existing story file (for example a 1980s Infocom one) rather than on one
that it has newly generated. This is the filename such a story file would
have by default, if so.
@<Existing story file@> =
TEMPORARY_TEXT(leaf);
WRITE_TO(leaf, "story.%S", story_filename_extension);
filename_of_existing_story_file =
Filenames::in_folder(pathname_of_area[MATERIALS_FS_AREA], leaf);
DISCARD_TEXT(leaf);
@h EILTs.
Each of the materials folder, the internal and external areas has a suite
of subfolders to hold I7 extensions (in an author tree: see below), I6
template files, language definitions and website templates.
=
void Locations::EILT_at(int area, pathname *P) {
pathname_of_extensions[area] = Pathnames::subfolder(P, I"Extensions");
pathname_of_i6t_files[area] = Pathnames::subfolder(P, I"I6T");
pathname_of_languages[area] = Pathnames::subfolder(P, I"Languages");
pathname_of_website_templates[area] = Pathnames::subfolder(P, I"Templates");
}
@h Location of extensions.
When Inform needs one of the EILT resources, it now has three places to look:
the internal resources folder, the external one, and the materials folder.
In fact, it checks them in reverse order, thus allowing the user to override
default resources.
To take the E part, within an Extensions folder, the extensions are stored
within subfolders named for their authors:
|Extensions|
| Emily Short|
| Locksmith.i7x|
This is now very much deprecated, but at one time the filename extension
|.i7x| was optional.
=
filename *Locations::of_extension(pathname *E, text_stream *title, text_stream *author, int i7x_flag) {
TEMPORARY_TEXT(leaf);
if (i7x_flag) WRITE_TO(leaf, "%S.i7x", title);
else WRITE_TO(leaf, "%S", title);
filename *F = Filenames::in_folder(Pathnames::subfolder(E, author), leaf);
DISCARD_TEXT(leaf);
return F;
}
@ Documentation is similarly arranged:
=
filename *Locations::of_extension_documentation(text_stream *title, text_stream *author) {
TEMPORARY_TEXT(leaf);
WRITE_TO(leaf, "%S.html", title);
filename *F = Filenames::in_folder(
Pathnames::subfolder(pathname_of_extension_docs_inner, author), leaf);
DISCARD_TEXT(leaf);
return F;
}
@h Location of index files.
Filenames within the |Index| subfolder. Filenames in |Details| have the form
|N_S| where |N| is the integer supplied and |S| the leafname; for instance,
|21_A.html| provides details page number 21 about actions, derived from the
leafname |A.html|.
=
filename *Locations::in_index(text_stream *leafname, int sub) {
if (pathname_of_project == NULL) return Filenames::in_folder(NULL, leafname);
if (sub >= 0) {
TEMPORARY_TEXT(full_leafname);
WRITE_TO(full_leafname, "%d_%S", sub, leafname);
filename *F = Filenames::in_folder(pathname_of_project_index_details_folder, full_leafname);
DISCARD_TEXT(full_leafname);
return F;
} else {
return Filenames::in_folder(pathname_of_project_index_folder, leafname);
}
}