DFortune

Unix fortune-cookie parser written in D
git clone git://xatko.vsos.ethz.ch/DFortune.git
Log | Files | Refs

commit d2f557455d923afee3d64937784460376c552e32
parent 3e28e92b15b2eb8e8e1953fd2753c64131e8f0bd
Author: Dominik Schmidt <das1993@hotmail.com>
Date:   Mon, 22 Feb 2016 23:10:29 +0100

Add a src/-subdirectory

Diffstat:
Makefile | 2+-
dfortune.d | 318-------------------------------------------------------------------------------
src/dfortune.d | 318+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 319 insertions(+), 319 deletions(-)

diff --git a/Makefile b/Makefile @@ -2,7 +2,7 @@ DMD=dmd DFLAGS?=-O DebugFLAGS?=-g LIB=dfortune -SOURCES=dfortune.d +SOURCES=src/dfortune.d OBJECTS:=$(patsubst %.d,%.o,$(SOURCES)) DOBJECTS:=$(patsubst %.o,%-debug.o,$(OBJECTS)) diff --git a/dfortune.d b/dfortune.d @@ -1,318 +0,0 @@ -module dfortune; -import std.stdio : File; -import std.bitmanip : bigEndianToNative; -import std.algorithm : map; -import core.exception : RangeError; -import std.range; - -/** - * A struct for retrieving single cookies from a cookiefile - * - * Examples: - * -------- - * Fortune f; - * f.open("file"); - * scope(exit) f.close(); - * writeln(f.strings[0]); - * -------- - */ -struct Fortune{ - private static struct Header{ - uint version_; - uint numstr; - uint longlen; - uint shortlen; - uint flags; - char delim; - void read(File f){ - f.seek(0); - ubyte[getOffset()] off; - auto red=f.rawRead(off); - version_=bigEndianToNative!(uint,uint.sizeof)(red[0*uint.sizeof..1*uint.sizeof]); - numstr=bigEndianToNative!(uint,uint.sizeof)(red[1*uint.sizeof..2*uint.sizeof]); - longlen=bigEndianToNative!(uint,uint.sizeof)(red[2*uint.sizeof..3*uint.sizeof]); - shortlen=bigEndianToNative!(uint,uint.sizeof)(red[3*uint.sizeof..4*uint.sizeof]); - flags=bigEndianToNative!(uint,uint.sizeof)(red[4*uint.sizeof..5*uint.sizeof]); - delim=red[getOffset()-char.sizeof]; - } - static size_t getOffset(){ - return 5*uint.sizeof+char.sizeof; - } - } - - invariant{ - assert(!(table.isOpen ^ content.isOpen)); - } - - /** - * A struct containing Information of a cookie inside a cookiefile - */ - static struct Cookie{ - ///The absolute position inside the textfile - size_t pos; - ///The length of the cookie in bytes - size_t size; - } - - private Header header; - private File table,content; - - /** - * Returns a random access range iterating over all cookies inside - * the cookiefile - * - * Returns: A random access Range iterating over all Cookie structures - */ - @property auto range(){ - return iota(0,header.numstr).map!(a=>getCookie(a)); - } - unittest{ - static assert(isRandomAccessRange!(typeof(Fortune.range))); - } - - /** - * Returns a random access range iterating over the text of all cookies inside - * the cookiefile - * - * Returns: A random access Range iterating over all cookies - */ - @property auto strings(){ - return range.map!(a=>read(a)); - } - unittest{ - static assert(isRandomAccessRange!(typeof(Fortune.strings))); - } - - /** - * Initializes the cookiefile pointing to the path file. - * - * file and file.dat have to exist! - * - * Params: - * file: The file to open. - */ - this(string file){ - open(file); - } - - /** - * Reads and caches the header of the opened dat-file - */ - public void initialize(){ - header.read(table); - } - - /** - * Opens "file" and "file.dat", and reads the header. - * - * Params: - * basename: The basename of the files to open - */ - public void open(string basename){ - table.open(basename~".dat","r"); - content.open(basename,"r"); - initialize(); - } - - /** - * Gets the start of the i-th fortunecookie, or the end(=the size) of - * the file, if i is one out of bound. - * This ensures, that we can get the end of the last cookie. - * - * Params: - * i: The number of the cookie - * Returns: The offset of the i-th fortunecookie - * Throws: RangeError, if i is out of range - */ - private size_t getOffset(uint i){ - if(i>header.numstr){ - throw new RangeError("Range violation"); - } - if(i==header.numstr){ - return cast(uint)content.size; - } - table.seek(Header.getOffset()+(i+1)*4-1); - assert(table.isOpen); - ubyte[uint.sizeof] b; - table.rawRead(b); - return bigEndianToNative!(uint,uint.sizeof)(b); - } - - /** - * Gets the content from the opened file (See open) from pos to end. - * - * Params: - * pos: The start position to read - * end: The position to read to. - * Returns: A GC-Allocated char-array containing the specified slice - * of the opened cookiefile - */ - public char[] sliceFile(size_t pos, size_t end) - in{ - assertOpen(); - } - body{ - if(pos==end){ - return null; - } - content.seek(pos); - ubyte[] buf=new ubyte[end-pos]; - content.rawRead(buf); - return cast(char[])buf; - } - - /** - * Reads the i-th cookie - * Params: - * i: The cookie-index - * Returns: A GC-Allocated char[] buffer. - */ - public char[] read(uint i){ - Cookie c=getCookie(i); - return sliceFile(c.pos,c.pos+c.size); - } - - /** - * Reads the boundaries of the i-th cookie. - * - * Params: - * i: THe cookie-index - * Returns: A Cookie struct denoting the position of the i-th cookie - * inside the cookiefile - */ - public Cookie getCookie(uint i) - in{ - assertOpen(); - } - body{ - if(i>=header.numstr){ - throw new RangeError("Range violation"); - } - size_t pos=getOffset(i); - size_t end=getOffset(i+1)-1; //-1 to exclude \n - if(end>pos+2){ - end-=2; //to exclude the following %\n - } - return Cookie(pos, end-pos); - } - - /** - * Reads the boundaries of the i-th cookie. - * - * Params: - * i: THe cookie-index - * Returns: A Cookie struct denoting the position of the i-th cookie - * inside the cookiefile - */ - public char[] read(in ref Cookie c){ - return sliceFile(c.pos,c.pos+c.size); - } - - /** - * Closes all opened files - * - * file and file.dat from the previous call to open, that is. - */ - public void close(){ - table.close(); - content.close(); - } - - /** - * Wether the files are opened - */ - @property private bool isOpen(){ - return table.isOpen && content.isOpen; - } - ///Ditto - private void assertOpen(){ - assert(isOpen, "Fortune must be opened before reading"); - } -} - -private auto rajoiner(Range)(Range r)if(isInputRange!(Range) && isRandomAccessRange!(ElementType!(Range))){ - static auto get(Range r, size_t index){ - while(index>=r.front.length){ - index-=r.front.length; - r.popFront(); - } - - return r.front[index]; - } - size_t length; - foreach(rr;r){ - length+=rr.length; - } - return iota(0,length).map!(a=>get(r,a)); -} - -/** - * An Fortune aggregate type - * - * Allows to load multiple fortunefiles and treat it as one. - * - * Examples: - * -------- - * Fortunes f; - * f~=Fortune("./fvl"); - * f~=Fortune("./bofh_excuses"); - * f.strings.take(10).joiner("\n"); - * f.close(); - * -------- - */ -struct Fortunes{ - Fortune[] fortunes; - - alias put=add; - /** - * Adds a new Fortunefile by struct - */ - void add(Fortune f){ - fortunes~=f; - } - - /** - * Adds a new Fortunefile by file-string - */ - void add(string f){ - add(Fortune(f)); - } - - /** - * Adds a range of new fortunes - */ - void add(T)(T r) if(isInputRange!(T) && (is(ElementType!(T)==string) || is(ElementType!(T)==Fortune) )){ - while(!r.empty){ - add(r.front); - r.popFront(); - } - } - - ref Fortunes opOpAssign(string op)(Fortune f) if(op=="~"){ - add(f); - return this; - } - - - /** - * Returns the aggregate cookie-struct range - */ - @property auto range(){ - return fortunes.map!((ref a)=>a.range).rajoiner; - } - /** - * Returns the aggregate cookie-string range - */ - @property auto strings(){ - return fortunes.map!((ref a)=>a.strings).rajoiner; - } - - /** - * Closes all the fortunefiles - */ - void close(){ - foreach(f;fortunes){ - f.close(); - } - } -} diff --git a/src/dfortune.d b/src/dfortune.d @@ -0,0 +1,318 @@ +module dfortune; +import std.stdio : File; +import std.bitmanip : bigEndianToNative; +import std.algorithm : map; +import core.exception : RangeError; +import std.range; + +/** + * A struct for retrieving single cookies from a cookiefile + * + * Examples: + * -------- + * Fortune f; + * f.open("file"); + * scope(exit) f.close(); + * writeln(f.strings[0]); + * -------- + */ +struct Fortune{ + private static struct Header{ + uint version_; + uint numstr; + uint longlen; + uint shortlen; + uint flags; + char delim; + void read(File f){ + f.seek(0); + ubyte[getOffset()] off; + auto red=f.rawRead(off); + version_=bigEndianToNative!(uint,uint.sizeof)(red[0*uint.sizeof..1*uint.sizeof]); + numstr=bigEndianToNative!(uint,uint.sizeof)(red[1*uint.sizeof..2*uint.sizeof]); + longlen=bigEndianToNative!(uint,uint.sizeof)(red[2*uint.sizeof..3*uint.sizeof]); + shortlen=bigEndianToNative!(uint,uint.sizeof)(red[3*uint.sizeof..4*uint.sizeof]); + flags=bigEndianToNative!(uint,uint.sizeof)(red[4*uint.sizeof..5*uint.sizeof]); + delim=red[getOffset()-char.sizeof]; + } + static size_t getOffset(){ + return 5*uint.sizeof+char.sizeof; + } + } + + invariant{ + assert(!(table.isOpen ^ content.isOpen)); + } + + /** + * A struct containing Information of a cookie inside a cookiefile + */ + static struct Cookie{ + ///The absolute position inside the textfile + size_t pos; + ///The length of the cookie in bytes + size_t size; + } + + private Header header; + private File table,content; + + /** + * Returns a random access range iterating over all cookies inside + * the cookiefile + * + * Returns: A random access Range iterating over all Cookie structures + */ + @property auto range(){ + return iota(0,header.numstr).map!(a=>getCookie(a)); + } + unittest{ + static assert(isRandomAccessRange!(typeof(Fortune.range))); + } + + /** + * Returns a random access range iterating over the text of all cookies inside + * the cookiefile + * + * Returns: A random access Range iterating over all cookies + */ + @property auto strings(){ + return range.map!(a=>read(a)); + } + unittest{ + static assert(isRandomAccessRange!(typeof(Fortune.strings))); + } + + /** + * Initializes the cookiefile pointing to the path file. + * + * file and file.dat have to exist! + * + * Params: + * file: The file to open. + */ + this(string file){ + open(file); + } + + /** + * Reads and caches the header of the opened dat-file + */ + public void initialize(){ + header.read(table); + } + + /** + * Opens "file" and "file.dat", and reads the header. + * + * Params: + * basename: The basename of the files to open + */ + public void open(string basename){ + table.open(basename~".dat","r"); + content.open(basename,"r"); + initialize(); + } + + /** + * Gets the start of the i-th fortunecookie, or the end(=the size) of + * the file, if i is one out of bound. + * This ensures, that we can get the end of the last cookie. + * + * Params: + * i: The number of the cookie + * Returns: The offset of the i-th fortunecookie + * Throws: RangeError, if i is out of range + */ + private size_t getOffset(uint i){ + if(i>header.numstr){ + throw new RangeError("Range violation"); + } + if(i==header.numstr){ + return cast(uint)content.size; + } + table.seek(Header.getOffset()+(i+1)*4-1); + assert(table.isOpen); + ubyte[uint.sizeof] b; + table.rawRead(b); + return bigEndianToNative!(uint,uint.sizeof)(b); + } + + /** + * Gets the content from the opened file (See open) from pos to end. + * + * Params: + * pos: The start position to read + * end: The position to read to. + * Returns: A GC-Allocated char-array containing the specified slice + * of the opened cookiefile + */ + public char[] sliceFile(size_t pos, size_t end) + in{ + assertOpen(); + } + body{ + if(pos==end){ + return null; + } + content.seek(pos); + ubyte[] buf=new ubyte[end-pos]; + content.rawRead(buf); + return cast(char[])buf; + } + + /** + * Reads the i-th cookie + * Params: + * i: The cookie-index + * Returns: A GC-Allocated char[] buffer. + */ + public char[] read(uint i){ + Cookie c=getCookie(i); + return sliceFile(c.pos,c.pos+c.size); + } + + /** + * Reads the boundaries of the i-th cookie. + * + * Params: + * i: THe cookie-index + * Returns: A Cookie struct denoting the position of the i-th cookie + * inside the cookiefile + */ + public Cookie getCookie(uint i) + in{ + assertOpen(); + } + body{ + if(i>=header.numstr){ + throw new RangeError("Range violation"); + } + size_t pos=getOffset(i); + size_t end=getOffset(i+1)-1; //-1 to exclude \n + if(end>pos+2){ + end-=2; //to exclude the following %\n + } + return Cookie(pos, end-pos); + } + + /** + * Reads the boundaries of the i-th cookie. + * + * Params: + * i: THe cookie-index + * Returns: A Cookie struct denoting the position of the i-th cookie + * inside the cookiefile + */ + public char[] read(in ref Cookie c){ + return sliceFile(c.pos,c.pos+c.size); + } + + /** + * Closes all opened files + * + * file and file.dat from the previous call to open, that is. + */ + public void close(){ + table.close(); + content.close(); + } + + /** + * Wether the files are opened + */ + @property private bool isOpen(){ + return table.isOpen && content.isOpen; + } + ///Ditto + private void assertOpen(){ + assert(isOpen, "Fortune must be opened before reading"); + } +} + +private auto rajoiner(Range)(Range r)if(isInputRange!(Range) && isRandomAccessRange!(ElementType!(Range))){ + static auto get(Range r, size_t index){ + while(index>=r.front.length){ + index-=r.front.length; + r.popFront(); + } + + return r.front[index]; + } + size_t length; + foreach(rr;r){ + length+=rr.length; + } + return iota(0,length).map!(a=>get(r,a)); +} + +/** + * An Fortune aggregate type + * + * Allows to load multiple fortunefiles and treat it as one. + * + * Examples: + * -------- + * Fortunes f; + * f~=Fortune("./fvl"); + * f~=Fortune("./bofh_excuses"); + * f.strings.take(10).joiner("\n"); + * f.close(); + * -------- + */ +struct Fortunes{ + Fortune[] fortunes; + + alias put=add; + /** + * Adds a new Fortunefile by struct + */ + void add(Fortune f){ + fortunes~=f; + } + + /** + * Adds a new Fortunefile by file-string + */ + void add(string f){ + add(Fortune(f)); + } + + /** + * Adds a range of new fortunes + */ + void add(T)(T r) if(isInputRange!(T) && (is(ElementType!(T)==string) || is(ElementType!(T)==Fortune) )){ + while(!r.empty){ + add(r.front); + r.popFront(); + } + } + + ref Fortunes opOpAssign(string op)(Fortune f) if(op=="~"){ + add(f); + return this; + } + + + /** + * Returns the aggregate cookie-struct range + */ + @property auto range(){ + return fortunes.map!((ref a)=>a.range).rajoiner; + } + /** + * Returns the aggregate cookie-string range + */ + @property auto strings(){ + return fortunes.map!((ref a)=>a.strings).rajoiner; + } + + /** + * Closes all the fortunefiles + */ + void close(){ + foreach(f;fortunes){ + f.close(); + } + } +}