view mde/mergetag/Writer.d @ 17:5f90774ea1ef

Applied the GNU GPL v2 to mde. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 15 Mar 2008 15:14:25 +0000
parents 4608be19ebe2
children 611f7b9063c6
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, version 2, as published by the Free Software Foundation.

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, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */

/**************************************************************************************************
 * 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.Writer;

// package imports
public import mde.mergetag.iface.IWriter;
import mde.mergetag.DataSet;
import mde.mergetag.internal;
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.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.
 */
IWriter makeWriter (char[] path, DataSet dataset = null, WriterMethod method = WriterMethod.FromExtension) {
    if (method == WriterMethod.FromExtension) {
        PathView fpath = new FilePath (path);
        
        if (fpath.ext == "mtt") return new MTTWriter (fpath, dataset);
        else if (fpath.ext == "mtb") return new MTBWriter (fpath, 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:
    // 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, 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 (IBuffer buffer, ID id) {
        buffer ("{" ~ cast(char[])id ~ "}" ~ Eol);
    }
    
    private void writeSection (IBuffer buffer, IDataSection 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 MTBWriter (and move both writers to own modules?).
*/
class MTBWriter : IWriter {
    public this (char[] path, DataSet ds = null) {
        this (new FilePath (path), ds);
    }
    public this (PathView 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();
    }
}