commit d9724293af943b481c6411ec33cad55ae87a0f25
parent fdeabfa4ab946086d5ab8547fb2a7f38c200cfaa
Author: Dominik Schmidt <das1993@hotmail.com>
Date: Mon, 22 Feb 2016 22:58:27 +0100
Mayor rewrite.
Don't implement the range-stuff myself, use a construct like
```D
iota(0,length).map!(a=>getCookie(a))
```
which leverages the random access range properties of iota.
Also, rewrite the Aggregate-type to do something similar.
Last but not least, Documentation!
Diffstat:
dfortune.d | | | 447 | ++++++++++++++++++++++++++++++++++++++----------------------------------------- |
1 file changed, 213 insertions(+), 234 deletions(-)
diff --git a/dfortune.d b/dfortune.d
@@ -1,19 +1,23 @@
module dfortune;
-import std.stdio;
-import std.conv;
-import std.bitmanip;
-import std.encoding;
+import std.stdio : File;
+import std.bitmanip : bigEndianToNative;
+import std.algorithm : map;
+import core.exception : RangeError;
import std.range;
-import std.algorithm;
-import core.exception;
-
-static struct Cookie{
- uint pos;
- uint size;
-}
-class Fortune : RandomAccessFinite!(Cookie){
- static struct Header{
+/**
+ * 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;
@@ -21,6 +25,7 @@ class Fortune : RandomAccessFinite!(Cookie){
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]);
@@ -34,46 +39,92 @@ class Fortune : RandomAccessFinite!(Cookie){
return 5*uint.sizeof+char.sizeof;
}
}
- Header header;
- string basename;
- File table,content;
- private EncodingScheme src;
- private EncodingScheme dst;
- uint pos,end;
+ invariant{
+ assert(!(table.isOpen ^ content.isOpen));
+ }
- auto @property strings(){
- return this.map!(a=>read(a));
+ /**
+ * 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;
}
- auto strings(T)(T r) if(isInputRange!(T) && is(ElementType!T == Cookie)){
- return r.map!(a=>read(a));
+
+ 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)));
}
- this(string file){
- basename=file;
- src=EncodingScheme.create("UTF-8");
- dst=EncodingScheme.create(encodingName!(char));
- initialize();
+ /**
+ * 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));
}
- ~this(){
- if(table.isOpen || content.isOpen){
- close();
- }
+ 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(){
- open();
- table.seek(0);
header.read(table);
- end=header.numstr-1;
}
- public void open(){
+ /**
+ * 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();
}
- public uint getOffset(uint i){
+ /**
+ * 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");
}
@@ -81,259 +132,187 @@ class Fortune : RandomAccessFinite!(Cookie){
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);
}
- public string sliceFile(uint pos, uint end)
+ /**
+ * 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{
- assert(table.isOpen && content.isOpen, "Fortunes must be opened before reading");
+ assertOpen();
}
body{
if(pos==end){
- return "";
+ return null;
}
content.seek(pos);
ubyte[] buf=new ubyte[end-pos];
content.rawRead(buf);
- if(src==dst){
- return cast(string) buf;
- }
-
- char[] str;
- str.reserve(buf.length);
- const(ubyte)[] derp=buf;
- while(derp.length>0){
- ubyte[4] bufbuf;
- ubyte written;
- written=cast(ubyte)dst.encode(src.safeDecode(derp),bufbuf);
- str~=bufbuf[0..written];
- }
-
- return cast(string)str;
+ return cast(char[])buf;
}
- public string read(uint i){
+ /**
+ * 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);
}
- public Cookie getCookie(uint i){
+
+ /**
+ * 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");
}
- uint pos=getOffset(i);
- uint end=getOffset(i+1)-1; //-1 to exclude \n
+ 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);
}
- public string read(in ref Cookie c){
+
+ /**
+ * 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();
}
- private Fortune clone(){
- Fortune c=new Fortune(this.basename);
-
- c.header=header;
- c.table=table;
- c.content=content;
- c.src=src;
- c.dst=dst;
- c.pos=pos;
- c.end=end;
- return c;
- }
- @property size_t length() const{
- return end-pos+1;
+ /**
+ * Wether the files are opened
+ */
+ @property private bool isOpen(){
+ return table.isOpen && content.isOpen;
}
- Fortune opSlice(size_t a, size_t b){
- Fortune c=clone();
- c.pos=cast(uint)a;
- c.end=cast(uint)b;
- return c;
+ ///Ditto
+ private void assertOpen(){
+ assert(isOpen, "Fortune must be opened before reading");
}
- @property Cookie back(){
- return getCookie(end);
- }
- Cookie moveBack(){
- return back;
- }
- void popBack(){
- end--;
- }
- int opApply(int delegate(Cookie)f){
- int ret;
- immutable uint pos=this.pos;
- while(!empty()){
- ret=f(front());
- popFront();
- if(ret){
- break;
- }
- }
- this.pos=pos;
- return ret;
- }
- Cookie moveFront(){
- return getCookie(pos);
- }
- @property bool empty() const{
- return (length==0);
- }
- @property Cookie front(){
- return moveFront();
- }
- void popFront(){
- pos++;
- }
- int opApply(int delegate(size_t,Cookie)f){
- int ret;
- size_t cnt=0;
- immutable uint pos=this.pos;
- while(!empty()){
- ret=f(cnt++, front());
- popFront();
- if(ret){
- break;
- }
+}
+
+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();
}
- this.pos=pos;
- return ret;
- }
- Cookie moveAt(size_t i){
- pos=cast(uint)i;
- return getCookie(pos);
- }
- Cookie opIndex(size_t i){
- return getCookie(cast(uint)i);
- }
-
- @property Fortune save(){
- return clone();
- }
- unittest{
- static assert(isRandomAccessRange!(Fortune));
+
+ return r.front[index];
}
-}
-struct CookieResult{
- Cookie c;
- Fortune f;
- alias c this;
- string toString(){
- return f.read(c);
+ 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;
- size_t pos,end;
+ alias put=add;
+ /**
+ * Adds a new Fortunefile by struct
+ */
void add(Fortune f){
fortunes~=f;
- end+=f.length-1;
- }
- auto @property strings(){
- return map!(a=>a.toString())(this);
- }
- auto static strings(T)(T r) if(isInputRange!T && is(ElementType!T==CookieResult)){
- return map!(a=>a.toString())(r);
}
- alias put=add;
+
+ /**
+ * Adds a new Fortunefile by file-string
+ */
void add(string f){
- Fortune n=new Fortune(f);
- n.initialize();
- add(n);
+ 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.moveFront());
+ add(r.front);
r.popFront();
}
}
- CookieResult opIndex(size_t i){
- foreach(Fortune f; fortunes.save){
- if(i>=f.length){
- i-=f.length;
- }
- else{
- return CookieResult(f.opIndex(i),f);
- }
- }
- throw new RangeError("Out of bounds");
- }
- alias opDollar=length;
- @property size_t length(){
- size_t size;
- foreach(Fortune f; fortunes)
- size+=f.length;
- return size;
- }
- @property CookieResult front(){
- return opIndex(pos);
- }
- @property CookieResult back(){
- return opIndex(end);
- }
- CookieResult moveAt(size_t i){
- pos=i;
- return opIndex(pos);
- }
- Fortunes opSlice(size_t a, size_t b){
- return Fortunes(fortunes, a, b);
- }
- @property Fortunes save(){
- return Fortunes(fortunes, pos, end);
- }
- CookieResult moveFront(){
- return front();
- }
- CookieResult moveBack(){
- return back();
+ ref Fortunes opOpAssign(string op)(Fortune f) if(op=="~"){
+ add(f);
+ return this;
}
- void popBack(){
- end--;
- }
- int opApply(int delegate(CookieResult)f){
- int ret;
- immutable size_t pos=this.pos;
- while(!empty()){
- ret=f(moveFront());
- popFront();
- if(ret){
- break;
- }
- }
- this.pos=pos;
- return ret;
+
+
+ /**
+ * 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;
}
- int opApply(int delegate(size_t,CookieResult)f){
- int ret;
- size_t i;
- immutable size_t pos=this.pos;
- while(!empty()){
- ret=f(i++,moveFront());
- popFront();
- if(ret){
- break;
- }
+
+ /**
+ * Closes all the fortunefiles
+ */
+ void close(){
+ foreach(f;fortunes){
+ f.close();
}
- this.pos=pos;
- return ret;
- }
- @property bool empty() const{
- return (pos>end);
- }
- void popFront(){
- pos++;
- }
- unittest{
- static assert(isRandomAccessRange!(Fortunes));
}
}