Mercurial > projects > mde
view mde/file/mergetag/Writer.d @ 136:4084f07f2c7a
Added simpler mergetag readers and writers, with unittest.
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Sun, 01 Feb 2009 12:36:21 +0000 |
parents | b16a534f5302 |
children | 9f035cd139c6 |
line wrap: on
line source
/* 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/>. */ /************************************************************************************************** * This module contains all writing functions, for both binary and text MergeTag files. * * Files can be written in a text or binary form; binary is faster and smaller while text allows * editing with an ordinary text editor. TextWriter and BinaryWriter are the main classes, both of * which implement the interface IWriter. DualWriter is another class implementing IWriter, which * contains a private instance of a TextWriter and a BinaryWriter and implements all methods in the * interface simply by chaining the appropriate method from each of these classes, thus performing * two writes at once. * * Any of these three classes may be used directly, or makeWriter may be invoked to create an * instance of the appropriate class. *************************************************************************************************/ module mde.file.mergetag.Writer; // package imports public import mde.file.mergetag.iface.IWriter; import mde.file.mergetag.DataSet; import mde.file.mergetag.internal; import mde.file.mergetag.exception; // tango imports import tango.core.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.Writer"); } /** Method to create and return either a MTTWriter or a MTBWriter. * * Has two modes of operation: if method is FromExtension, examines the existing extension and * creates a MTT/MTB writer if the extension is mtt or mtb (throwing if not). * * Otherwise, writing format is determined directly by method, and appropriate extensions are * added to the file name without checking for an existing extension. * * Params: * path = File path * dataset = Dataset passed to Writer to write from (if null, must be set before write() is called) * method = $(TABLE * $(TR $(TH Value) $(TH Writer returned) $(TH Suffix added)) * $(TR $(TD FromExtension) $(TD MTBWriter or MTTWriter)$(TD $(I none))) * $(TR $(TD Binary) $(TD MTBWriter) $(TD .mtb)) * $(TR $(TD Text) $(TD MTTWriter) $(TD .mtt)) * $(TR $(TD Both) $(TD DualWriter) $(TD .mtb / .mtt)) * ) * * Throws: * MTFileFormatException if neither test can deduce the writing method, the supplied writing * method is invalid or the determined/supplied method is not yet implemented. * * Use as: * ----------------------- * DataSet dataset; // contains data to write * IWriter foo; * try { * foo = makeWriter(...); * foo.write(); * } * catch (MTException) {} * ----------------------- * Where the makeWriter line has one of the following forms: * ----------------------- * foo = makeWriter("foo.mtt", dataset); * foo = makeWriter("foo", dataset, WriterMethod.Text); * ----------------------- * * Throws: * MTFileFormatException if unable to determine writing format or use requested format. */ //FIXME: separate functions for separate functionality, like in Reader? IWriter makeWriter (char[] path, DataSet dataset = null, WriterMethod method = WriterMethod.FromExtension) { if (method == WriterMethod.FromExtension) { if (path.length > 4 && path[$-4..$] == ".mtt") return new MTTWriter (path, dataset); else if (path.length > 4 && path[$-4..$] == ".mtb") return new MTBWriter (path, dataset); else { logger.error ("Unable to determine writing format: text or binary"); throw new MTFileFormatException; } } else { if (method == WriterMethod.Binary) return new MTBWriter (path~".mtb", dataset); else if (method == WriterMethod.Text) return new MTTWriter (path~".mtt", dataset); else if (method == WriterMethod.Both) return new DualWriter (path, dataset); else throw new MTFileFormatException; } } /** * Class to write a dataset to a file. * * Files are only actually open for writing while the write() method is running. * * Throws: * $(TABLE * $(TR $(TH Exception) $(TH Thrown when)) * $(TR $(TD MTNoDataSetException) $(TD No dataset is available to write from)) * $(TR $(TD MTFileIOException) $(TD An error occurs while attemting to write the file)) * $(TR $(TD MTException) $(TD An unexpected error occurs)) * ) * Note that all exceptions extend MTException; unlike Reader exceptions don't block further calls. */ class MTTWriter : IWriter { //BEGIN DATA /// Get or set the DataSet (i.e. the container from which all data is written). DataSet dataset () { return _dataset; } void dataset (DataSet ds) /// ditto { _dataset = ds; } private: /* The container where data is written from. */ DataSet _dataset; char[] _path; //END DATA //BEGIN CTOR / DTOR /** Prepares to open file path for writing. * * The call doesn't actually execute any code so cannot fail (unless out of memory). * * Params: * path = The name of the file to open. * Standard extensions are .mtt and .mtb for text and binary files respectively. * dataset_ = If null create a new DataSet, else use existing DataSet *dataset_ and merge read * data into it. */ public this (char[] path, DataSet ds = null) { _path = path; _dataset = ds; } //END CTOR / DTOR /** Writes the header and all DataSections. * * Firstly writes the header unless it has already been written. Then writes all DataSections * to the file. Thus if write is called more than once with or without changing the DataSet the * header should be written only once. This behaviour could, for instance, be used to write * multiple DataSets into one file without firstly merging them. Note that this behaviour may * be changed when binary support is added. */ public void write () { if (!_dataset) throwMTErr ("write(): no Dataset available to write from!", new MTNoDataSetException ()); try { scope File conduit; // actual conduit; don't use directly when there's content in the buffer scope BufferOutput buffer; // write strings directly to this (use opCall(void[]) ) // Open a conduit on the file: conduit = new File (_path, File.WriteCreate); scope(exit) conduit.close(); buffer = new BufferOutput(conduit); // And a buffer scope(exit) buffer.flush(); // Write the header: buffer.append ("{" ~ CurrentVersionString ~ "}" ~ Eol); if (_dataset.header !is null) writeSection (buffer, _dataset.header); // Write the rest: foreach (ID id, IDataSection sec; _dataset.sec) { writeSectionIdentifier (buffer, id); writeSection (buffer, sec); } buffer.flush(); } catch (IOException e) { throwMTErr ("Error writing to file: " ~ e.msg, new MTFileIOException); } catch (Exception e) { throwMTErr ("Unexpected exception when writing file: " ~ e.msg); } } private void writeSectionIdentifier (BufferOutput buffer, ID id) { 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 ~ "|" ~ id ~"=" ~ dt ~ ">" ~ Eol); } sec.writeAll (&writeItem); buffer.append (Eol); // blank line at end of each section } private void throwMTErr (char[] msg, Exception exc = new MTException) { logger.error (msg); // report the error throw exc; // and signal our error } } /* * Implement MTBWriter (and move both writers to own modules?). */ class MTBWriter : IWriter { public this (char[] path, DataSet ds = null) { throw new MTNotImplementedException; /+_path = path; _dataset = ds;+/ } DataSet dataset () { return null; } void dataset (DataSet) {} void write () {} } /* Basic implementation for mtt only. * *Implement std CTORs which add extensions to each filename and extra CTORs which take two filenames. */ class DualWriter : IWriter { /** The individual writers. * * Potentially could be used directly, but there should be no need. */ MTTWriter mtt; //MTBWriter mtb; /** ditto */ public this (char[] path, DataSet ds = null) { mtt = new MTTWriter (path~".mtt", ds); } DataSet dataset () { return mtt.dataset; } void dataset (DataSet ds) { mtt.dataset = ds; } /** Write. * * Write text then binary, so the mtb file will be the most recent. */ void write () { mtt.write(); } }