DFortune

git clone git://xatko.vsos.ethz.ch/DFortune.git
Log | Files | Refs | README

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 }