To configure the many locations used in the host filing system.


§1. Definitions.

§2. 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":

    define NO_FS_AREAS 3
    define MATERIALS_FS_AREA 0     must match ORIGIN_WAS_* constants minus 1
    define EXTERNAL_FS_AREA 1
    define INTERNAL_FS_AREA 2
    char *AREA_NAME[3] = { "from .materials", "installed", "built in" };

§3. Now for the folders:

    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;

§4. And secondly, the files:

    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;

§5. 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;
    }

The function Locations::set_project is used in 1/mr (§6).

The function Locations::set_internal is used in 1/mr (§6).

The function Locations::set_default_internal is used in 1/mr (§4.4).

The function Locations::set_external is used in 1/mr (§6).

The function Locations::set_transient is used in 1/mr (§6).

The function Locations::set_I7_source is used in 1/mr (§6).

The function Locations::set_SR_module is used in 1/mr (§6).

§6. 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 6.1>;
        <External resources 6.2>;
        <Project resources 6.3>;
        <Materials resources 6.4>;
        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;
    }

The function Locations::set_defaults is used in 1/mr (§4.5).

§6.1. 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 6.1> =

        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 6.1.1>;

This code is used in §6.

§6.1.1. Most of these files are to help Inblorb 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 6.1.1> =

        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");

This code is used in §6.1.

§6.2. 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 6.2> =

        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 6.2.1>;

        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 6.2.2>;

This code is used in §6.

§6.2.1. 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 6.2.1> =

        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");

This code is used in §6.2.

§6.2.2. The transient resources are all written by us.

<Transient external resources 6.2.2> =

        <Transient documentation 6.2.2.1>;
        <Transient telemetry 6.2.2.2>;

This code is used in §6.2.

§6.2.2.1. 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 6.2.2.1> =

        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;

This code is used in §6.2.2.

§6.2.2.2. 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 6.2.2.2> =

        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);

This code is used in §6.2.2.

§6.3. 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 Inblorb. 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 6.3> =

        <The Source folder within the project 6.3.1>;
        <The Build folder within the project 6.3.2>;
        <The Index folder within the project 6.3.3>;

        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 code is used in §6.

§6.3.1. 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 6.3.1> =

        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");

This code is used in §6.3.

§6.3.2. 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 Inblorb will produce if this is a Release run.

<The Build folder within the project 6.3.2> =

        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");

This code is used in §6.3.

§6.3.3. 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 6.3.3> =

        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");

This code is used in §6.3.

§6.4. 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 6.4> =

        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 6.4.1>;
        <The Release folder 6.4.2>;
        <Existing story file 6.4.3>;

This code is used in §6.

§6.4.1. 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 6.4.1> =

        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");

This code is used in §6.4.

§6.4.2. On a release run, Inblorb 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 6.4.2> =

        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");

This code is used in §6.4.

§6.4.3. 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 6.4.3> =

        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);

This code is used in §6.4.

§7. 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");
    }

The function Locations::EILT_at is used in §6.1, §6.2.1, §6.4.

§8. 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;
    }

The function Locations::of_extension is used in 3/rst (§6.1), 8/ec (§5.2.2.1).

§9. 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;
    }

The function Locations::of_extension_documentation is used in 8/ed2 (§3).

§10. 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);
        }
    }

The function Locations::in_index appears nowhere else.