1 module vfile;
2 
3 import core.stdc.stdlib;
4 import core.stdc.string : memcpy;
5 import std.stdio;
6 import std.typecons : Flag, Yes, No;
7 
8 /**
9  * Implements the virtual file.
10  */
11 public struct VFile{
12 	enum Orientation { unknown, narrow, wide }
13 	private void[] datastream;
14 	private size_t position;
15 	private string name;			///mostly a placeholder, but can be set if needed
16 
17 	/**
18 	 * Creates a virtual file out from an array.
19 	 */
20 	public this(T)(ref T[] target, string name = null){
21 		datastream = cast(void[])target;
22 		this.name = name;
23 	}
24 	/**
25 	 * Creates a virtual file out from a pointer and a length indicator.
26 	 */
27 	public this(void* ptr, size_t length, string name = null){
28 		datastream = ptr[0..length];
29 		this.name = name;
30 	}
31 	/**
32 	 * Indicates if the virtual file is opened.
33 	 * If the datastream isn't null, it must be open.
34 	 */
35 	public @safe @property @nogc bool isOpen(){
36 		return datastream !is null;
37 	}
38 	/**
39 	 * Closes the datastream.
40 	 */
41 	public @safe void close(){
42 		datastream = null;
43 	}
44 	/**
45 	 * Copies the data into the buffer and moves the file forward the length of the buffer.
46 	 * Throws exception if EOF reached.
47 	 */
48 	public T[] rawRead(T)(T[] buffer){
49 		//const size_t remaining = datastream.length - position;
50 		if(position + (buffer.length * T.sizeof) <= datastream.length){
51 			memcpy(buffer.ptr, datastream.ptr + position, buffer.length * T.sizeof);
52 			position += buffer.length * T.sizeof;
53 			return buffer;
54 		}else{
55 			import std.conv : to;
56 			throw new Exception("EOF reached at position " ~ to!string(position));
57 		}
58 	}
59 	/**
60 	 * Reads a single element from the stream.
61 	 * Throws Exception if EOF reached.
62 	 * Important: Does not provide any complex serialization method, so structs must avoid heap managed fields like dynamic
63 	 * arrays. They should implement a custom serializer.
64 	 */
65 	public T read(T)(){
66 		T buffer;
67 		if(remaining + T.sizeof <= datastream.length){
68 			memcpy(&buffer, datastream.ptr + position, T.sizeof);
69 			position += T.sizeof;
70 			return buffer;
71 		}else{
72 			throw new Exception("EOF has been reached");
73 		}
74 	}
75 	/**
76 	 * Writes data into the datastream.
77 	 * If the stream is shorter, then it'll be extended.
78 	 */
79 	public void rawWrite(T)(T[] buffer){
80 		if(buffer.length + position >= datastream.length){
81 			datastream.length = position;
82 			datastream.length +=  buffer.length * T.sizeof;
83 			//datastream ~= cast(void[])buffer;
84 		}//else{
85 		memcpy(datastream.ptr + position, buffer.ptr, buffer.length * T.sizeof);
86 		//}
87 		position +=  buffer.length * T.sizeof;
88 		assert(position <= datastream.length);
89 	}
90 	/**
91 	 * Writes a single element to the stream.
92 	 * Throws Exception if EOF reached.
93 	 * Important: Does not provide any complex serialization method, so structs must avoid heap managed fields like dynamic
94 	 * arrays. They should implement a custom serializer.
95 	 */
96 	public T write(T)(T buffer){
97 		if(remaining + T.sizeof <= datastream.length){
98 			memcpy(datastream.ptr + position, &buffer, T.sizeof);
99 			position += T.sizeof;
100 			return buffer;
101 		}else{
102 			throw new Exception("EOF has been reached");
103 		}
104 	}
105 	/**
106 	 * Jumps to the given location.
107 	 */
108 	@nogc @property public void seek(long offset, int origin = SEEK_SET) @trusted{
109 		assert(position <= datastream.length);
110 		final switch(origin){
111 			case SEEK_SET:
112 				position = cast(sizediff_t)offset;
113 				break;
114 			case SEEK_CUR:
115 				position += cast(sizediff_t)offset;
116 				break;
117 			case SEEK_END:
118 				position = datastream.length + cast(sizediff_t)offset;
119 				break;
120 		}
121 		position = position > datastream.length ? datastream.length : position;
122 	}
123 	/**
124 	 * Returns the current position.
125 	 */
126 	@nogc @property public size_t tell(){
127 		return position;
128 	}
129 	/**
130 	 * Return an input range to read the data stream by line.
131 	 */
132 	auto byLine(Term = char, Char = char)(Flag!"keepTerm" keepTerm = No.keepTerm, Term term = '\x0a'){
133 		class ByLineRange{
134 			public:
135 			@property bool empty(){ return lines == null; }
136 			@property Char[] front() { return curline; }
137 			void popFront(){
138 				import std.algorithm.searching : countUntil;
139 				auto len = lines.countUntil(term);
140 				if(len < 0){ // For files lacking a final newline.
141 					curline = lines;
142 					lines = null;
143 				}else{
144 					curline = keepTerm ? lines[0..len+term.sizeof] : lines[0..len];
145 					lines = lines[len+term.sizeof..$];
146 				}
147 			}
148 
149 			private:
150 			Char[] lines;
151 			Char[] curline;
152 			bool keepTerm;
153 			Term term;
154 
155 			this(Char[] data, bool keepTerm, Term term){
156 				lines = data;
157 				this.keepTerm = keepTerm;
158 				this.term = term;
159 				popFront();
160 			}
161 		}
162 		return new ByLineRange(cast(Char[])datastream, keepTerm, term);
163 	}
164 	/**
165 	 * Returns the size of the datastream.
166 	 */
167 	@nogc @property public size_t size(){
168 		return datastream.length;
169 	}
170 
171 }
172 unittest{
173 	immutable string a = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum et libero dignissim, porta odio id, 
174 			sollicitudin quam. Suspendisse ultrices quis erat nec elementum. Phasellus sollicitudin vehicula nunc, vel 
175 			laoreet magna dictum eget. Pellentesque sit amet tellus ac mauris elementum bibendum sed in eros. Nullam nec ligula 
176 			id ligula egestas malesuada. Etiam et nulla nec nibh faucibus suscipit non id felis. Integer vulputate lobortis 
177 			neque, ut congue nisl suscipit at. Nullam ac ante eu orci viverra dapibus. \n";
178 	immutable string b = "Vivamus quis finibus mi. Proin lectus enim, convallis a libero a, condimentum eleifend nisl. Vestibulum 
179 			luctus malesuada orci a ornare. Curabitur orci dolor, ultricies ut malesuada nec, semper dictum justo. Ut vestibulum 
180 			nisl id velit congue, tristique porta sapien rutrum. Pellentesque ultricies id arcu non fermentum. Ut at est ex. Sed 
181 			elementum gravida risus, et cursus magna tincidunt nec. Morbi hendrerit efficitur neque, non ultricies quam maximus 
182 			in. Sed bibendum bibendum dui, egestas dignissim orci tincidunt porta. Sed nec aliquet leo, eu posuere massa. \n";
183 	string s;
184 	VFile file = VFile(s);
185 	file.rawWrite(a);
186 	file.rawWrite(b);
187 	assert(file.size == a.length + b.length);
188 	char[] c;
189 	c.length = a.length + b.length;
190 	file.seek(0);
191 	file.rawRead(c);
192 	assert(c == a ~ b);
193 	file.seek(0);
194 	c.length = 5;
195 	file.rawRead(c);
196 	assert(c == "Lorem");
197 }
198 unittest{
199 	immutable string a = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n" ~
200 		"Suspendisse ultrices quis erat nec elementum.\n" ~
201 		"Pellentesque sit amet tellus ac mauris elementum bibendum sed in eros.\n";
202 	string s;
203 	VFile file = VFile(s);
204 	file.rawWrite(a);
205 	auto l = file.byLine();
206 	assert(l.front() == "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", l.front());
207 	l.popFront();
208 	assert(l.front() == "Suspendisse ultrices quis erat nec elementum.", l.front());
209 	l.popFront();
210 	assert(l.front() == "Pellentesque sit amet tellus ac mauris elementum bibendum sed in eros.", l.front());
211 	l.popFront();
212 	assert(l.empty(), l.front());
213 
214 	l = file.byLine(Yes.keepTerm);
215 	assert(l.front() == "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n", l.front());
216 }