dfortune.d (6784B)
1 module dfortune; 2 import std.stdio : File; 3 import std.bitmanip : bigEndianToNative; 4 import std.algorithm : map; 5 import core.exception : RangeError; 6 import std.range; 7 8 /** 9 * A struct for retrieving single cookies from a cookiefile 10 * 11 * Examples: 12 * -------- 13 * Fortune f; 14 * f.open("file"); 15 * scope(exit) f.close(); 16 * writeln(f.strings[0]); 17 * -------- 18 */ 19 struct Fortune{ 20 private static struct Header{ 21 uint version_; 22 uint numstr; 23 uint longlen; 24 uint shortlen; 25 uint flags; 26 char delim; 27 void read(File f){ 28 f.seek(0); 29 ubyte[getOffset()] off; 30 auto red=f.rawRead(off); 31 version_=bigEndianToNative!(uint,uint.sizeof)(red[0*uint.sizeof..1*uint.sizeof]); 32 numstr=bigEndianToNative!(uint,uint.sizeof)(red[1*uint.sizeof..2*uint.sizeof]); 33 longlen=bigEndianToNative!(uint,uint.sizeof)(red[2*uint.sizeof..3*uint.sizeof]); 34 shortlen=bigEndianToNative!(uint,uint.sizeof)(red[3*uint.sizeof..4*uint.sizeof]); 35 flags=bigEndianToNative!(uint,uint.sizeof)(red[4*uint.sizeof..5*uint.sizeof]); 36 delim=red[getOffset()-char.sizeof]; 37 } 38 static size_t getOffset(){ 39 return 5*uint.sizeof+char.sizeof; 40 } 41 } 42 43 invariant{ 44 assert(!(table.isOpen ^ content.isOpen)); 45 } 46 47 /** 48 * A struct containing Information of a cookie inside a cookiefile 49 */ 50 static struct Cookie{ 51 ///The absolute position inside the textfile 52 size_t pos; 53 ///The length of the cookie in bytes 54 size_t size; 55 } 56 57 private Header header; 58 private File table,content; 59 60 /** 61 * Returns a random access range iterating over all cookies inside 62 * the cookiefile 63 * 64 * Returns: A random access Range iterating over all Cookie structures 65 */ 66 @property auto range(){ 67 return iota(0,header.numstr).map!(a=>getCookie(a)); 68 } 69 unittest{ 70 static assert(isRandomAccessRange!(typeof(Fortune.range))); 71 } 72 73 /** 74 * Returns a random access range iterating over the text of all cookies inside 75 * the cookiefile 76 * 77 * Returns: A random access Range iterating over all cookies 78 */ 79 @property auto strings(){ 80 return range.map!(a=>read(a)); 81 } 82 unittest{ 83 static assert(isRandomAccessRange!(typeof(Fortune.strings))); 84 } 85 86 /** 87 * Initializes the cookiefile pointing to the path file. 88 * 89 * file and file.dat have to exist! 90 * 91 * Params: 92 * file: The file to open. 93 */ 94 this(string file){ 95 open(file); 96 } 97 98 /** 99 * Reads and caches the header of the opened dat-file 100 */ 101 public void initialize(){ 102 header.read(table); 103 } 104 105 /** 106 * Opens "file" and "file.dat", and reads the header. 107 * 108 * Params: 109 * basename: The basename of the files to open 110 */ 111 public void open(string basename){ 112 table.open(basename~".dat","r"); 113 content.open(basename,"r"); 114 initialize(); 115 } 116 117 /** 118 * Gets the start of the i-th fortunecookie, or the end(=the size) of 119 * the file, if i is one out of bound. 120 * This ensures, that we can get the end of the last cookie. 121 * 122 * Params: 123 * i: The number of the cookie 124 * Returns: The offset of the i-th fortunecookie 125 * Throws: RangeError, if i is out of range 126 */ 127 private size_t getOffset(uint i){ 128 if(i>header.numstr){ 129 throw new RangeError("Range violation"); 130 } 131 if(i==header.numstr){ 132 return cast(uint)content.size; 133 } 134 table.seek(Header.getOffset()+(i+1)*4-1); 135 assert(table.isOpen); 136 ubyte[uint.sizeof] b; 137 table.rawRead(b); 138 return bigEndianToNative!(uint,uint.sizeof)(b); 139 } 140 141 /** 142 * Gets the content from the opened file (See open) from pos to end. 143 * 144 * Params: 145 * pos: The start position to read 146 * end: The position to read to. 147 * Returns: A GC-Allocated char-array containing the specified slice 148 * of the opened cookiefile 149 */ 150 public char[] sliceFile(size_t pos, size_t end) 151 in{ 152 assertOpen(); 153 } 154 body{ 155 if(pos==end){ 156 return null; 157 } 158 content.seek(pos); 159 ubyte[] buf=new ubyte[end-pos]; 160 content.rawRead(buf); 161 return cast(char[])buf; 162 } 163 164 /** 165 * Reads the i-th cookie 166 * Params: 167 * i: The cookie-index 168 * Returns: A GC-Allocated char[] buffer. 169 */ 170 public char[] read(uint i){ 171 Cookie c=getCookie(i); 172 return sliceFile(c.pos,c.pos+c.size); 173 } 174 175 /** 176 * Reads the boundaries of the i-th cookie. 177 * 178 * Params: 179 * i: THe cookie-index 180 * Returns: A Cookie struct denoting the position of the i-th cookie 181 * inside the cookiefile 182 */ 183 public Cookie getCookie(uint i) 184 in{ 185 assertOpen(); 186 } 187 body{ 188 if(i>=header.numstr){ 189 throw new RangeError("Range violation"); 190 } 191 size_t pos=getOffset(i); 192 size_t end=getOffset(i+1)-1; //-1 to exclude \n 193 if(end>pos+2){ 194 end-=2; //to exclude the following %\n 195 } 196 return Cookie(pos, end-pos); 197 } 198 199 /** 200 * Reads the boundaries of the i-th cookie. 201 * 202 * Params: 203 * i: THe cookie-index 204 * Returns: A Cookie struct denoting the position of the i-th cookie 205 * inside the cookiefile 206 */ 207 public char[] read(in ref Cookie c){ 208 return sliceFile(c.pos,c.pos+c.size); 209 } 210 211 /** 212 * Closes all opened files 213 * 214 * file and file.dat from the previous call to open, that is. 215 */ 216 public void close(){ 217 table.close(); 218 content.close(); 219 } 220 221 /** 222 * Wether the files are opened 223 */ 224 @property private bool isOpen(){ 225 return table.isOpen && content.isOpen; 226 } 227 ///Ditto 228 private void assertOpen(){ 229 assert(isOpen, "Fortune must be opened before reading"); 230 } 231 } 232 233 private auto rajoiner(Range)(Range r)if(isInputRange!(Range) && isRandomAccessRange!(ElementType!(Range))){ 234 static auto get(Range r, size_t index){ 235 while(index>=r.front.length){ 236 index-=r.front.length; 237 r.popFront(); 238 } 239 240 return r.front[index]; 241 } 242 size_t length; 243 foreach(rr;r){ 244 length+=rr.length; 245 } 246 return iota(0,length).map!(a=>get(r,a)); 247 } 248 249 /** 250 * An Fortune aggregate type 251 * 252 * Allows to load multiple fortunefiles and treat it as one. 253 * 254 * Examples: 255 * -------- 256 * Fortunes f; 257 * f~=Fortune("./fvl"); 258 * f~=Fortune("./bofh_excuses"); 259 * f.strings.take(10).joiner("\n"); 260 * f.close(); 261 * -------- 262 */ 263 struct Fortunes{ 264 Fortune[] fortunes; 265 266 alias put=add; 267 /** 268 * Adds a new Fortunefile by struct 269 */ 270 void add(Fortune f){ 271 fortunes~=f; 272 } 273 274 /** 275 * Adds a new Fortunefile by file-string 276 */ 277 void add(string f){ 278 add(Fortune(f)); 279 } 280 281 /** 282 * Adds a range of new fortunes 283 */ 284 void add(T)(T r) if(isInputRange!(T) && (is(ElementType!(T)==string) || is(ElementType!(T)==Fortune) )){ 285 while(!r.empty){ 286 add(r.front); 287 r.popFront(); 288 } 289 } 290 291 ref Fortunes opOpAssign(string op)(Fortune f) if(op=="~"){ 292 add(f); 293 return this; 294 } 295 296 297 /** 298 * Returns the aggregate cookie-struct range 299 */ 300 @property auto range(){ 301 return fortunes.map!((ref a)=>a.range).rajoiner; 302 } 303 /** 304 * Returns the aggregate cookie-string range 305 */ 306 @property auto strings(){ 307 return fortunes.map!((ref a)=>a.strings).rajoiner; 308 } 309 310 /** 311 * Closes all the fortunefiles 312 */ 313 void close(){ 314 foreach(f;fortunes){ 315 f.close(); 316 } 317 } 318 }