view mde/mergetag/write.d @ 0:d547009c104c

Repository creation. committer: Diggory Hardy <diggory.hardy@gmail.com>
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 27 Oct 2007 18:05:39 +0100
parents
children 18491334a525
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
import mde.mergetag.dataset;
import mde.mergetag.exception;

// tango imports
import tango.io.FileConduit;
import tango.io.Buffer : Buffer, IBuffer;
import tango.text.convert.Layout : Layout;
import tango.io.Print : Print;
import tango.util.log.Log : Log, Logger;

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.
 */
IWriter makeWriter (char[] path, DataSet dataset, WriterMethod method = WriterMethod.Unspecified) {
    makeWriter (new FilePath (path), dataset, method);
}
/** ditto */
IWriter makeWriter (PathView path, DataSet dataset, WriterMethod method = WriterMethod.Unspecified) {
    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);
}

/// Interface for methods and data necessarily available in TextWriter and/or BinaryWriter.
scope interface IWriter {
    char[][ID] indexTable;	// only used by TextWriter, but available in both
    
    this (char[] path, DataSet dataset_);
    this (PathView path, DataSet dataset_);
    ~this ();
    
    void write ();
}

/+
scope class BinaryWriter : IWriter
{
}
+/

/**
 * Class to write a dataset to a file.
 *
 * Is a scope class, since the file is kept open until ~this() runs.
 */
scope class TextWriter : IWriter
{
//BEGIN DATA
    /** The container where data is written from.
     */
    DataSet dataset;
    
    
    /** A table, which if created, allows items in a text file to be written with a string ID.
    *
    * If any ID (for a section or tag) to be written is found in this table, the corresponding
    * string is written instead.
    */
    char[][ID] indexTable;	// see setIndexLookupTable() doc for use.
    
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";

    bool fatal = false;		// fatal error occured: don't attempt anything else
    bool fileOpen = false;	// file needs to be closed on exit
    bool writtenHeader = false;	// The header MUST be written exactly once at the beginning of the file.
    
    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[]) )
    Print!(char) format;	// formats output to buffer
//END DATA
    
//BEGIN CTOR / DTOR
    /** Tries to open file path for writing.
    *
    * 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 dataset_) {
        this (new FilePath (path), dataset_);
    }
    /** ditto */
    public this (PathView path, DataSet dataset_) {
        try {	// open a conduit on the file
            conduit = new FileConduit (path, FileConduit.WriteCreate);
            buffer = new Buffer(conduit);
            format = new Print!(char) (new Layout!(char), buffer);
            fileOpen = true;
        } catch (Exception e) {
            throwMTErr ("Error opening file: " ~ e.msg);
        }
    }	// OK, all set to start writing.
    
    ~this () {	// close file on exit
        if (fileOpen) {
            buffer.flush();
            conduit.close();
        }
    }
//END CTOR / DTOR
    
    /** Writes the header and all DataSections.
     *
     * Firstly writes the header unless it has already been read. Then writes all DataSections
     * to the file. Thus 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 ()
    {
        // Write the header:
        if (!writtenHeader) {
            buffer ("{MT")(MTFormatVersion.CurrentString)("}")(Eol);
            writtenHeader = true;
        }
        writeSection (dataset.header);
        
        // Write the rest:
        foreach (ID id, DataSection sec; dataset.sec) {
            writeSectionIdentifier (id);
            writeSection (sec);
        }
        
        buffer.flush();
    }
    
    private void writeSectionIdentifier (ID id) {
        buffer ("{");
        char[]* p = id in indexTable;	// look for a string ID
        if (p) buffer ("\"")(*p)("\"");	// write a string ID
        else format (cast(uint) id);	// write a numeric ID
        buffer ("}")(Eol);
    }
    
    private void writeSection (DataSection sec) {
        
        buffer (Eol);			// blank line at end of file
    }
    
    private void throwMTErr (char[] msg, Exception 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
    }
}

/+
Implement std CTORs which add extensions to each filename and extra CTORs which take two filenames.
scope class DualWriter : IWriter
{
}
+/