changeset 134:7ababdf97748

Moved mde.setup.paths to mde.file.paths and paths.mdeReader to mde.file.mergetag.Reader.MTMultiReader.
author Diggory Hardy <diggory.hardy@gmail.com>
date Thu, 29 Jan 2009 14:59:45 +0000
parents 9fd705793568
children bc697a218716
files mde/file/mergetag/Reader.d mde/file/paths.d mde/font/font.d mde/gui/WidgetManager.d mde/input/Config.d mde/lookup/Options.d mde/lookup/Translation.d mde/setup/Init.d mde/setup/paths.d
diffstat 9 files changed, 430 insertions(+), 424 deletions(-) [+]
line wrap: on
line diff
--- a/mde/file/mergetag/Reader.d	Fri Jan 23 16:05:05 2009 +0000
+++ b/mde/file/mergetag/Reader.d	Thu Jan 29 14:59:45 2009 +0000
@@ -518,3 +518,67 @@
     void read (ID[] secSet) {}          /// ditto
     void read (IContainer!(ID) secSet) {}/// ditto
 }
+
+
+/** A special adapter for reading from multiple mergetag files.
+ *
+ * The number of files $(B must not) exceed MAX_PATHS. */
+class MTMultiReader : IReader
+{
+    this (FilePath[] files, DataSet ds, bool rdHeader)
+    in {
+        assert (files !is null, "mdeReader.this: files is null");
+    } body {
+        // Don't let sub-readers create their own, separate, datasets:
+        if (ds is null) ds = new DataSet;
+        
+        Exception exc;
+        foreach (file; files) {
+            try {   // try reading header of each file
+                IReader r = makeReader (file, ds, rdHeader);
+                readers[readersLen++] = r;
+            } catch (Exception e) {
+                exc = e;
+            }
+        }
+        if (readersLen == 0)        // no files have valid headers
+            throw exc;              // fail: re-throw last exception
+    }
+    
+    DataSet dataset () {                /// Get the DataSet
+        return readers[0].dataset;      // all readers share the same dataset
+    }
+    void dataset (DataSet ds) {         /// Set the DataSet
+        for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds);
+    }
+    
+    void dataSecCreator (IDataSection delegate (ID) dsC) {  /// Set the dataSecCreator
+        for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC;
+    }
+    
+    /** Get identifiers for all sections.
+     *
+     * Note: the identifiers from all sections in all files are just strung
+     * together, starting with the highest-priority file. */
+    ID[] getSectionNames () {
+        ID[] names;
+        for (int i = readersLen-1; i >= 0; --i)
+            names ~= readers[i].getSectionNames;
+        return names;
+    }
+    void read () {                      /// Commence reading
+        for (uint i = 0; i < readersLen; ++i) readers[i].read();
+    }
+    void read (ID[] secSet) {           /// ditto
+        for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet);
+    }
+    void read (IContainer   !(ID) secSet) {      /// ditto
+        for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet);
+    }
+    
+    const MAX_READERS = 4;
+private:
+    // Use a simpler static array:
+    IReader[MAX_READERS] readers;
+    ubyte readersLen = 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mde/file/paths.d	Thu Jan 29 14:59:45 2009 +0000
@@ -0,0 +1,360 @@
+/* LICENSE BLOCK
+Part of mde: a Modular D game-oriented Engine
+Copyright © 2007-2008 Diggory Hardy
+
+This program is free software: you can redistribute it and/or modify it under the terms
+of the GNU General Public License as published by the Free Software Foundation, either
+version 2 of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+/** Resource paths module.
+ *
+ * Internally, most mde code using files doesn't use absolute paths but paths
+ * relative to mde directory. Further, mergetag files are usually not read
+ * just from one file, but are the result of merging multiple files from the
+ * same relative paths in different directories, including the root data
+ * directory, possibly a directory in /etc, a directory for files specific to
+ * the user accont, and possibly other directories.
+ * 
+ * This module transforms relative paths to absolute paths, allowing for
+ * multiple files to be read and merged.
+ * 
+ * The base paths from which relative files are read are slit into two groups:
+ * data paths, and conf paths (usually the conf paths refer to a "conf"
+ * directory within the data paths). */
+/* Implementation note:
+* All paths are stored internally as strings, rather than as an instance of FilePath/FilePath once
+* the FilePath has served its immediate purpose, since it's more convenient and creating new
+* FilePaths for adjusted paths should be no slower than mutating existing ones. */
+module mde.file.paths;
+
+import mde.exception;
+import mde.file.mergetag.Reader;
+import mde.file.mergetag.Writer;
+import mde.file.mergetag.DataSet;
+import mde.file.mergetag.exception;
+
+import tango.io.Console;
+import tango.io.FilePath;
+version (linux) {
+    import tango.io.FileScan;
+    import tango.util.container.SortedMap;
+    import tango.sys.Environment;
+} else version (Windows)
+    import tango.sys.win32.SpecialPath;
+
+debug {
+    import tango.util.log.Log : Log, Logger;
+    private Logger logger;
+    static this() {
+	logger = Log.getLogger ("mde.file.paths");
+    }
+}
+
+/** Order to read files in.
+*
+* Values: HIGH_LOW, LOW_HIGH, HIGH_ONLY. */
+enum PRIORITY : byte { HIGH_LOW, LOW_HIGH, HIGH_ONLY }
+
+/** This struct has one instance for each "directory".
+*
+* It is the only item within this module that you should need to interact with.
+*
+* In the case of confDir, the user path is guaranteed to exist (as highest priority path). */
+struct mdeDirectory
+{
+    /** Creates an MT reader for each file.
+    *
+    * Params:
+    *   file      = The file path and name relative to the mdeDirectory, without a suffix
+    *               (e.g. "options")
+    *   readOrder = Read the highest priority or lowest priority files first? For correct merging,
+    *               this should be LOW_HIGH when newly-read items override old ones (as is the case
+    *               with DefaultData) and HIGH_LOW when the first-read items survive. Thus override
+    *               order needs to be the same for each section, except the header which is always
+    *               read with LOW_HIGH order.
+    *               Alternately, for files which shouldn't be
+    *               merged where only the highest priority file should be read, pass HIGH_ONLY.
+    *   ds        = The dataset, as for mergetag. Note: all actual readers share one dataset.
+    *   rdHeader  = Read the headers for each file and merge if rdHeader == true.
+    */
+    IReader makeMTReader (char[] file, PRIORITY readOrder, DataSet ds = null, bool rdHeader = false)
+    {
+        FilePath[] files = getFiles (file, readOrder);
+        if (files is null)
+            throw new NoFileException ("Unable to find the file: "~file~"[.mtt|mtb]");
+        
+        return new MTMultiReader (files, ds, rdHeader);
+    }
+    
+    /** Creates an MT writer for file deciding on the best path to use.
+    *
+    * Params:
+    *   file      = The file path and name relative to the mdeDirectory, without a suffix
+    *               (e.g. "options")
+    *   ds        = The dataset, as for mergetag.
+    */
+    IWriter makeMTWriter (char[] file, DataSet ds = null)
+    {
+        // FIXME: use highest priority writable path
+        return makeWriter (paths[pathsLen-1] ~ file, ds, WriterMethod.Text);
+    }
+    
+    /** Returns a string listing the file name or names (if readOrder is not HIGH_ONLY and multiple
+      * matches are found), or "no file found". Intended for user output only. */
+    char[] getFileName (char[] file, PRIORITY readOrder)
+    {
+        FilePath[] files = getFiles (file, readOrder);
+        if (files is null)
+            return "no file found";
+        
+        char[] ret = files[0].toString;
+        foreach (f; files[1..$])
+            ret ~= ", " ~ f.toString;
+        return ret;
+    }
+    
+    /// Print all paths found.
+    static void printPaths () {
+        Cout ("Data paths found:");
+        dataDir.coutPaths;
+        Cout ("\nConf paths found:");
+        confDir.coutPaths;
+        Cout ("\nLog file directory:\n\t")(logDir);
+	version (Windows) {
+	    Cout ("\nFont directory:\n\t")(fontDir).newline;
+	} else version (linux) {
+	    Cout ("\nFont files found:");
+	    foreach (f,p; fontFiles)
+		Cout ("\n\t")(f)("\t")(p[0..$-1]);
+	    Cout.newline;
+	}
+    }
+    
+private:
+    FilePath[] getFiles (char[] filename, PRIORITY readOrder)
+    {
+        FilePath[] ret;
+        
+        if (readOrder == PRIORITY.LOW_HIGH) {
+            for (size_t i = 0; i < pathsLen; ++i) {
+                FilePath file = findFile (paths[i]~filename);
+                if (file !is null)
+                    ret ~= file;
+            }
+        } else {
+            assert (readOrder == PRIORITY.HIGH_LOW ||
+                    readOrder == PRIORITY.HIGH_ONLY );
+            
+            for (int i = pathsLen - 1; i >= 0; --i) {
+                FilePath file = findFile (paths[i]~filename);
+                if (file !is null)
+                    ret ~= file;
+                if (readOrder == PRIORITY.HIGH_ONLY) break;
+            }
+        }
+        return ret;
+    }
+    
+    // Unconditionally add a path
+    void addPath (char[] path) {
+	assert (pathsLen < MAX_PATHS);
+        paths[pathsLen++] = path~'/';
+    }
+    
+    // Test a path and add if is a folder.
+    bool tryPath (char[] path, bool create = false) {
+	assert (pathsLen < MAX_PATHS);
+	FilePath fp = FilePath (path);
+        if (fp.exists && fp.isFolder) {
+            paths[pathsLen++] = path~'/';
+            return true;
+        } else if (create) {
+            try {
+                fp.create;
+                paths[pathsLen++] = fp.toString~'/';
+                return true;
+            } catch (Exception e) {
+                // No logging avaiable yet: Use Stdout/Cout
+                Cout ("Creating path "~path~" failed:" ~ e.msg).newline;
+            }
+        }
+        return false;
+    }
+    
+    void coutPaths () {
+        if (pathsLen) {
+            for (size_t i = 0; i < pathsLen; ++i)
+                Cout ("\n\t" ~ paths[i]);
+        } else
+            Cout ("[none]");
+    }
+    
+    // Use a static array to store all possible paths with separate length counters.
+    // Lowest priority paths are first.
+    char[][MAX_PATHS] paths;
+    ubyte pathsLen = 0;
+}
+
+/** These are the actual instances, one for each of the data and conf "directories". */
+mdeDirectory dataDir, confDir;
+char[] logDir;		/// Directory for log files
+
+//BEGIN Path resolution
+/** Find at least one path for each required directory. */
+debug (mdeUnitTest) {
+    /** In unittest mode, add paths unittest/data and unittest/conf before init runs. */
+    static this () {
+	dataDir.tryPath ("unittest/data");
+	confDir.tryPath ("unittest/conf");
+	if (!(dataDir.pathsLen && confDir.pathsLen))
+	    throw new mdeException ("Fatal: unittest/data and unittest/conf don't both exist");
+	// Don't bother with real paths or logDir or font dir(s) for unittest.
+    }
+}
+version (linux) {
+    SortedMap!(char[],char[]) fontFiles;	// key is file name, value is CString path
+    /** Get the actual path of a font file, or throw NoFileException if not found.
+     *
+     * Returns a C string (null terminated). */
+    char[] getFontPath (char[] file) {
+	char[] ret;
+	if (fontFiles.get (file, ret))
+	    return ret;
+	throw new NoFileException ("Unable to find font file: "~file);
+    }
+    
+    // base-path not used on posix
+    void resolvePaths (char[] base = "data") {
+        // Home directory:
+        char[] HOME = Environment.get("HOME", ".");
+        
+        // Base paths:
+        // Static data (must exist):
+        FilePath staticPath =
+                findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", base);
+        // Config (can just use defaults if necessary, so long as we can save afterwards):
+        FilePath userPath = findPath (true, HOME~"/.config/mde", HOME~"/.mde");
+        
+        // Static data paths:
+        dataDir.addPath (staticPath.toString);      // we know this is valid anyway
+        dataDir.tryPath (userPath.toString ~ "/data");
+        if (extraDataPath) dataDir.tryPath (extraDataPath);
+        if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!");
+        
+        // Configuration paths:
+        confDir.tryPath (staticPath.toString ~ "/conf");
+        confDir.tryPath ("/etc/mde");
+        confDir.tryPath (userPath.toString ~ "/conf", true);
+        if (extraConfPath) confDir.tryPath (extraConfPath);
+        if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!");
+        
+        // Logging path:
+        logDir = userPath.toString;
+	
+	// Font paths:
+	auto fs = new FileScan;
+	// Scan for directories containing truetype and type1 fonts:
+        fs.sweep ("/usr/share/fonts", delegate bool(FilePath fp, bool isDir)
+	    {	return isDir || fp.suffix == ".ttf" || fp.suffix == ".pfb";	},
+		   true);
+	fontFiles = new SortedMap!(char[],char[]);
+	foreach (fp; fs.files)
+	    fontFiles.add (fp.file, fp.cString);	// both strings should be slices of same memory
+    }
+} else version (Windows) {
+    char[] fontDir;
+    /** Get the actual path of a font file, or throw NoFileException if not found.
+     *
+     * Returns a C string (null terminated). */
+    char[] getFontPath (char[] file) {
+	FilePath path = new FilePath (fontDir~file);
+	if (path.exists && !path.isFolder)
+	    return path.cString;
+	throw new NoFileException ("Unable to find font file: "~file);
+    }
+    
+    void resolvePaths (char[] base = "./") {
+        //FIXME: Get base path from registry
+        
+        // Base paths:
+        FilePath installPath = findPath (false, base);
+        FilePath staticPath = findPath (false, installPath.toString);
+	FilePath userPath = findPath (true, getSpecialPath(CSIDL_LOCAL_APPDATA) ~ "/mde");
+        
+        // Static data paths:
+        dataDir.addPath (staticPath.toString);   // we know this is valid anyway
+	dataDir.tryPath (userPath.toString ~ "/data");
+        if (extraDataPath) dataDir.tryPath (extraDataPath);
+        if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!");
+        
+        // Configuration paths:
+        confDir.tryPath (staticPath.toString ~ "/conf");
+        confDir.tryPath (installPath.append("user").toString);
+        confDir.tryPath (userPath.toString ~ "/conf", true);
+        if (extraConfPath) confDir.tryPath (extraConfPath);
+        if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!");
+        
+        // Logging path:
+        logDir = userPath.toString;
+	
+	// Font path:
+	fontDir = getSpecialPath (CSIDL_FONTS) ~ "/";	// append separator
+    }
+} else {
+    static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!");
+}
+
+/// For command line args: these paths are added if non-null, with highest priority.
+char[] extraDataPath, extraConfPath;
+
+private {
+    class PathException : mdeException {
+        this(char[] msg) {
+            super (msg);
+        }
+    }
+    
+    // The maximum number of paths for any one "directory".
+    const MAX_PATHS = 4;
+    static assert (MTMultiReader.MAX_READERS == MAX_PATHS, "MAX_PATHS not all equal");
+    
+    /* Try each path in succession, returning the first to exist and be a folder.
+     * If none are valid and create is true, will try creating each in turn.
+     * If still none are valid, throws. */
+    FilePath findPath (bool create, char[][] paths ...) {
+        FilePath[] fps;
+        fps.length = paths.length;
+        foreach (i,path; paths) {
+            FilePath pv = new FilePath (path);
+            if (pv.exists && pv.isFolder) return pv;    // got a valid path
+            fps[i] = pv;
+        }
+        if (create) {   // try to create a folder, using each path in turn until succesful
+            foreach (fp; fps) {
+                try {
+                    return fp.create;
+                }
+                catch (Exception e) {}
+            }
+        }
+    // no valid path...
+        char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:";
+        foreach (path; paths) msg ~= "  \"" ~ path ~ '\"';
+        throw new PathException (msg);
+    }
+}
+//END Path resolution
+
+/// Thrown when makeMTReader couldn't find a file.
+class NoFileException : MTFileIOException {
+    this (char[] msg) {
+        super(msg);
+    }
+}
\ No newline at end of file
--- a/mde/font/font.d	Fri Jan 23 16:05:05 2009 +0000
+++ b/mde/font/font.d	Thu Jan 29 14:59:45 2009 +0000
@@ -22,7 +22,7 @@
 import mde.font.exception;
 
 import mde.setup.exception;     // InitStage stuff
-import mde.setup.paths;
+import mde.file.paths;
 
 import derelict.freetype.ft;
 
--- a/mde/gui/WidgetManager.d	Fri Jan 23 16:05:05 2009 +0000
+++ b/mde/gui/WidgetManager.d	Thu Jan 29 14:59:45 2009 +0000
@@ -37,7 +37,7 @@
 import mt = mde.file.mergetag.DataSet;
 import mde.file.mergetag.Reader;
 import mde.file.mergetag.Writer;
-import mde.setup.paths;
+import mde.file.paths;
 
 // Widgets to create:
 import mde.gui.widget.layout;
--- a/mde/input/Config.d	Fri Jan 23 16:05:05 2009 +0000
+++ b/mde/input/Config.d	Thu Jan 29 14:59:45 2009 +0000
@@ -19,7 +19,7 @@
 import mde.input.exception;
 
 import MT = mde.file.mergetag.Reader;
-import mde.setup.paths;
+import mde.file.paths;
 import mde.file.deserialize;
 debug import mde.file.serialize;
 
--- a/mde/lookup/Options.d	Fri Jan 23 16:05:05 2009 +0000
+++ b/mde/lookup/Options.d	Thu Jan 29 14:59:45 2009 +0000
@@ -17,7 +17,7 @@
  * of a simple type (see Options.TYPES). */
 module mde.lookup.Options;
 
-import mde.setup.paths;
+import mde.file.paths;
 import mde.exception;
 
 public import mde.content.AStringContent;
--- a/mde/lookup/Translation.d	Fri Jan 23 16:05:05 2009 +0000
+++ b/mde/lookup/Translation.d	Thu Jan 29 14:59:45 2009 +0000
@@ -38,7 +38,7 @@
 module mde.lookup.Translation;
 
 import mde.lookup.Options;
-import mde.setup.paths;
+import mde.file.paths;
 import mde.exception;
 
 import mde.file.mergetag.DataSet;
--- a/mde/setup/Init.d	Fri Jan 23 16:05:05 2009 +0000
+++ b/mde/setup/Init.d	Thu Jan 29 14:59:45 2009 +0000
@@ -44,7 +44,7 @@
 import mde.setup.exception;
 
 import mde.lookup.Options;
-import paths = mde.setup.paths;
+import paths = mde.file.paths;
 import mde.exception;           // optionsLoadException
 import imde = mde.imde;
 
--- a/mde/setup/paths.d	Fri Jan 23 16:05:05 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,418 +0,0 @@
-/* LICENSE BLOCK
-Part of mde: a Modular D game-oriented Engine
-Copyright © 2007-2008 Diggory Hardy
-
-This program is free software: you can redistribute it and/or modify it under the terms
-of the GNU General Public License as published by the Free Software Foundation, either
-version 2 of the License, or (at your option) any later version.
-
-This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-See the GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>. */
-
-/** Resource paths module.
-*
-* Internally to mde code other than code dealing directly with files and this module, paths are
-* relative to the mde directory. This module transforms those paths to absolute paths.
-*
-* Additionally, the intention is to look for all files in two directories: the installation (i.e.
-* main data) directory and a user directory (for user-specific configuration). Besides exposing
-* both paths and checking in which valid files exist, this module provides some extra mergetag
-* functionality to simplify correct reading and writing.
-*
-* Currently the paths are found as follows: (see codeDoc/paths.txt)
-*/
-/* Implementation note:
-* All paths are stored internally as strings, rather than as an instance of FilePath/FilePath once
-* the FilePath has served its immediate purpose, since it's more convenient and creating new
-* FilePaths for adjusted paths should be no slower than mutating existing ones. */
-module mde.setup.paths;
-
-import mde.exception;
-import mde.file.mergetag.Reader;
-import mde.file.mergetag.Writer;
-import mde.file.mergetag.DataSet;
-import mde.file.mergetag.exception;
-
-import tango.io.Console;
-import tango.io.FilePath;
-version (linux) {
-    import tango.io.FileScan;
-    import tango.util.container.SortedMap;
-    import tango.sys.Environment;
-} else version (Windows)
-    import tango.sys.win32.SpecialPath;
-
-debug {
-    import tango.util.log.Log : Log, Logger;
-    private Logger logger;
-    static this() {
-	logger = Log.getLogger ("mde.setup.paths");
-    }
-}
-
-/** Order to read files in.
-*
-* Values: HIGH_LOW, LOW_HIGH, HIGH_ONLY. */
-enum PRIORITY : byte { HIGH_LOW, LOW_HIGH, HIGH_ONLY }
-
-/** This struct has one instance for each "directory".
-*
-* It is the only item within this module that you should need to interact with.
-*
-* In the case of confDir, the user path is guaranteed to exist (as highest priority path). */
-struct mdeDirectory
-{
-    /** Creates an MT reader for each file.
-    *
-    * Params:
-    *   file      = The file path and name relative to the mdeDirectory, without a suffix
-    *               (e.g. "options")
-    *   readOrder = Read the highest priority or lowest priority files first? For correct merging,
-    *               this should be LOW_HIGH when newly-read items override old ones (as is the case
-    *               with DefaultData) and HIGH_LOW when the first-read items survive. Thus override
-    *               order needs to be the same for each section, except the header which is always
-    *               read with LOW_HIGH order.
-    *               Alternately, for files which shouldn't be
-    *               merged where only the highest priority file should be read, pass HIGH_ONLY.
-    *   ds        = The dataset, as for mergetag. Note: all actual readers share one dataset.
-    *   rdHeader  = Read the headers for each file and merge if rdHeader == true.
-    */
-    IReader makeMTReader (char[] file, PRIORITY readOrder, DataSet ds = null, bool rdHeader = false)
-    {
-        FilePath[] files = getFiles (file, readOrder);
-        if (files is null)
-            throw new NoFileException ("Unable to find the file: "~file~"[.mtt|mtb]");
-        
-        return new mdeReader (files, ds, rdHeader);
-    }
-    
-    /** Creates an MT writer for file deciding on the best path to use.
-    *
-    * Params:
-    *   file      = The file path and name relative to the mdeDirectory, without a suffix
-    *               (e.g. "options")
-    *   ds        = The dataset, as for mergetag.
-    */
-    IWriter makeMTWriter (char[] file, DataSet ds = null)
-    {
-        // FIXME: use highest priority writable path
-        return makeWriter (paths[pathsLen-1] ~ file, ds, WriterMethod.Text);
-    }
-    
-    /** Returns a string listing the file name or names (if readOrder is not HIGH_ONLY and multiple
-      * matches are found), or "no file found". Intended for user output only. */
-    char[] getFileName (char[] file, PRIORITY readOrder)
-    {
-        FilePath[] files = getFiles (file, readOrder);
-        if (files is null)
-            return "no file found";
-        
-        char[] ret = files[0].toString;
-        foreach (f; files[1..$])
-            ret ~= ", " ~ f.toString;
-        return ret;
-    }
-    
-    /// Print all paths found.
-    static void printPaths () {
-        Cout ("Data paths found:");
-        dataDir.coutPaths;
-        Cout ("\nConf paths found:");
-        confDir.coutPaths;
-        Cout ("\nLog file directory:\n\t")(logDir);
-	version (Windows) {
-	    Cout ("\nFont directory:\n\t")(fontDir).newline;
-	} else version (linux) {
-	    Cout ("\nFont files found:");
-	    foreach (f,p; fontFiles)
-		Cout ("\n\t")(f)("\t")(p[0..$-1]);
-	    Cout.newline;
-	}
-    }
-    
-private:
-    FilePath[] getFiles (char[] filename, PRIORITY readOrder)
-    {
-        FilePath[] ret;
-        
-        if (readOrder == PRIORITY.LOW_HIGH) {
-            for (size_t i = 0; i < pathsLen; ++i) {
-                FilePath file = findFile (paths[i]~filename);
-                if (file !is null)
-                    ret ~= file;
-            }
-        } else {
-            assert (readOrder == PRIORITY.HIGH_LOW ||
-                    readOrder == PRIORITY.HIGH_ONLY );
-            
-            for (int i = pathsLen - 1; i >= 0; --i) {
-                FilePath file = findFile (paths[i]~filename);
-                if (file !is null)
-                    ret ~= file;
-                if (readOrder == PRIORITY.HIGH_ONLY) break;
-            }
-        }
-        return ret;
-    }
-    
-    // Unconditionally add a path
-    void addPath (char[] path) {
-	assert (pathsLen < MAX_PATHS);
-        paths[pathsLen++] = path~'/';
-    }
-    
-    // Test a path and add if is a folder.
-    bool tryPath (char[] path, bool create = false) {
-	assert (pathsLen < MAX_PATHS);
-	FilePath fp = FilePath (path);
-        if (fp.exists && fp.isFolder) {
-            paths[pathsLen++] = path~'/';
-            return true;
-        } else if (create) {
-            try {
-                fp.create;
-                paths[pathsLen++] = fp.toString~'/';
-                return true;
-            } catch (Exception e) {
-                // No logging avaiable yet: Use Stdout/Cout
-                Cout ("Creating path "~path~" failed:" ~ e.msg).newline;
-            }
-        }
-        return false;
-    }
-    
-    void coutPaths () {
-        if (pathsLen) {
-            for (size_t i = 0; i < pathsLen; ++i)
-                Cout ("\n\t" ~ paths[i]);
-        } else
-            Cout ("[none]");
-    }
-    
-    // Use a static array to store all possible paths with separate length counters.
-    // Lowest priority paths are first.
-    char[][MAX_PATHS] paths;
-    ubyte pathsLen = 0;
-}
-
-/** These are the actual instances, one for each of the data and conf "directories". */
-mdeDirectory dataDir, confDir;
-char[] logDir;		/// Directory for log files
-
-//BEGIN Path resolution
-/** Find at least one path for each required directory. */
-debug (mdeUnitTest) {
-    /** In unittest mode, add paths unittest/data and unittest/conf before init runs. */
-    static this () {
-	dataDir.tryPath ("unittest/data");
-	confDir.tryPath ("unittest/conf");
-	if (!(dataDir.pathsLen && confDir.pathsLen))
-	    throw new mdeException ("Fatal: unittest/data and unittest/conf don't both exist");
-	// Don't bother with real paths or logDir or font dir(s) for unittest.
-    }
-}
-version (linux) {
-    SortedMap!(char[],char[]) fontFiles;	// key is file name, value is CString path
-    /** Get the actual path of a font file, or throw NoFileException if not found.
-     *
-     * Returns a C string (null terminated). */
-    char[] getFontPath (char[] file) {
-	char[] ret;
-	if (fontFiles.get (file, ret))
-	    return ret;
-	throw new NoFileException ("Unable to find font file: "~file);
-    }
-    
-    // base-path not used on posix
-    void resolvePaths (char[] base = "data") {
-        // Home directory:
-        char[] HOME = Environment.get("HOME", ".");
-        
-        // Base paths:
-        // Static data (must exist):
-        FilePath staticPath =
-                findPath (false, "/usr/share/games/mde", "/usr/local/share/games/mde", base);
-        // Config (can just use defaults if necessary, so long as we can save afterwards):
-        FilePath userPath = findPath (true, HOME~"/.config/mde", HOME~"/.mde");
-        
-        // Static data paths:
-        dataDir.addPath (staticPath.toString);      // we know this is valid anyway
-        dataDir.tryPath (userPath.toString ~ "/data");
-        if (extraDataPath) dataDir.tryPath (extraDataPath);
-        if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!");
-        
-        // Configuration paths:
-        confDir.tryPath (staticPath.toString ~ "/conf");
-        confDir.tryPath ("/etc/mde");
-        confDir.tryPath (userPath.toString ~ "/conf", true);
-        if (extraConfPath) confDir.tryPath (extraConfPath);
-        if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!");
-        
-        // Logging path:
-        logDir = userPath.toString;
-	
-	// Font paths:
-	auto fs = new FileScan;
-	// Scan for directories containing truetype and type1 fonts:
-	fs.sweep ("/usr/share/fonts", (FilePath fp, bool isDir)
-	    {	return isDir || fp.suffix == ".ttf" || fp.suffix == ".pfb";	},
-		   true);
-	fontFiles = new SortedMap!(char[],char[]);
-	foreach (fp; fs.files)
-	    fontFiles.add (fp.file, fp.cString);	// both strings should be slices of same memory
-    }
-} else version (Windows) {
-    char[] fontDir;
-    /** Get the actual path of a font file, or throw NoFileException if not found.
-     *
-     * Returns a C string (null terminated). */
-    char[] getFontPath (char[] file) {
-	FilePath path = new FilePath (fontDir~file);
-	if (path.exists && !path.isFolder)
-	    return path.cString;
-	throw new NoFileException ("Unable to find font file: "~file);
-    }
-    
-    void resolvePaths (char[] base = "./") {
-        //FIXME: Get base path from registry
-        
-        // Base paths:
-        FilePath installPath = findPath (false, base);
-        FilePath staticPath = findPath (false, installPath.toString);
-	FilePath userPath = findPath (true, getSpecialPath(CSIDL_LOCAL_APPDATA) ~ "/mde");
-        
-        // Static data paths:
-        dataDir.addPath (staticPath.toString);   // we know this is valid anyway
-	dataDir.tryPath (userPath.toString ~ "/data");
-        if (extraDataPath) dataDir.tryPath (extraDataPath);
-        if (!dataDir.pathsLen) throw new mdeException ("Fatal: no data path found!");
-        
-        // Configuration paths:
-        confDir.tryPath (staticPath.toString ~ "/conf");
-        confDir.tryPath (installPath.append("user").toString);
-        confDir.tryPath (userPath.toString ~ "/conf", true);
-        if (extraConfPath) confDir.tryPath (extraConfPath);
-        if (!confDir.pathsLen) throw new mdeException ("Fatal: no conf path found!");
-        
-        // Logging path:
-        logDir = userPath.toString;
-	
-	// Font path:
-	fontDir = getSpecialPath (CSIDL_FONTS) ~ "/";	// append separator
-    }
-} else {
-    static assert (false, "Platform is not linux or Windows: no support for paths on this platform yet!");
-}
-
-/// For command line args: these paths are added if non-null, with highest priority.
-char[] extraDataPath, extraConfPath;
-
-private {
-    class PathException : mdeException {
-        this(char[] msg) {
-            super (msg);
-        }
-    }
-    
-    // The maximum number of paths for any one "directory".
-    const MAX_PATHS = 4;
-
-    /* Try each path in succession, returning the first to exist and be a folder.
-     * If none are valid and create is true, will try creating each in turn.
-     * If still none are valid, throws. */
-    FilePath findPath (bool create, char[][] paths ...) {
-        FilePath[] fps;
-        fps.length = paths.length;
-        foreach (i,path; paths) {
-            FilePath pv = new FilePath (path);
-            if (pv.exists && pv.isFolder) return pv;    // got a valid path
-            fps[i] = pv;
-        }
-        if (create) {   // try to create a folder, using each path in turn until succesful
-            foreach (fp; fps) {
-                try {
-                    return fp.create;
-                }
-                catch (Exception e) {}
-            }
-        }
-    // no valid path...
-        char[] msg = "Unable to find"~(create ? " or create" : "")~" a required path! The following were tried:";
-        foreach (path; paths) msg ~= "  \"" ~ path ~ '\"';
-        throw new PathException (msg);
-    }
-//END Path resolution
-
-    /** A special adapter for reading from multiple mergetag files with the same relative path to an
-     * mdeDirectory simultaneously.
-     */
-    class mdeReader : IReader
-    {
-        private this (FilePath[] files, DataSet ds, bool rdHeader)
-        in {
-            assert (files !is null, "mdeReader.this: files is null");
-        } body {
-            // Don't let sub-readers create their own, separate, datasets:
-            if (ds is null) ds = new DataSet;
-            
-            Exception exc;
-            foreach (file; files) {
-                try {   // try reading header of each file
-                    IReader r = makeReader (file, ds, rdHeader);
-                    readers[readersLen++] = r;
-                } catch (Exception e) {
-                    exc = e;
-                }
-            }
-            if (readersLen == 0)        // no files have valid headers
-                throw exc;              // fail: re-throw last exception
-        }
-    
-        DataSet dataset () {                /// Get the DataSet
-            return readers[0].dataset;      // all readers share the same dataset
-        }
-        void dataset (DataSet ds) {         /// Set the DataSet
-            for (uint i = 0; i < readersLen; ++i) readers[i].dataset (ds);
-        }
-    
-        void dataSecCreator (IDataSection delegate (ID) dsC) {  /// Set the dataSecCreator
-            for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC;
-        }
-    
-        /** Get identifiers for all sections.
-         *
-         * Note: the identifiers from all sections in all files are just strung together, starting with
-         * the highest-priority file. */
-        ID[] getSectionNames () {
-            ID[] names;
-            for (int i = readersLen-1; i >= 0; --i) names ~= readers[i].getSectionNames;
-            return names;
-        }
-        void read () {                      /// Commence reading
-            for (uint i = 0; i < readersLen; ++i) readers[i].read();
-        }
-        void read (ID[] secSet) {           /// ditto
-            for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet);
-        }
-        void read (IContainer   !(ID) secSet) {      /// ditto
-            for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet);
-        }
-        
-        private:
-            IReader[MAX_PATHS] readers;
-            ubyte readersLen = 0;
-            
-            PRIORITY rdOrder;
-    }
-}
-
-/// Thrown when makeMTReader couldn't find a file.
-class NoFileException : MTFileIOException {
-    this (char[] msg) {
-        super(msg);
-    }
-}
\ No newline at end of file