Mercurial > projects > mde
view mde/mergetag/write.d @ 11:b940f267419e
Options class created & changes to mergetag exception messages.
Options class created (barebones). Loading/saving from Init.
Init no longer runs cleanup functions after initialisation failure.
Improved mergetag exception messages & error reporting.
committer: Diggory Hardy <diggory.hardy@gmail.com>
author | Diggory Hardy <diggory.hardy@gmail.com> |
---|---|
date | Thu, 21 Feb 2008 09:05:33 +0000 |
parents | 4c3575400769 |
children |
line wrap: on
line source
/************************************************************************************************** * 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.mergetag.write; // package imports public import mde.mergetag.dataset; import mde.mergetag.exception; // tango imports import tango.core.Exception; import tango.io.FileConduit; import tango.io.Buffer : Buffer, IBuffer; import tango.io.Print : Print; import convInt = tango.text.convert.Integer; import tango.util.log.Log : Log, Logger; private Logger logger; static this () { logger = Log.getLogger ("mde.mergetag.write"); } /** * Enumeration for specifying the writing method ("Params" section shows possible values). * * Params: * Unspecified = If the filename ends with one of .mtb or .mtt, the file is written in * that format. Otherwise a binary mode is assumed. * Binary = Use binary mode (default extension: .mtb or no extension). * Text = Use text mode (default extension: .mtt; as with the above it is not automatically added). * Both = */ enum WriterMethod : byte { Unspecified = -1, Binary = 1, Text = 2, Both = 3 } /** Method to create and return either a TextWriter or a BinaryWriter, depending primalily on the * method parameter and using the filename extension as a fallback. * * An exception is thrown if neither test can deduce the writing method. * * Use as: * ----------------------- * DataSet dataset; // contains data to write * IWriter foo; * try { * foo = makeWriter("foo.mtt", dataset); * foo.write(); * } * catch (MTException) {} * ----------------------- * * Throws: * MTFileFormatException if unable to determine writing format or use requested format. */ IWriter makeWriter (char[] path, DataSet dataset = null, WriterMethod method = WriterMethod.Unspecified) { return makeWriter (new FilePath (path), dataset, method); } /** ditto */ IWriter makeWriter (PathView path, DataSet dataset = null, WriterMethod method = WriterMethod.Unspecified) { void throwMTErr (char[] msg, Exception exc = new MTException) { logger.error (msg); throw exc; } if (method == WriterMethod.Unspecified) { if (path.ext == "mtt") method = WriterMethod.Text; else if (path.ext == "mtb") method = WriterMethod.Binary; else throwMTErr ("Unable to determine writing format: text or binary", new MTFileFormatException); } if (method == WriterMethod.Binary) throwMTErr ("Binary writing not supported yet!", new MTFileFormatException); else if (method == WriterMethod.Text) return new TextWriter (path, dataset); else if (method == WriterMethod.Both) throwMTErr ("Dual writing not supported yet!", new MTFileFormatException); else debug throwMTErr ("Bad value of method", new MTFileFormatException); } /// Interface for methods and data necessarily available in TextWriter and/or BinaryWriter. // FIXME: rename MTWriter or so. interface IWriter { /// Get or set the DataSet. DataSet dataset (); void dataset (DataSet); /// ditto void write (); /// Writing method. } /+ scope class BinaryWriter : IWriter { } +/ /** * 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 TextWriter : IWriter { //BEGIN DATA /// Get or set the DataSet. DataSet dataset () { return dataset; } void dataset (DataSet ds) /// ditto { dataset = ds; } 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; PathView _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 or FilePath 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) { this (new FilePath (path), ds); } /** ditto */ public this (PathView 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 { FileConduit conduit; // actual conduit; don't use directly when there's content in the buffer IBuffer buffer; // write strings directly to this (use opCall(void[]) ) // Open a conduit on the file: conduit = new FileConduit (_path, FileConduit.WriteCreate); scope(exit) conduit.close(); buffer = new Buffer(conduit); // And a buffer scope(exit) buffer.flush(); // Write the header: buffer ("{MT" ~ MTFormatVersion.CurrentString ~ "}" ~ Eol); if (_dataset.header !is null) writeSection (buffer, _dataset.header); // Write the rest: foreach (ID id, DataSection 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 (IBuffer buffer, ID id) { buffer ("{" ~ cast(char[])id ~ "}" ~ Eol); } private void writeSection (IBuffer buffer, DataSection sec) { void writeItem (char[] tp, ID id, char[] dt) { // actually writes an item buffer ("<" ~ tp ~ "|" ~ cast(char[])id ~"=" ~ dt ~ ">" ~ Eol); } sec.writeAll (&writeItem); buffer (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 std CTORs which add extensions to each filename and extra CTORs which take two filenames. scope class DualWriter : IWriter { } +/