# HG changeset patch # User Diggory Hardy # Date 1233491781 0 # Node ID 4084f07f2c7a2678f6bce8c8a1222a71de5a5fe3 # Parent bc697a218716083c68fe9962a97e1dcd95bada72 Added simpler mergetag readers and writers, with unittest. diff -r bc697a218716 -r 4084f07f2c7a mde/file/mergetag/MTTagReader.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/file/mergetag/MTTagReader.d Sun Feb 01 12:36:21 2009 +0000 @@ -0,0 +1,284 @@ +/* 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 . */ + +/****************************************************************************** + * This module contains a simpler, easier to use, mergetag reader. + *****************************************************************************/ +module mde.file.mergetag.MTTagReader; + +import mde.file.mergetag.internal; +import mde.file.mergetag.exception; + +import tango.io.UnicodeFile; +import tango.io.FilePath; +import Util = tango.text.Util; +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this() { + logger = Log.getLogger ("mde.file.mergetag.MTTagReader"); +} + +/** Make an TagReader class. +* +* Create an appropriate reader: MTTTagReader or MTBTagReader. +* +* Throws: +* $(TABLE +* $(TR $(TH Exception) $(TH Thrown when)) +* $(TR $(TD MTFileIOException) $(TD When extension given is neither mtt nor mtb)) +* ) +* +*/ +MTTagReader makeMTTagReader (FilePath path) { + if (path.ext == "mtb") return new MTBTagReader (path.toString); + else if (path.ext == "mtt") return new MTTTagReader (path.toString); + else throw new MTFileIOException ("Invalid mergetag extension"); +} + +abstract class MTTagReader +{ + /** The current section (null for header), tag type, tag id, and tag data. + * + * If the last call to readTag returned true, these are valid. */ + char[] section, tagType, tagID, tagData; + + /** Read the next tag. + * + * The variables section, tagType, tagID and tagData are updated, dependant + * on the type of tag read. + * + * Params: + * sectionTag = If sectionTag is true, the read tag is a section marker, + * not a data tag. + * + * Returns: + * True if a tag has been read, false at EOF. + * + * Example usage: + * ------ + * TagReader reader = ...; + * while (reader.readTag (sectionTag)) { + * if (sectionTag) { + * // section has changed, potentially do something... + * } else { + * // do something with the tag data... + * } + * } + * ------ */ + abstract bool readTag (out bool sectionTag); +} + +class MTTTagReader : MTTagReader +{ +private: + // Non-static symbols: + final char[] ErrInFile; // something like "in \"path/file.mtt\"" + + final char[] fbuf; // file is read into this + size_t pos; // position within fbuf + MTFormat fileVer = MTFormat.INVALID; // Remains INVALID until set otherwise by CTOR. + + bool fatal = false; // a fatal file error occured; don't try to recover +//END DATA + + /** Tries to read file path and checks its header. + * + * Params: + * path = The name or FilePath of the file to open. + * Standard extensions are .mtt and .mtb for text and binary files respectively. + * + * No need to close later; the whole file is read within this(). */ + public this (char[] path) { + scope (failure) + logger.warn ("Failure reading {}", path); + + // Open & read the file + try { // Supports unicode files with a BOM; defaults to UTF8 when there isn't a BOM: + scope file = new UnicodeFile!(char) (path, Encoding.Unknown); + fbuf = cast(char[]) file.read(); + } catch (Exception e) { + throwMTErr ("Error reading file: " ~ e.msg, new MTFileIOException); + } + ErrInFile = " in \"" ~ path ~ '"'; + + // Version checking & matching header section tag: + if (checkHeader(fbuf) != MTFormat.MT01) + throwMTErr("Not a valid (known) MergeTag text file" ~ ErrInFile, new MTFileFormatException); + } + + public bool readTag (out bool sectionTag) { + debug scope (failure) + logger.trace ("MTTTagReader.readTag: failure"); + if (fatal) return false; + + /* Searches fbuf starting from start to find one of <=>| and stops at its index. + + If quotable then be quote-aware for single and double quotes. + Note: there's no length restriction for the content of the quote since it could be a single + non-ascii UTF-8 char which would look like several chars. + */ + void fbufLocateDataTagChar (ref size_t pos, bool quotable) { + while (true) { + fbufIncrement (pos); + + if ((fbuf[pos] >= '<' && fbuf[pos] <= '>') || fbuf[pos] == '|') return; + else if (quotable) { + char c = fbuf[pos]; + if (c == '\'' || c == '"') { + fbufIncrement(pos); + while (fbuf[pos] != c) { + if (fbuf[pos] == '\\') ++pos; // escape seq. + fbufIncrement(pos); + } + } + } + } + } + + // Used to ignore a tag (if it starts !< or !{ or should otherwise be ignored): + bool comment = false; + while (pos < fbuf.length) { + if (Util.isSpace(fbuf[pos])) { + ++pos; + continue; + } + else if (fbuf[pos] == '<') { // data tag + char[] ErrDTAG = "Bad data tag format: not " ~ ErrInFile; + + // Type section of tag: + size_t pos_s = pos + 1; + fbufLocateDataTagChar (pos, false); // find end of type section + if (fbuf[pos] != '|') throwMTErr (ErrDTAG, new MTSyntaxException); + tagType = Util.trim (fbuf[pos_s..pos]); + + // char[] section of tag: + pos_s = pos + 1; + fbufLocateDataTagChar (pos, false); // find end of type section + if (fbuf[pos] != '=') throwMTErr (ErrDTAG, new MTSyntaxException); + tagID = cast(char[]) fbuf[pos_s..pos]; + + // Data section of tag: + pos_s = pos + 1; + fbufLocateDataTagChar (pos, true); // find end of data section + if (fbuf[pos] != '>') throwMTErr (ErrDTAG, new MTSyntaxException); + tagData = fbuf[pos_s..pos]; + ++pos; + + if (!comment) { + return true; // got a tag + } else comment = false; // cancel comment status now + } + else if (fbuf[pos] == '{') { + fbufIncrement (pos); + size_t start = pos; + uint depth = 0; // depth of embedded {} blocks + while (true) { + if (fbuf[pos] == '}') { + if (depth == 0) break; + else --depth; + } else if (fbuf[pos] == '{') + ++depth; + fbufIncrement (pos); + } + fbufIncrement(pos); + if (comment) { // simple block comment + comment = false; // end of this comment + } else { + section = cast(char[]) fbuf[start..pos]; + sectionTag = true; + return true; + } + } + else if (fbuf[pos] == '!') { // possibly a comment; check next char + comment = true; // starting a comment (or an error) + // variable is reset at end of comment + } else // must be an error + throwMTErr ("Invalid character '"~fbuf[pos..pos+1]~"' (or sequence starting \"!\") outside of tag" ~ ErrInFile, new MTSyntaxException); + } + return false; // EOF + } + + /* Increments pos and checks it hasn't hit fbuf.length . */ + private void fbufIncrement(ref size_t pos) { + ++pos; + if (pos >= fbuf.length) throwMTErr("Unexpected EOF" ~ ErrInFile, new MTSyntaxException); + } + + private void throwMTErr (char[] msg, MTException exc = new MTException) { + fatal = true; // if anyone catches the error and tries to do anything --- we're dead now + logger.error (msg); // report the error + throw exc; // and signal our error + } +} + + +/** +* Class for reading a mergetag text file. +* +* Currently only a dummy class: a MTNotImplementedException will be thrown if created. +*/ +class MTBTagReader : MTTagReader +{ + public this (char[] path) { + throw new MTNotImplementedException; + } + + bool readTag (out bool sectionTag) { + return false; + } +} + + +/** A special adapter for reading from multiple mergetag files. */ +class MTMultiTagReader : MTTagReader +{ + this (FilePath[] files) + in { + assert (files !is null, "MTMultiTagReader.this: files is null"); + } body { + Exception exc; + foreach (file; files) { + try { // try reading each file + MTTagReader r = makeMTTagReader (file); + readers ~= r; + } catch (Exception e) { + exc = e; + } + } + if (readers.length == 0) // no files have valid headers + throw exc; // fail: re-throw last exception + } + + bool readTag (out bool sectionTag) { + while (readers.length) { // something left to read + MTTagReader r = readers[0]; + bool ret = r.readTag (sectionTag); + if (ret) { + section = r.section; + tagType = r.tagType; + tagID = r.tagID; + tagData = r.tagData; + return true; + } + delete r; + readers = readers[1..$]; // done with that reader + } + return false; + } + +private: + MTTagReader[] readers; +} diff -r bc697a218716 -r 4084f07f2c7a mde/file/mergetag/MTTagUnittest.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/file/mergetag/MTTagUnittest.d Sun Feb 01 12:36:21 2009 +0000 @@ -0,0 +1,73 @@ +/* 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 . */ + +/****************************************************************************** + * A unittest for the tag reader and writer. + *****************************************************************************/ +module mde.file.mergetag.MTTagUnittest; + +debug (mdeUnitTest) { + import mde.file.mergetag.MTTagReader; + import mde.file.mergetag.MTTagWriter; + import tango.io.FilePath; + import tango.util.log.Log : Log, Logger; + + private Logger logger; + static this() { + logger = Log.getLogger ("mde.file.mergetag.MTTagUnittest"); + } + + unittest { + auto file = FilePath("unittest.mtt"); + struct S { + char[] type, id, data; + } + static S tag1 = { type:"t1", id:"i1", data:"123"}; + static S tag2 = { type:"t2", id:"i2", data:"abc"}; + static S tag3 = { type:"t3", id:"i3", data:"\" a string \""}; + static S tag4 = { type:"t1", id:"i1", data:"5.-98"}; + + MTTagWriter w = makeMTTagWriter (file.toString); + w.dataTag (tag2.type, tag2.id, tag2.data); + w.sectionTag ("one"); + w.dataTag (tag1.type, tag1.id, tag1.data); + w.sectionTag ("three"); + w.dataTag (tag3.type, tag3.id, tag3.data); + w.writeTag ("one", tag4.type, tag4.id, tag4.data); + w.close; + + MTTagReader r = makeMTTagReader (file); + bool isSecTag; + while (r.readTag (isSecTag)) { + if (isSecTag) continue; + if (r.tagID == tag1.id) { + assert (r.tagType == tag1.type, r.tagID); + assert (r.tagData == tag1.data || r.tagData == tag4.data, r.tagID); + } else if (r.tagID == tag2.id) { + assert (r.tagType == tag2.type, r.tagID); + assert (r.tagData == tag2.data, r.tagID); + } else if (r.tagID == tag3.id) { + assert (r.tagType == tag3.type, r.tagID); + assert (r.tagData == tag3.data, r.tagID); + } else assert (false, "extra tag: "~r.tagID); + } + + // Delete the unittest file now + file.remove; + + logger.info ("Unittest complete."); + } +} + diff -r bc697a218716 -r 4084f07f2c7a mde/file/mergetag/MTTagWriter.d --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mde/file/mergetag/MTTagWriter.d Sun Feb 01 12:36:21 2009 +0000 @@ -0,0 +1,102 @@ +/* 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 . */ + +/****************************************************************************** + * This module contains a simpler, easier to use, mergetag writer. + *****************************************************************************/ +module mde.file.mergetag.MTTagWriter; + +import mde.file.mergetag.internal; +import mde.file.mergetag.exception; + +import tango.io.device.File; +import tango.io.stream.Buffer; +import tango.util.log.Log : Log, Logger; + +private Logger logger; +static this() { + logger = Log.getLogger ("mde.file.mergetag.MTTagWriter"); +} + +MTTagWriter makeMTTagWriter (char[] path) { + if (path.length > 4 && path[$-4..$] == ".mtt") + return new MTTTagWriter (path); + else if (path.length > 4 && path[$-4..$] == ".mtb") + return new MTBTagWriter (path); + else { + logger.error ("Unable to determine writing format: text or binary"); + throw new MTFileFormatException; + } +} + +abstract class MTTagWriter +{ + /// Set the current section + void sectionTag (char[] section); + + /// Write a data tag + void dataTag (char[] type, char[] id, char[] data); + + /// Change the section if necessary and write a data tag + void writeTag (char[] section, char[] type, char[] id, char[] data) { + if (section != sec) + sectionTag (section); + dataTag (type, id, data); + } + + /// Close the file + void close (); + +protected: + char[] sec; // current section +} + +class MTTTagWriter : MTTagWriter +{ + /** Opens the file path for writing. Call close() when done! */ + this (char[] path) { + buffer = new BufferOutput (new File (path, File.WriteCreate)); + + buffer.append ("{" ~ CurrentVersionString ~ "}" ~ Eol); + } + + void sectionTag (char[] section) { + sec = section; + buffer.append ("{" ~ section ~ "}" ~ Eol); + } + + void dataTag (char[] type, char[] id, char[] data) { + buffer.append ("<" ~ type ~ "|" ~ id ~"=" ~ data ~ ">" ~ Eol); + } + + void close () { + buffer.flush; + buffer.close; + } + +private: + scope BufferOutput buffer; +} + +class MTBTagWriter : MTTagWriter +{ + this (char[] path) { + throw new MTNotImplementedException; + } + + void sectionTag (char[] section) {} + void dataTag (char[] type, char[] id, char[] data) {} + void close () {} +} diff -r bc697a218716 -r 4084f07f2c7a mde/file/mergetag/Reader.d --- a/mde/file/mergetag/Reader.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/file/mergetag/Reader.d Sun Feb 01 12:36:21 2009 +0000 @@ -22,8 +22,8 @@ public import mde.file.mergetag.iface.IReader; import mde.file.mergetag.DataSet; import mde.file.mergetag.DefaultData; +import mde.file.mergetag.internal; import mde.file.mergetag.exception; -import mde.file.mergetag.internal; import tango.core.Exception; @@ -55,8 +55,8 @@ * */ IReader makeReader (FilePath path, DataSet ds = null, bool rdHeader = false) { - if (path.ext == "mtb") return new MTBReader (path, ds, rdHeader); - else if (path.ext == "mtt") return new MTTReader (path, ds, rdHeader); + if (path.ext == "mtb") return new MTBReader (path.toString, ds, rdHeader); + else if (path.ext == "mtt") return new MTTReader (path.toString, ds, rdHeader); else throw new MTFileIOException ("Invalid mergetag extension"); } @@ -154,7 +154,6 @@ final char[] ErrInFile; // something like "in \"path/file.mtt\"" final char[] fbuf; // file is read into this - MTFormatVersion.VERS fileVer = MTFormatVersion.VERS.INVALID; // Remains INVALID until set otherwise by CTOR. IDataSection delegate (ID) _dataSecCreator = null; // see property setter above @@ -205,31 +204,24 @@ * would no longer be possible. */ public this (char[] path, DataSet ds = null, bool rdHeader = false) { - this (new FilePath (path), ds, rdHeader); - } - /** ditto */ - public this (FilePath path, DataSet ds = null, bool rdHeader = false) { // Create a dataset or use an existing one if (ds !is null) _dataset = ds; else _dataset = new DataSet(); // Open & read the file try { // Supports unicode files with a BOM; defaults to UTF8 when there isn't a BOM: - scope file = new UnicodeFile!(char) (path.toString, Encoding.Unknown); + scope file = new UnicodeFile!(char) (path, Encoding.Unknown); fbuf = cast(char[]) file.read(); } catch (Exception e) { throwMTErr ("Error reading file: " ~ e.msg, new MTFileIOException); } // Remember the file name so that we can report errors (somewhat) informatively: - ErrFile = path.path ~ path.file; + ErrFile = path; ErrInFile = " in \"" ~ ErrFile ~ '"'; // Version checking & matching header section tag: - if (fbuf.length < 6 || fbuf[0] != '{' || fbuf[1] != 'M' || fbuf[2] != 'T' || fbuf[5] != '}') - throwMTErr("Not a valid MergeTag text file" ~ ErrInFile, new MTFileFormatException); - fileVer = MTFormatVersion.parseString (fbuf[3..5]); - if (fileVer == MTFormatVersion.VERS.INVALID) - throwMTErr("Unrecognised MergeTag version: MT" ~ fbuf[3..5] ~ ErrInFile, new MTFileFormatException); + if (checkHeader(fbuf) != MTFormat.MT01) + throwMTErr("Not a valid (known) MergeTag text file" ~ ErrInFile, new MTFileFormatException); // Header reading/skipping: if (rdHeader) { // only bother actually reading it if it was requested @@ -520,14 +512,12 @@ } -/** A special adapter for reading from multiple mergetag files. - * - * The number of files $(B must not) exceed MAX_PATHS. */ +/** A special adapter for reading from multiple mergetag files. */ class MTMultiReader : IReader { this (FilePath[] files, DataSet ds, bool rdHeader) in { - assert (files !is null, "mdeReader.this: files is null"); + assert (files !is null, "MTMultiReader.this: files is null"); } body { // Don't let sub-readers create their own, separate, datasets: if (ds is null) ds = new DataSet; @@ -536,12 +526,12 @@ foreach (file; files) { try { // try reading header of each file IReader r = makeReader (file, ds, rdHeader); - readers[readersLen++] = r; + readers ~= r; } catch (Exception e) { exc = e; } } - if (readersLen == 0) // no files have valid headers + if (readers.length == 0) // no files have valid headers throw exc; // fail: re-throw last exception } @@ -549,11 +539,11 @@ 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); + foreach (reader; readers) reader.dataset (ds); } void dataSecCreator (IDataSection delegate (ID) dsC) { /// Set the dataSecCreator - for (uint i = 0; i < readersLen; ++i) readers[i].dataSecCreator = dsC; + foreach (reader; readers) reader.dataSecCreator = dsC; } /** Get identifiers for all sections. @@ -562,23 +552,20 @@ * together, starting with the highest-priority file. */ ID[] getSectionNames () { ID[] names; - for (int i = readersLen-1; i >= 0; --i) - names ~= readers[i].getSectionNames; + foreach_reverse (reader; readers) + names ~= reader.getSectionNames; return names; } void read () { /// Commence reading - for (uint i = 0; i < readersLen; ++i) readers[i].read(); + foreach (reader; readers) reader.read(); } void read (ID[] secSet) { /// ditto - for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); + foreach (reader; readers) reader.read(secSet); } void read (IContainer !(ID) secSet) { /// ditto - for (uint i = 0; i < readersLen; ++i) readers[i].read(secSet); + foreach (reader; readers) reader.read(secSet); } - const MAX_READERS = 4; private: - // Use a simpler static array: - IReader[MAX_READERS] readers; - ubyte readersLen = 0; + IReader[] readers; } diff -r bc697a218716 -r 4084f07f2c7a mde/file/mergetag/Writer.d --- a/mde/file/mergetag/Writer.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/file/mergetag/Writer.d Sun Feb 01 12:36:21 2009 +0000 @@ -38,7 +38,6 @@ import tango.core.Exception; import tango.io.device.File; import tango.io.stream.Buffer; -import convInt = tango.text.convert.Integer; import tango.util.log.Log : Log, Logger; private Logger logger; @@ -135,12 +134,6 @@ private: - // taken from tango.io.Console, mostly to make sure notepad can read our files: - version (Win32) - const char[] Eol = "\r\n"; - else - const char[] Eol = "\n"; - /* The container where data is written from. */ DataSet _dataset; @@ -188,7 +181,7 @@ scope(exit) buffer.flush(); // Write the header: - buffer.append ("{MT" ~ MTFormatVersion.CurrentString ~ "}" ~ Eol); + buffer.append ("{" ~ CurrentVersionString ~ "}" ~ Eol); if (_dataset.header !is null) writeSection (buffer, _dataset.header); // Write the rest: @@ -209,13 +202,12 @@ } private void writeSectionIdentifier (BufferOutput buffer, ID id) { - char[] tp = "{" ~ cast(char[])id ~ "}" ~ Eol; - buffer.append (tp); + buffer.append ("{" ~ id ~ "}" ~ Eol); } private void writeSection (BufferOutput buffer, IDataSection sec) { void writeItem (char[] tp, ID id, char[] dt) { // actually writes an item - buffer.append ("<" ~ tp ~ "|" ~ cast(char[])id ~"=" ~ dt ~ ">" ~ Eol); + buffer.append ("<" ~ tp ~ "|" ~ id ~"=" ~ dt ~ ">" ~ Eol); } sec.writeAll (&writeItem); diff -r bc697a218716 -r 4084f07f2c7a mde/file/mergetag/internal.d --- a/mde/file/mergetag/internal.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/file/mergetag/internal.d Sun Feb 01 12:36:21 2009 +0000 @@ -16,20 +16,33 @@ /// Contains functions/data structures used internally by mergetag. module mde.file.mergetag.internal; -package abstract class MTFormatVersion { - enum VERS : ubyte { // convenient list of all known file format versions + enum MTFormat : ubyte { // known formats INVALID = 0x00, MT01 = 0x01, // not yet final } /// The current MergeTag version - static const VERS Current = VERS.MT01; - static const char[2] CurrentString = "01"; + static const MTFormat CurrentVersion = MTFormat.MT01; + static const char[] CurrentVersionString = "MT01"; - static VERS parseString (char[] str) + static MTFormat checkVersion (char[] str) in { assert (str.length == 2); } body { - if (str[0] == '0' && str[1] == '1') return VERS.MT01; - else return VERS.INVALID; + if (str[0] == '0' && str[1] == '1') return MTFormat.MT01; + else return MTFormat.INVALID; } -} + + /// Check the header (first 6 bytes) and return version + static MTFormat checkHeader (char[] fbuf) { + if (fbuf.length < 6 || fbuf[0] != '{' || fbuf[1] != 'M' || fbuf[2] != 'T' || fbuf[5] != '}') + return MTFormat.INVALID; + else + return checkVersion (fbuf[3..5]); + } + + // taken from tango.io.Console, mostly to make sure notepad can read our files: + version (Win32) + const char[] Eol = "\r\n"; + else + const char[] Eol = "\n"; + diff -r bc697a218716 -r 4084f07f2c7a mde/file/mergetag/mdeUT.d --- a/mde/file/mergetag/mdeUT.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/file/mergetag/mdeUT.d Sun Feb 01 12:36:21 2009 +0000 @@ -39,7 +39,7 @@ const file = "unittest"; const ID UT_ID = cast (ID) "mdeUT"; const headInfo = "mde Unit Test"; - + DataSet dsW = new DataSet(); dsW.header = new DefaultData(); diff -r bc697a218716 -r 4084f07f2c7a mde/file/paths.d --- a/mde/file/paths.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/file/paths.d Sun Feb 01 12:36:21 2009 +0000 @@ -36,7 +36,9 @@ import mde.exception; import mde.file.mergetag.Reader; +import mde.file.mergetag.MTTagReader; import mde.file.mergetag.Writer; +import mde.file.mergetag.MTTagWriter; import mde.file.mergetag.DataSet; import mde.file.mergetag.exception; @@ -69,28 +71,33 @@ * 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. + /** Creates an MT IReader for each file (using MTMultiReader). * * 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. + * 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); + IReader makeMTReader (char[] file, PRIORITY readOrder, DataSet ds = null, bool rdHeader = false) { + return new MTMultiReader (getFiles (file, readOrder), ds, rdHeader); + } + + /** Creates an MTTagReader for each file (using MTMultiTagReader). + * + * Params as for makeMTReader. + */ + MTTagReader makeMTTagReader (char[] file, PRIORITY readOrder) { + return new MTMultiTagReader (getFiles (file, readOrder)); } /** Creates an MT writer for file deciding on the best path to use. @@ -106,13 +113,24 @@ 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. */ + /** Creates an MTTagWriter for file. */ + MTTagWriter makeMTWriter (char[] file) + { + // FIXME: use highest priority writable path + return makeMTTagWriter (paths[pathsLen-1] ~ file); + } + + /** Returns a string listing the file name or names (if readOrder is not + * HIGH_ONLY and multiple matches are found), or an error message. Intended + * for user output only. */ char[] getFileName (char[] file, PRIORITY readOrder) { - FilePath[] files = getFiles (file, readOrder); - if (files is null) - return "no file found"; + FilePath[] files; + try { + files = getFiles (file, readOrder); + } catch (NoFileException e) { + return e.msg; + } char[] ret = files[0].toString; foreach (f; files[1..$]) @@ -155,6 +173,8 @@ if (readOrder == PRIORITY.HIGH_ONLY) break; } } + if (ret is null) + throw new NoFileException ("Unable to find the file: "~filename~"[.mtt|mtb]"); return ret; } @@ -314,7 +334,6 @@ // 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. diff -r bc697a218716 -r 4084f07f2c7a mde/gui/widget/layout.d --- a/mde/gui/widget/layout.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/gui/widget/layout.d Sun Feb 01 12:36:21 2009 +0000 @@ -913,7 +913,7 @@ b.spacing = 2; foreach (ref wd; b.minWidth) wd = 10; - b.sizable[1] = b.sizable[3] = true; + b.sizable = [false, true, false, true, false]; b.setWidths; assert (b.w == 58); diff -r bc697a218716 -r 4084f07f2c7a mde/input/Input.d --- a/mde/input/Input.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/input/Input.d Sun Feb 01 12:36:21 2009 +0000 @@ -646,52 +646,52 @@ e.type = SDL_KEYDOWN; e.key.state = SDL_PRESSED; e.key.keysym.sym = 292; // SDLK_F11 - ut(e); + ut.send(e); // Right mouse button up: e.type = SDL_MOUSEBUTTONUP; e.button.button = 3; // SDL_BUTTON_RIGHT e.button.state = SDL_RELEASED; e.button.x = 291; e.button.y = 10010; - ut(e); + ut.send(e); // Mouse motion: e.type = SDL_MOUSEMOTION; e.motion.x = 63; e.motion.y = 44; e.motion.xrel = 14; e.motion.yrel = -1; - ut(e); + ut.send(e); // Joystick 2 button 5 down: e.type = SDL_JOYBUTTONDOWN; e.jbutton.which = 2; e.jbutton.button = 5; e.jbutton.state = SDL_PRESSED; - ut(e); + ut.send(e); // Same button released: e.jbutton.state = SDL_RELEASED; - ut(e); + ut.send(e); // Joystick 1 axis 8 motion: e.type = SDL_JOYAXISMOTION; e.jaxis.which = 1; e.jaxis.axis = 8; e.jaxis.value = 32767; - ut(e); + ut.send(e); // Joystick 22 ball 100 motion: e.type = SDL_JOYBALLMOTION; e.jball.which = 22; e.jball.ball = 100; e.jball.xrel = -21; e.jball.yrel = 1024; - ut(e); + ut.send(e); // Joystick 214 hat 12 DOWN-RIGHT: e.type = SDL_JOYHATMOTION; e.jhat.which = 214; e.jhat.hat = 12; e.jhat.value = SDL_HAT_RIGHTDOWN; - ut(e); + ut.send(e); // Same hat LEFT: e.jhat.value = SDL_HAT_LEFT; - ut(e); + ut.send(e); //END Post a lot of events //BEGIN Check states diff -r bc697a218716 -r 4084f07f2c7a mde/mde.d --- a/mde/mde.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/mde.d Sun Feb 01 12:36:21 2009 +0000 @@ -33,6 +33,7 @@ debug (mdeUnitTest) { // These modules contain unittests which wouldn't be run otherwise. import mde.file.ssi; import mde.file.mergetag.mdeUT; + import mde.file.mergetag.MTTagUnittest; import mde.lookup.Translation; import mde.gui.widget.layout; } diff -r bc697a218716 -r 4084f07f2c7a mde/setup/Init.d --- a/mde/setup/Init.d Fri Jan 30 15:51:42 2009 +0000 +++ b/mde/setup/Init.d Sun Feb 01 12:36:21 2009 +0000 @@ -464,7 +464,7 @@ miscOpts.maxThreads = new IntContent ("maxThreads", 4); // force up to 4 threads for unittest logger.level(Logger.Info); // hide a lot of trace messages - logger.info ("You should see some warning messages starting \"InitStage\":"); + logger.info ("You should see some messages about InitStages not run/failing:"); // Run the above. runStages!(true); assert (init1);