mirror of https://github.com/buggins/dlangide.git
fixes; add more files to sample projects
This commit is contained in:
parent
fd763e525a
commit
d580165beb
|
@ -194,7 +194,6 @@
|
|||
<File path="src\ddc\lexer\exceptions.d" />
|
||||
<File path="src\ddc\lexer\Lexer.d" />
|
||||
<File path="src\ddc\lexer\LexerException.d" />
|
||||
<File path="src\ddc\lexer\LineStream.d" />
|
||||
<File path="src\ddc\lexer\SourceEncodingException.d" />
|
||||
<File path="src\ddc\lexer\textsource.d" />
|
||||
<File path="src\ddc\lexer\Tokenizer.d" />
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// D grammar - according to http://dlang.org/grammar
|
||||
|
||||
module ddc.lexer.Lexer;
|
||||
import ddc.lexer.LineStream;
|
||||
import ddc.lexer.Tokenizer;
|
||||
|
||||
/** Lexem type constants */
|
||||
|
@ -280,9 +279,4 @@ class AltDeclarator : Lexem {
|
|||
|
||||
class Lexer
|
||||
{
|
||||
LineStream _lineStream;
|
||||
this(LineStream lineStream)
|
||||
{
|
||||
_lineStream = lineStream;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,589 +0,0 @@
|
|||
module ddc.lexer.LineStream;
|
||||
|
||||
import std.stream;
|
||||
import ddc.lexer.exceptions;
|
||||
import std.stdio;
|
||||
import std.conv;
|
||||
import ddc.lexer.textsource;
|
||||
|
||||
class LineStream : SourceLines {
|
||||
public enum EncodingType {
|
||||
ASCII,
|
||||
UTF8,
|
||||
UTF16BE,
|
||||
UTF16LE,
|
||||
UTF32BE,
|
||||
UTF32LE
|
||||
};
|
||||
|
||||
static immutable uint LINE_POSITION_UNDEFINED = uint.max;
|
||||
static immutable int TEXT_BUFFER_SIZE = 1024;
|
||||
static immutable int BYTE_BUFFER_SIZE = 512;
|
||||
static immutable int QUARTER_BYTE_BUFFER_SIZE = BYTE_BUFFER_SIZE / 4;
|
||||
|
||||
InputStream _stream;
|
||||
string _filename;
|
||||
SourceFile _file;
|
||||
ubyte[] _buf; // stream reading buffer
|
||||
uint _pos; // reading position of stream buffer
|
||||
uint _len; // number of bytes in stream buffer
|
||||
bool _streamEof; // true if input stream is in EOF state
|
||||
uint _line; // current line number
|
||||
|
||||
uint _textPos; // start of text line in text buffer
|
||||
uint _textLen; // position of last filled char in text buffer + 1
|
||||
dchar[] _textBuf; // text buffer
|
||||
bool _eof; // end of file, no more lines
|
||||
|
||||
override @property SourceFile file() { return _file; }
|
||||
@property string filename() { return _file.filename; }
|
||||
override @property uint line() { return _line; }
|
||||
@property EncodingType encoding() { return _encoding; }
|
||||
override @property int errorCode() { return _errorCode; }
|
||||
override @property string errorMessage() { return _errorMessage; }
|
||||
override @property int errorLine() { return _errorLine; }
|
||||
override @property int errorPos() { return _errorPos; }
|
||||
|
||||
immutable EncodingType _encoding;
|
||||
|
||||
int _errorCode;
|
||||
string _errorMessage;
|
||||
uint _errorLine;
|
||||
uint _errorPos;
|
||||
|
||||
protected this(InputStream stream, SourceFile file, EncodingType encoding, ubyte[] buf, uint offset, uint len) {
|
||||
_file = file;
|
||||
_stream = stream;
|
||||
_encoding = encoding;
|
||||
_buf = buf;
|
||||
_len = len;
|
||||
_pos = offset;
|
||||
_streamEof = _stream.eof;
|
||||
}
|
||||
|
||||
// returns slice of bytes available in buffer
|
||||
uint readBytes() {
|
||||
uint bytesLeft = _len - _pos;
|
||||
if (_streamEof || bytesLeft > QUARTER_BYTE_BUFFER_SIZE)
|
||||
return bytesLeft;
|
||||
if (_pos > 0) {
|
||||
for (uint i = 0; i < bytesLeft; i++)
|
||||
_buf[i] = _buf[i + _pos];
|
||||
_len = bytesLeft;
|
||||
_pos = 0;
|
||||
}
|
||||
uint bytesRead = cast(uint)_stream.read(_buf[_len .. BYTE_BUFFER_SIZE]);
|
||||
_len += bytesRead;
|
||||
_streamEof = _stream.eof;
|
||||
return _len - _pos; //_buf[_pos .. _len];
|
||||
}
|
||||
|
||||
// when bytes consumed from byte buffer, call this method to update position
|
||||
void consumedBytes(uint count) {
|
||||
_pos += count;
|
||||
}
|
||||
|
||||
// reserve text buffer for specified number of characters, and return pointer to first free character in buffer
|
||||
dchar * reserveTextBuf(uint len) {
|
||||
// create new text buffer if necessary
|
||||
if (_textBuf == null) {
|
||||
if (len < TEXT_BUFFER_SIZE)
|
||||
len = TEXT_BUFFER_SIZE;
|
||||
_textBuf = new dchar[len];
|
||||
return _textBuf.ptr;
|
||||
}
|
||||
uint spaceLeft = cast(uint)_textBuf.length - _textLen;
|
||||
if (spaceLeft >= len)
|
||||
return _textBuf.ptr + _textLen;
|
||||
// move text to beginning of buffer, if necessary
|
||||
if (_textPos > _textBuf.length / 2) {
|
||||
uint charCount = _textLen - _textPos;
|
||||
dchar * p = _textBuf.ptr;
|
||||
for (uint i = 0; i < charCount; i++)
|
||||
p[i] = p[i + _textPos];
|
||||
_textLen = charCount;
|
||||
_textPos = 0;
|
||||
}
|
||||
// resize buffer if necessary
|
||||
if (_textLen + len > _textBuf.length) {
|
||||
// resize buffer
|
||||
uint newsize = cast(uint)_textBuf.length * 2;
|
||||
if (newsize < _textLen + len)
|
||||
newsize = _textLen + len;
|
||||
_textBuf.length = newsize;
|
||||
}
|
||||
return _textBuf.ptr + _textLen;
|
||||
}
|
||||
|
||||
void appendedText(uint len) {
|
||||
//writeln("appended ", len, " chars of text"); //:", _textBuf[_textLen .. _textLen + len]);
|
||||
_textLen += len;
|
||||
}
|
||||
|
||||
void setError(int code, string message, uint errorLine, uint errorPos) {
|
||||
_errorCode = code;
|
||||
_errorMessage = message;
|
||||
_errorLine = errorLine;
|
||||
_errorPos = errorPos;
|
||||
}
|
||||
|
||||
// override to decode text
|
||||
abstract uint decodeText();
|
||||
|
||||
override public dchar[] readLine() {
|
||||
if (_errorCode != 0) {
|
||||
//writeln("error ", _errorCode, ": ", _errorMessage, " in line ", _errorLine);
|
||||
return null; // error detected
|
||||
}
|
||||
if (_eof) {
|
||||
//writeln("EOF found");
|
||||
return null;
|
||||
}
|
||||
_line++;
|
||||
uint p = 0;
|
||||
uint eol = LINE_POSITION_UNDEFINED;
|
||||
uint eof = LINE_POSITION_UNDEFINED;
|
||||
uint lastchar = LINE_POSITION_UNDEFINED;
|
||||
do {
|
||||
if (_errorCode != 0) {
|
||||
//writeln("error ", _errorCode, ": ", _errorMessage, " in line ", _errorLine);
|
||||
return null; // error detected
|
||||
}
|
||||
uint charsLeft = _textLen - _textPos;
|
||||
if (p >= charsLeft) {
|
||||
uint decodedChars = decodeText();
|
||||
if (_errorCode != 0) {
|
||||
return null; // error detected
|
||||
}
|
||||
charsLeft = _textLen - _textPos;
|
||||
if (decodedChars == 0) {
|
||||
eol = charsLeft;
|
||||
eof = charsLeft;
|
||||
lastchar = charsLeft;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (; p < charsLeft; p++) {
|
||||
dchar ch = _textBuf[_textPos + p];
|
||||
if (ch == 0x0D) {
|
||||
lastchar = p;
|
||||
if (p == charsLeft - 1) {
|
||||
// need one more char to check if it's 0D0A or just 0D eol
|
||||
//writeln("read one more char for 0D0A detection");
|
||||
decodeText();
|
||||
if (_errorCode != 0) {
|
||||
return null; // error detected
|
||||
}
|
||||
charsLeft = _textLen - _textPos;
|
||||
}
|
||||
dchar ch2 = (p < charsLeft - 1) ? _textBuf[_textPos + p + 1] : 0;
|
||||
if (ch2 == 0x0A)
|
||||
eol = p + 2;
|
||||
else
|
||||
eol = p + 1;
|
||||
break;
|
||||
} else if (ch == 0x0A || ch == 0x2028 || ch == 0x2029) {
|
||||
// single char eoln
|
||||
lastchar = p;
|
||||
eol = p + 1;
|
||||
break;
|
||||
} else if (ch == 0 || ch == 0x001A) {
|
||||
// eof
|
||||
//writeln("EOF char found");
|
||||
lastchar = p;
|
||||
eol = eof = p + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (eol == LINE_POSITION_UNDEFINED);
|
||||
uint lineStart = _textPos;
|
||||
uint lineEnd = _textPos + lastchar;
|
||||
_textPos += eol; // consume text
|
||||
if (eof != LINE_POSITION_UNDEFINED) {
|
||||
_eof = true;
|
||||
//writeln("Setting eof flag. lastchar=", lastchar, ", p=", p, ", lineStart=", lineStart);
|
||||
if (lineStart >= lineEnd) {
|
||||
//writeln("lineStart >= lineEnd -- treat as eof");
|
||||
return null; // eof
|
||||
}
|
||||
}
|
||||
// return slice with decoded line
|
||||
return _textBuf[lineStart .. lineEnd];
|
||||
}
|
||||
|
||||
|
||||
// factory for string parser
|
||||
public static LineStream create(string code, string filename = "") {
|
||||
uint len = cast(uint)code.length;
|
||||
ubyte[] data = new ubyte[len + 3];
|
||||
for (uint i = 0; i < len; i++)
|
||||
data[i + 3] = code[i];
|
||||
// BOM for UTF8
|
||||
data[0] = 0xEF;
|
||||
data[1] = 0xBB;
|
||||
data[2] = 0xBF;
|
||||
MemoryStream stream = new MemoryStream(data);
|
||||
return create(stream, filename);
|
||||
}
|
||||
|
||||
// factory
|
||||
public static LineStream create(InputStream stream, string filename) {
|
||||
ubyte[] buf = new ubyte[BYTE_BUFFER_SIZE];
|
||||
buf[0] = buf[1] = buf[2] = buf[3] = 0;
|
||||
if (!stream.isOpen)
|
||||
return null;
|
||||
uint len = cast(uint)stream.read(buf);
|
||||
if (buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF) {
|
||||
return new Utf8LineStream(stream, filename, buf, len);
|
||||
} else if (buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0xFE && buf[3] == 0xFF) {
|
||||
return new Utf32beLineStream(stream, filename, buf, len);
|
||||
} else if (buf[0] == 0xFF && buf[1] == 0xFE && buf[2] == 0x00 && buf[3] == 0x00) {
|
||||
return new Utf32leLineStream(stream, filename, buf, len);
|
||||
} else if (buf[0] == 0xFE && buf[1] == 0xFF) {
|
||||
return new Utf16beLineStream(stream, filename, buf, len);
|
||||
} else if (buf[0] == 0xFF && buf[1] == 0xFE) {
|
||||
return new Utf16leLineStream(stream, filename, buf, len);
|
||||
} else {
|
||||
return new AsciiLineStream(stream, filename, buf, len);
|
||||
}
|
||||
}
|
||||
|
||||
protected bool invalidCharFlag;
|
||||
protected void invalidCharError() {
|
||||
uint pos = _textLen - _textPos + 1;
|
||||
setError(1, "Invalid character in line " ~ to!string(_line) ~ ":" ~ to!string(pos), _line, pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AsciiLineStream : LineStream {
|
||||
this(InputStream stream, string filename, ubyte[] buf, uint len) {
|
||||
super(stream, new SourceFile(filename), EncodingType.ASCII, buf, 0, len);
|
||||
}
|
||||
override uint decodeText() {
|
||||
if (invalidCharFlag) {
|
||||
invalidCharError();
|
||||
return 0;
|
||||
}
|
||||
uint bytesAvailable = readBytes();
|
||||
ubyte * bytes = _buf.ptr + _pos;
|
||||
if (bytesAvailable == 0)
|
||||
return 0; // nothing to decode
|
||||
uint len = bytesAvailable;
|
||||
ubyte* b = bytes;
|
||||
dchar* text = reserveTextBuf(len);
|
||||
uint i = 0;
|
||||
for (; i < len; i++) {
|
||||
ubyte ch = b[i];
|
||||
if (ch & 0x80) {
|
||||
// invalid character
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
text[i] = ch;
|
||||
}
|
||||
consumedBytes(i);
|
||||
appendedText(i);
|
||||
return len;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Utf8LineStream : LineStream {
|
||||
this(InputStream stream, string filename, ubyte[] buf, uint len) {
|
||||
super(stream, new SourceFile(filename), EncodingType.UTF8, buf, 3, len);
|
||||
}
|
||||
override uint decodeText() {
|
||||
if (invalidCharFlag) {
|
||||
invalidCharError();
|
||||
return 0;
|
||||
}
|
||||
uint bytesAvailable = readBytes();
|
||||
ubyte * bytes = _buf.ptr + _pos;
|
||||
if (bytesAvailable == 0)
|
||||
return 0; // nothing to decode
|
||||
uint len = bytesAvailable;
|
||||
uint chars = 0;
|
||||
ubyte* b = bytes;
|
||||
dchar* text = reserveTextBuf(len);
|
||||
uint i = 0;
|
||||
for (; i < len; i++) {
|
||||
uint ch = 0;
|
||||
uint ch0 = b[i];
|
||||
uint bleft = len - i;
|
||||
uint bread = 0;
|
||||
if (!(ch0 & 0x80)) {
|
||||
// 0x00..0x7F single byte
|
||||
ch = ch0;
|
||||
bread = 1;
|
||||
} if ((ch0 & 0xE0) == 0xC0) {
|
||||
// two bytes 110xxxxx 10xxxxxx
|
||||
if (bleft < 2)
|
||||
break;
|
||||
uint ch1 = b[i + 1];
|
||||
if ((ch1 & 0xC0) != 0x80) {
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
ch = ((ch0 & 0x1F) << 6) | ((ch1 & 0x3F));
|
||||
bread = 2;
|
||||
} if ((ch0 & 0xF0) == 0xE0) {
|
||||
// three bytes 1110xxxx 10xxxxxx 10xxxxxx
|
||||
if (bleft < 3)
|
||||
break;
|
||||
uint ch1 = b[i + 1];
|
||||
uint ch2 = b[i + 2];
|
||||
if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80) {
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
ch = ((ch0 & 0x0F) << 12) | ((ch1 & 0x1F) << 6) | ((ch2 & 0x3F));
|
||||
bread = 3;
|
||||
} if ((ch0 & 0xF8) == 0xF0) {
|
||||
// four bytes 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if (bleft < 4)
|
||||
break;
|
||||
uint ch1 = b[i + 1];
|
||||
uint ch2 = b[i + 2];
|
||||
uint ch3 = b[i + 3];
|
||||
if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80) {
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
ch = ((ch0 & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | ((ch3 & 0x3F));
|
||||
bread = 4;
|
||||
} if ((ch0 & 0xFC) == 0xF8) {
|
||||
// five bytes 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if (bleft < 5)
|
||||
break;
|
||||
uint ch1 = b[i + 1];
|
||||
uint ch2 = b[i + 2];
|
||||
uint ch3 = b[i + 3];
|
||||
uint ch4 = b[i + 4];
|
||||
if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80 || (ch4 & 0xC0) != 0x80) {
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
ch = ((ch0 & 0x03) << 24) | ((ch1 & 0x3F) << 18) | ((ch2 & 0x3F) << 12) | ((ch3 & 0x3F) << 6) | ((ch4 & 0x3F));
|
||||
bread = 5;
|
||||
} if ((ch0 & 0xFE) == 0xFC) {
|
||||
// six bytes 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
if (bleft < 6)
|
||||
break;
|
||||
uint ch1 = b[i + 1];
|
||||
uint ch2 = b[i + 2];
|
||||
uint ch3 = b[i + 3];
|
||||
uint ch4 = b[i + 4];
|
||||
uint ch5 = b[i + 5];
|
||||
if ((ch1 & 0xC0) != 0x80 || (ch2 & 0xC0) != 0x80 || (ch3 & 0xC0) != 0x80 || (ch4 & 0xC0) != 0x80 || (ch5 & 0xC0) != 0x80) {
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
ch = ((ch0 & 0x01) << 30) | ((ch1 & 0x3F) << 24) | ((ch2 & 0x3F) << 18) | ((ch3 & 0x3F) << 12) | ((ch4 & 0x3F) << 6) | ((ch5 & 0x3F));
|
||||
bread = 5;
|
||||
}
|
||||
if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
if (ch < 0x10000) {
|
||||
text[chars++] = ch;
|
||||
} else {
|
||||
uint lo = ch & 0x3FF;
|
||||
uint hi = ch >> 10;
|
||||
text[chars++] = (0xd800 | hi);
|
||||
text[chars++] = (0xdc00 | lo);
|
||||
}
|
||||
i += bread - 1;
|
||||
}
|
||||
consumedBytes(i);
|
||||
appendedText(chars);
|
||||
uint bleft = len - i;
|
||||
if (_streamEof && bleft > 0)
|
||||
invalidCharFlag = true; // incomplete character at end of stream
|
||||
return chars;
|
||||
}
|
||||
}
|
||||
|
||||
class Utf16beLineStream : LineStream {
|
||||
this(InputStream stream, string filename, ubyte[] buf, uint len) {
|
||||
super(stream, new SourceFile(filename), EncodingType.UTF16BE, buf, 2, len);
|
||||
}
|
||||
override uint decodeText() {
|
||||
if (invalidCharFlag) {
|
||||
invalidCharError();
|
||||
return 0;
|
||||
}
|
||||
uint bytesAvailable = readBytes();
|
||||
ubyte * bytes = _buf.ptr + _pos;
|
||||
if (bytesAvailable == 0)
|
||||
return 0; // nothing to decode
|
||||
uint len = bytesAvailable;
|
||||
uint chars = 0;
|
||||
ubyte* b = bytes;
|
||||
dchar* text = reserveTextBuf(len / 2 + 1);
|
||||
uint i = 0;
|
||||
for (; i < len - 1; i += 2) {
|
||||
uint ch0 = b[i];
|
||||
uint ch1 = b[i + 1];
|
||||
uint ch = (ch0 << 8) | ch1;
|
||||
// TODO: check special cases
|
||||
text[chars++] = ch;
|
||||
}
|
||||
consumedBytes(i);
|
||||
appendedText(chars);
|
||||
uint bleft = len - i;
|
||||
if (_streamEof && bleft > 0)
|
||||
invalidCharFlag = true; // incomplete character at end of stream
|
||||
return chars;
|
||||
}
|
||||
}
|
||||
|
||||
class Utf16leLineStream : LineStream {
|
||||
this(InputStream stream, string filename, ubyte[] buf, uint len) {
|
||||
super(stream, new SourceFile(filename), EncodingType.UTF16LE, buf, 2, len);
|
||||
}
|
||||
override uint decodeText() {
|
||||
if (invalidCharFlag) {
|
||||
invalidCharError();
|
||||
return 0;
|
||||
}
|
||||
uint bytesAvailable = readBytes();
|
||||
ubyte * bytes = _buf.ptr + _pos;
|
||||
if (bytesAvailable == 0)
|
||||
return 0; // nothing to decode
|
||||
uint len = bytesAvailable;
|
||||
uint chars = 0;
|
||||
ubyte* b = bytes;
|
||||
dchar* text = reserveTextBuf(len / 2 + 1);
|
||||
uint i = 0;
|
||||
for (; i < len - 1; i += 2) {
|
||||
uint ch0 = b[i];
|
||||
uint ch1 = b[i + 1];
|
||||
uint ch = (ch1 << 8) | ch0;
|
||||
// TODO: check special cases
|
||||
text[chars++] = ch;
|
||||
}
|
||||
consumedBytes(i);
|
||||
appendedText(chars);
|
||||
uint bleft = len - i;
|
||||
if (_streamEof && bleft > 0)
|
||||
invalidCharFlag = true; // incomplete character at end of stream
|
||||
return chars;
|
||||
}
|
||||
}
|
||||
|
||||
class Utf32beLineStream : LineStream {
|
||||
this(InputStream stream, string filename, ubyte[] buf, uint len) {
|
||||
super(stream, new SourceFile(filename), EncodingType.UTF32BE, buf, 4, len);
|
||||
}
|
||||
override uint decodeText() {
|
||||
if (invalidCharFlag) {
|
||||
invalidCharError();
|
||||
return 0;
|
||||
}
|
||||
uint bytesAvailable = readBytes();
|
||||
ubyte * bytes = _buf.ptr + _pos;
|
||||
if (bytesAvailable == 0)
|
||||
return 0; // nothing to decode
|
||||
uint len = bytesAvailable;
|
||||
uint chars = 0;
|
||||
ubyte* b = bytes;
|
||||
dchar* text = reserveTextBuf(len / 2 + 1);
|
||||
uint i = 0;
|
||||
for (; i < len - 3; i += 4) {
|
||||
uint ch0 = b[i];
|
||||
uint ch1 = b[i + 1];
|
||||
uint ch2 = b[i + 2];
|
||||
uint ch3 = b[i + 3];
|
||||
uint ch = (ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3;
|
||||
if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
text[chars++] = ch;
|
||||
}
|
||||
consumedBytes(i);
|
||||
appendedText(chars);
|
||||
uint bleft = len - i;
|
||||
if (_streamEof && bleft > 0)
|
||||
invalidCharFlag = true; // incomplete character at end of stream
|
||||
return chars;
|
||||
}
|
||||
}
|
||||
|
||||
class Utf32leLineStream : LineStream {
|
||||
this(InputStream stream, string filename, ubyte[] buf, uint len) {
|
||||
super(stream, new SourceFile(filename), EncodingType.UTF32LE, buf, 4, len);
|
||||
}
|
||||
override uint decodeText() {
|
||||
if (invalidCharFlag) {
|
||||
invalidCharError();
|
||||
return 0;
|
||||
}
|
||||
uint bytesAvailable = readBytes();
|
||||
ubyte * bytes = _buf.ptr + _pos;
|
||||
if (bytesAvailable == 0)
|
||||
return 0; // nothing to decode
|
||||
uint len = bytesAvailable;
|
||||
uint chars = 0;
|
||||
ubyte* b = bytes;
|
||||
dchar* text = reserveTextBuf(len / 2 + 1);
|
||||
uint i = 0;
|
||||
for (; i < len - 3; i += 4) {
|
||||
uint ch3 = b[i];
|
||||
uint ch2 = b[i + 1];
|
||||
uint ch1 = b[i + 2];
|
||||
uint ch0 = b[i + 3];
|
||||
uint ch = (ch0 << 24) | (ch1 << 16) | (ch2 << 8) | ch3;
|
||||
if ((ch >= 0xd800 && ch < 0xe000) || (ch > 0x10FFFF)) {
|
||||
invalidCharFlag = true;
|
||||
break;
|
||||
}
|
||||
text[chars++] = ch;
|
||||
}
|
||||
consumedBytes(i);
|
||||
appendedText(chars);
|
||||
uint bleft = len - i;
|
||||
if (_streamEof && bleft > 0)
|
||||
invalidCharFlag = true; // incomplete character at end of stream
|
||||
return chars;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unittest {
|
||||
static if (false) {
|
||||
import std.stdio;
|
||||
import std.conv;
|
||||
import std.utf;
|
||||
//string fname = "C:\\projects\\d\\ddc\\ddclexer\\src\\ddc\\lexer\\LineStream.d";
|
||||
//string fname = "/home/lve/src/d/ddc/ddclexer/" ~ __FILE__; //"/home/lve/src/d/ddc/ddclexer/src/ddc/lexer/Lexer.d";
|
||||
//string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf8.d";
|
||||
//string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf16be.d";
|
||||
//string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf16le.d";
|
||||
//string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf32be.d";
|
||||
string fname = "/home/lve/src/d/ddc/ddclexer/tests/LineStream_utf32le.d";
|
||||
writeln("opening file");
|
||||
std.stream.File f = new std.stream.File(fname);
|
||||
scope(exit) { f.close(); }
|
||||
try {
|
||||
LineStream lines = LineStream.create(f, fname);
|
||||
for (;;) {
|
||||
dchar[] s = lines.readLine();
|
||||
if (s is null)
|
||||
break;
|
||||
writeln("line " ~ to!string(lines.line()) ~ ":" ~ toUTF8(s));
|
||||
}
|
||||
if (lines.errorCode != 0) {
|
||||
writeln("Error ", lines.errorCode, " ", lines.errorMessage, " -- at line ", lines.errorLine, " position ", lines.errorPos);
|
||||
} else {
|
||||
writeln("EOF reached");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
writeln("Exception " ~ e.toString);
|
||||
}
|
||||
}
|
||||
}
|
||||
// LAST LINE
|
|
@ -58,24 +58,24 @@ class SimpleDSyntaxHighlighter : SyntaxHighlighter {
|
|||
//writeln("EOF token");
|
||||
break;
|
||||
}
|
||||
uint newPos = token.pos;
|
||||
uint newLine = token.line;
|
||||
uint newPos = token.pos - 1;
|
||||
uint newLine = token.line - 1;
|
||||
|
||||
if (category) {
|
||||
// fill with category
|
||||
for (uint i = tokenLine - 1; i <= newLine - 1; i++) {
|
||||
uint start = i > tokenLine - 1 ? 0 : tokenPos;
|
||||
uint end = i < newLine - 1 ? lines[i].length : tokenPos;
|
||||
for (uint j = start; j < end; j++) {
|
||||
assert(i < _props.length);
|
||||
if (j - 1 < _props[i].length)
|
||||
_props[i][j - 1] = category;
|
||||
}
|
||||
//if (category) {
|
||||
// fill with category
|
||||
for (uint i = tokenLine; i <= newLine; i++) {
|
||||
uint start = i > tokenLine ? 0 : tokenPos;
|
||||
uint end = i < newLine ? lines[i].length : tokenPos;
|
||||
for (uint j = start; j < end; j++) {
|
||||
assert(i < _props.length);
|
||||
if (j - 1 < _props[i].length)
|
||||
_props[i][j - 1] = category;
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
TokenType t = token.type;
|
||||
// handle token
|
||||
// handle token - convert to category
|
||||
if (t == TokenType.COMMENT) {
|
||||
category = TokenCategory.Comment;
|
||||
} else if (t == TokenType.KEYWORD) {
|
||||
|
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
|
||||
This module implements object collections.
|
||||
|
||||
Copyright: Vadim Lopatin, 2014
|
||||
License: Boost License 1.0
|
||||
Authors: Vadim Lopatin, coolreader.org@gmail.com
|
||||
*/
|
||||
module collections;
|
||||
|
||||
import std.algorithm;
|
||||
|
||||
/**
|
||||
Array based collection of items.
|
||||
|
||||
Retains item order when during add/remove operations.
|
||||
|
||||
Optionally destroys removed objects when instanciated with ownItems = true.
|
||||
*/
|
||||
struct Collection(T, bool ownItems = false) {
|
||||
private T[] _items;
|
||||
private size_t _len;
|
||||
/// returns true if there are no items in collection
|
||||
@property bool empty() { return _len == 0; }
|
||||
/// returns number of items in collection
|
||||
@property size_t length() { return _len; }
|
||||
/// returns currently allocated capacity (may be more than length)
|
||||
@property size_t size() { return _items.length; }
|
||||
/// change capacity (e.g. to reserve big space to avoid multiple reallocations)
|
||||
@property void size(size_t newSize) {
|
||||
if (_len > newSize)
|
||||
length = newSize; // shrink
|
||||
_items.length = newSize;
|
||||
}
|
||||
/// returns number of items in collection
|
||||
@property void length(size_t newSize) {
|
||||
if (newSize < _len) {
|
||||
// shrink
|
||||
static if (is(T == class) || is(T == struct)) {
|
||||
// clear items
|
||||
for (size_t i = newSize; i < _len; i++) {
|
||||
static if (ownItems)
|
||||
destroy(_items[i]);
|
||||
_items[i] = T.init;
|
||||
}
|
||||
}
|
||||
} else if (newSize > _len) {
|
||||
// expand
|
||||
if (_items.length < newSize)
|
||||
_items.length = newSize;
|
||||
}
|
||||
_len = newSize;
|
||||
}
|
||||
/// access item by index
|
||||
ref T opIndex(size_t index) {
|
||||
assert(index < _len);
|
||||
return _items[index];
|
||||
}
|
||||
/// insert new item in specified position
|
||||
void add(T item, size_t index = size_t.max) {
|
||||
if (index > _len)
|
||||
index = _len;
|
||||
if (_items.length <= _len) {
|
||||
if (_items.length < 4)
|
||||
_items.length = 4;
|
||||
else
|
||||
_items.length = _items.length * 2;
|
||||
}
|
||||
if (index < _len) {
|
||||
for (size_t i = _len; i > index; i--)
|
||||
_items[i] = _items[i - 1];
|
||||
}
|
||||
_items[index] = item;
|
||||
_len++;
|
||||
}
|
||||
/// add all items from other collection
|
||||
void addAll(ref Collection!(T, ownItems) v) {
|
||||
for (int i = 0; i < v.length; i++)
|
||||
add(v[i]);
|
||||
}
|
||||
/// support for appending (~=, +=) and removing by value (-=)
|
||||
ref Collection opOpAssign(string op)(T item) {
|
||||
static if (op.equal("~") || op.equal("+")) {
|
||||
// append item to end of collection
|
||||
add(item);
|
||||
return this;
|
||||
} else if (op.equal("-")) {
|
||||
// remove item from collection, if present
|
||||
removeValue(item);
|
||||
return this;
|
||||
} else {
|
||||
assert(false, "not supported opOpAssign " ~ op);
|
||||
}
|
||||
}
|
||||
/// returns index of first occurence of item, size_t.max if not found
|
||||
size_t indexOf(T item) {
|
||||
for (size_t i = 0; i < _len; i++)
|
||||
if (_items[i] == item)
|
||||
return i;
|
||||
return size_t.max;
|
||||
}
|
||||
/// remove single item, returning removed item
|
||||
T remove(size_t index) {
|
||||
assert(index < _len);
|
||||
T result = _items[index];
|
||||
for (size_t i = index; i + 1 < _len; i++)
|
||||
_items[i] = _items[i + 1];
|
||||
_items[_len] = T.init;
|
||||
_len--;
|
||||
return result;
|
||||
}
|
||||
/// remove single item by value - if present in collection, returning true if item was found and removed
|
||||
bool removeValue(T value) {
|
||||
size_t index = indexOf(value);
|
||||
if (index == size_t.max)
|
||||
return false;
|
||||
T res = remove(index);
|
||||
static if (ownItems)
|
||||
destroy(res);
|
||||
return true;
|
||||
}
|
||||
/// support of foreach with reference
|
||||
int opApply(int delegate(ref T param) op) {
|
||||
int result = 0;
|
||||
for (size_t i = 0; i < _len; i++) {
|
||||
result = op(_items[i]);
|
||||
if (result)
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/// remove all items
|
||||
void clear() {
|
||||
static if (is(T == class) || is(T == struct)) {
|
||||
/// clear references
|
||||
for(size_t i = 0; i < _len; i++) {
|
||||
static if (ownItems)
|
||||
destroy(_items[i]);
|
||||
_items[i] = T.init;
|
||||
}
|
||||
}
|
||||
_len = 0;
|
||||
_items = null;
|
||||
}
|
||||
|
||||
//===================================
|
||||
// stack/queue-like ops
|
||||
|
||||
/// remove first item
|
||||
@property T popFront() {
|
||||
if (empty)
|
||||
return T.init; // no items
|
||||
return remove(0);
|
||||
}
|
||||
|
||||
/// insert item at beginning of collection
|
||||
void pushFront(T item) {
|
||||
add(item, 0);
|
||||
}
|
||||
|
||||
/// remove last item
|
||||
@property T popBack() {
|
||||
if (empty)
|
||||
return T.init; // no items
|
||||
return remove(length - 1);
|
||||
}
|
||||
|
||||
/// insert item at end of collection
|
||||
void pushBack(T item) {
|
||||
add(item);
|
||||
}
|
||||
|
||||
/// peek first item
|
||||
@property T front() {
|
||||
if (empty)
|
||||
return T.init; // no items
|
||||
return _items[0];
|
||||
}
|
||||
|
||||
/// peek last item
|
||||
@property T back() {
|
||||
if (empty)
|
||||
return T.init; // no items
|
||||
return _items[_len - 1];
|
||||
}
|
||||
/// removes all items on destroy
|
||||
~this() {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** object list holder, owning its objects - on destroy of holder, all own objects will be destroyed */
|
||||
struct ObjectList(T) {
|
||||
protected T[] _list;
|
||||
protected int _count;
|
||||
/** returns count of items */
|
||||
@property int count() const { return _count; }
|
||||
/** get item by index */
|
||||
T get(int index) {
|
||||
assert(index >= 0 && index < _count, "child index out of range");
|
||||
return _list[index];
|
||||
}
|
||||
/// get item by index
|
||||
T opIndex(int index) {
|
||||
return get(index);
|
||||
}
|
||||
/** add item to list */
|
||||
T add(T item) {
|
||||
if (_list.length <= _count) // resize
|
||||
_list.length = _list.length < 4 ? 4 : _list.length * 2;
|
||||
_list[_count++] = item;
|
||||
return item;
|
||||
}
|
||||
/** add item to list */
|
||||
T insert(T item, int index = -1) {
|
||||
if (index > _count || index < 0)
|
||||
index = _count;
|
||||
if (_list.length <= _count) // resize
|
||||
_list.length = _list.length < 4 ? 4 : _list.length * 2;
|
||||
for (int i = _count; i > index; i--)
|
||||
_list[i] = _list[i - 1];
|
||||
_list[index] = item;
|
||||
_count++;
|
||||
return item;
|
||||
}
|
||||
/** find child index for item, return -1 if not found */
|
||||
int indexOf(T item) {
|
||||
if (item is null)
|
||||
return -1;
|
||||
for (int i = 0; i < _count; i++)
|
||||
if (_list[i] == item)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
/** find child index for item by id, return -1 if not found */
|
||||
static if (__traits(hasMember, T, "compareId")) {
|
||||
int indexOf(string id) {
|
||||
for (int i = 0; i < _count; i++)
|
||||
if (_list[i].compareId(id))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
/** remove item from list, return removed item */
|
||||
T remove(int index) {
|
||||
assert(index >= 0 && index < _count, "child index out of range");
|
||||
T item = _list[index];
|
||||
for (int i = index; i < _count - 1; i++)
|
||||
_list[i] = _list[i + 1];
|
||||
_count--;
|
||||
return item;
|
||||
}
|
||||
/** Replace item with another value, destroy old value. */
|
||||
void replace(T item, int index) {
|
||||
T old = _list[index];
|
||||
_list[index] = item;
|
||||
destroy(old);
|
||||
}
|
||||
/** Replace item with another value, destroy old value. */
|
||||
void replace(T newItem, T oldItem) {
|
||||
int idx = indexOf(oldItem);
|
||||
if (newItem is null) {
|
||||
if (idx >= 0) {
|
||||
T item = remove(idx);
|
||||
destroy(item);
|
||||
}
|
||||
} else {
|
||||
if (idx >= 0)
|
||||
replace(newItem, idx);
|
||||
else
|
||||
add(newItem);
|
||||
}
|
||||
}
|
||||
/** remove and destroy all items */
|
||||
void clear() {
|
||||
for (int i = 0; i < _count; i++) {
|
||||
destroy(_list[i]);
|
||||
_list[i] = null;
|
||||
}
|
||||
_count = 0;
|
||||
}
|
||||
~this() {
|
||||
clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
This module contains some type definitions.
|
||||
*/
|
||||
module types;
|
||||
|
||||
/** 2D point */
|
||||
struct Point {
|
||||
/// x coordinate
|
||||
int x;
|
||||
/// y coordinate
|
||||
int y;
|
||||
this(int x0, int y0) {
|
||||
x = x0;
|
||||
y = y0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 2D rectangle */
|
||||
struct Rect {
|
||||
/// x coordinate of top left corner
|
||||
int left;
|
||||
/// y coordinate of top left corner
|
||||
int top;
|
||||
/// x coordinate of bottom right corner
|
||||
int right;
|
||||
/// y coordinate of bottom right corner
|
||||
int bottom;
|
||||
/// returns average of left, right
|
||||
@property int middlex() { return (left + right) / 2; }
|
||||
/// returns average of top, bottom
|
||||
@property int middley() { return (top + bottom) / 2; }
|
||||
/// returns middle point
|
||||
@property Point middle() { return Point(middlex, middley); }
|
||||
/// add offset to horizontal and vertical coordinates
|
||||
void offset(int dx, int dy) {
|
||||
left += dx;
|
||||
right += dx;
|
||||
top += dy;
|
||||
bottom += dy;
|
||||
}
|
||||
/// expand rectangle dimensions
|
||||
void expand(int dx, int dy) {
|
||||
left -= dx;
|
||||
right += dx;
|
||||
top -= dy;
|
||||
bottom += dy;
|
||||
}
|
||||
/// shrink rectangle dimensions
|
||||
void shrink(int dx, int dy) {
|
||||
left += dx;
|
||||
right -= dx;
|
||||
top += dy;
|
||||
bottom -= dy;
|
||||
}
|
||||
/// for all fields, sets this.field to rc.field if rc.field > this.field
|
||||
void setMax(Rect rc) {
|
||||
if (left < rc.left)
|
||||
left = rc.left;
|
||||
if (right < rc.right)
|
||||
right = rc.right;
|
||||
if (top < rc.top)
|
||||
top = rc.top;
|
||||
if (bottom < rc.bottom)
|
||||
bottom = rc.bottom;
|
||||
}
|
||||
/// returns width of rectangle (right - left)
|
||||
@property int width() { return right - left; }
|
||||
/// returns height of rectangle (bottom - top)
|
||||
@property int height() { return bottom - top; }
|
||||
/// constructs rectangle using left, top, right, bottom coordinates
|
||||
this(int x0, int y0, int x1, int y1) {
|
||||
left = x0;
|
||||
top = y0;
|
||||
right = x1;
|
||||
bottom = y1;
|
||||
}
|
||||
/// returns true if rectangle is empty (right <= left || bottom <= top)
|
||||
@property bool empty() {
|
||||
return right <= left || bottom <= top;
|
||||
}
|
||||
/// translate rectangle coordinates by (x,y) - add deltax to x coordinates, and deltay to y coordinates
|
||||
void moveBy(int deltax, int deltay) {
|
||||
left += deltax;
|
||||
right += deltax;
|
||||
top += deltay;
|
||||
bottom += deltay;
|
||||
}
|
||||
/// moves this rect to fit rc bounds, retaining the same size
|
||||
void moveToFit(ref Rect rc) {
|
||||
if (right > rc.right)
|
||||
moveBy(rc.right - right, 0);
|
||||
if (bottom > rc.bottom)
|
||||
moveBy(0, rc.bottom - bottom);
|
||||
if (left < rc.left)
|
||||
moveBy(rc.left - left, 0);
|
||||
if (top < rc.top)
|
||||
moveBy(0, rc.top - top);
|
||||
|
||||
}
|
||||
/// updates this rect to intersection with rc, returns true if result is non empty
|
||||
bool intersect(Rect rc) {
|
||||
if (left < rc.left)
|
||||
left = rc.left;
|
||||
if (top < rc.top)
|
||||
top = rc.top;
|
||||
if (right > rc.right)
|
||||
right = rc.right;
|
||||
if (bottom > rc.bottom)
|
||||
bottom = rc.bottom;
|
||||
return right > left && bottom > top;
|
||||
}
|
||||
/// returns true if this rect has nonempty intersection with rc
|
||||
bool intersects(Rect rc) {
|
||||
if (rc.left >= right || rc.top >= bottom || rc.right <= left || rc.bottom <= top)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
/// returns true if point is inside of this rectangle
|
||||
bool isPointInside(Point pt) {
|
||||
return pt.x >= left && pt.x < right && pt.y >= top && pt.y < bottom;
|
||||
}
|
||||
/// returns true if point is inside of this rectangle
|
||||
bool isPointInside(int x, int y) {
|
||||
return x >= left && x < right && y >= top && y < bottom;
|
||||
}
|
||||
/// this rectangle is completely inside rc
|
||||
bool isInsideOf(Rect rc) {
|
||||
return left >= rc.left && right <= rc.right && top >= rc.top && bottom <= rc.bottom;
|
||||
}
|
||||
|
||||
bool opEquals(Rect rc) {
|
||||
return left == rc.left && right == rc.right && top == rc.top && bottom == rc.bottom;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,340 @@
|
|||
// Written in the D programming language.
|
||||
|
||||
/**
|
||||
|
||||
This module contains cross-platform file access utilities
|
||||
|
||||
|
||||
|
||||
Synopsis:
|
||||
|
||||
----
|
||||
import exlib.files;
|
||||
----
|
||||
|
||||
Copyright: Vadim Lopatin, 2014
|
||||
License: Boost License 1.0
|
||||
Authors: Vadim Lopatin, coolreader.org@gmail.com
|
||||
*/
|
||||
module dlangui.core.files;
|
||||
|
||||
import std.algorithm;
|
||||
|
||||
private import dlangui.core.logger;
|
||||
private import std.process;
|
||||
private import std.path;
|
||||
private import std.file;
|
||||
private import std.utf;
|
||||
|
||||
version (Windows) {
|
||||
/// path delimiter (\ for windows, / for others)
|
||||
immutable char PATH_DELIMITER = '\\';
|
||||
} else {
|
||||
/// path delimiter (\ for windows, / for others)
|
||||
immutable char PATH_DELIMITER = '/';
|
||||
}
|
||||
|
||||
/// Filesystem root entry / bookmark types
|
||||
enum RootEntryType : uint {
|
||||
/// filesystem root
|
||||
ROOT,
|
||||
/// current user home
|
||||
HOME,
|
||||
/// removable drive
|
||||
REMOVABLE,
|
||||
/// fixed drive
|
||||
FIXED,
|
||||
/// network
|
||||
NETWORK,
|
||||
/// cd rom
|
||||
CDROM,
|
||||
/// sd card
|
||||
SDCARD,
|
||||
/// custom bookmark
|
||||
BOOKMARK,
|
||||
}
|
||||
|
||||
/// Filesystem root entry item
|
||||
struct RootEntry {
|
||||
private RootEntryType _type;
|
||||
private string _path;
|
||||
private dstring _display;
|
||||
this(RootEntryType type, string path, dstring display = null) {
|
||||
_type = type;
|
||||
_path = path;
|
||||
_display = display;
|
||||
if (display is null) {
|
||||
_display = toUTF32(baseName(path));
|
||||
}
|
||||
}
|
||||
/// Returns type
|
||||
@property RootEntryType type() { return _type; }
|
||||
/// Returns path
|
||||
@property string path() { return _path; }
|
||||
/// Returns display label
|
||||
@property dstring label() { return _display; }
|
||||
/// Returns icon resource id
|
||||
@property string icon() {
|
||||
switch (type) {
|
||||
case RootEntryType.NETWORK:
|
||||
return "folder-network";
|
||||
case RootEntryType.BOOKMARK:
|
||||
return "folder-bookmark";
|
||||
case RootEntryType.CDROM:
|
||||
return "drive-optical";
|
||||
case RootEntryType.FIXED:
|
||||
return "drive-harddisk";
|
||||
case RootEntryType.HOME:
|
||||
return "user-home";
|
||||
case RootEntryType.ROOT:
|
||||
return "computer";
|
||||
case RootEntryType.SDCARD:
|
||||
return "media-flash-sd-mmc";
|
||||
case RootEntryType.REMOVABLE:
|
||||
return "device-removable-media";
|
||||
default:
|
||||
return "folder-blue";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns
|
||||
@property RootEntry homeEntry() {
|
||||
return RootEntry(RootEntryType.HOME, homePath);
|
||||
}
|
||||
|
||||
/// returns array of system root entries
|
||||
@property RootEntry[] getRootPaths() {
|
||||
RootEntry[] res;
|
||||
res ~= RootEntry(RootEntryType.HOME, homePath);
|
||||
version (posix) {
|
||||
res ~= RootEntry(RootEntryType.ROOT, "/", "File System"d);
|
||||
}
|
||||
version (Windows) {
|
||||
import win32.windows;
|
||||
uint mask = GetLogicalDrives();
|
||||
for (int i = 0; i < 26; i++) {
|
||||
if (mask & (1 << i)) {
|
||||
char letter = cast(char)('A' + i);
|
||||
string path = "" ~ letter ~ ":\\";
|
||||
dstring display = ""d ~ letter ~ ":"d;
|
||||
// detect drive type
|
||||
RootEntryType type;
|
||||
uint wtype = GetDriveTypeA(("" ~ path).ptr);
|
||||
//Log.d("Drive ", path, " type ", wtype);
|
||||
switch (wtype) {
|
||||
case DRIVE_REMOVABLE:
|
||||
type = RootEntryType.REMOVABLE;
|
||||
break;
|
||||
case DRIVE_REMOTE:
|
||||
type = RootEntryType.NETWORK;
|
||||
break;
|
||||
case DRIVE_CDROM:
|
||||
type = RootEntryType.CDROM;
|
||||
break;
|
||||
default:
|
||||
type = RootEntryType.FIXED;
|
||||
break;
|
||||
}
|
||||
res ~= RootEntry(type, path, display);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// returns true if directory is root directory (e.g. / or C:\)
|
||||
bool isRoot(string path) {
|
||||
string root = rootName(path);
|
||||
if (path.equal(root))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// returns parent directory for specified path
|
||||
string parentDir(string path) {
|
||||
return buildNormalizedPath(path, "..");
|
||||
}
|
||||
|
||||
/// check filename with pattern (currently only *.ext pattern is supported)
|
||||
bool filterFilename(string filename, string pattern) {
|
||||
if (pattern.equal("*.*"))
|
||||
return true; // matches any
|
||||
if (pattern.length < 3)
|
||||
return false;
|
||||
if (pattern[0] != '*' || pattern[1] != '.')
|
||||
return false;
|
||||
return filename.endsWith(pattern[1..$]);
|
||||
}
|
||||
|
||||
/// Filters file name by pattern list
|
||||
bool filterFilename(string filename, string[] filters) {
|
||||
if (filters.length == 0)
|
||||
return true; // no filters - show all
|
||||
foreach(pattern; filters) {
|
||||
if (filterFilename(filename, pattern))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** List directory content
|
||||
|
||||
Optionally filters file names by filter.
|
||||
|
||||
Result will be placed into entries array.
|
||||
|
||||
Returns true if directory exists and listed successfully, false otherwise.
|
||||
*/
|
||||
bool listDirectory(string dir, bool includeDirs, bool includeFiles, bool showHiddenFiles, string[] filters, ref DirEntry[] entries) {
|
||||
|
||||
entries.length = 0;
|
||||
|
||||
if (!isDir(dir)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isRoot(dir) && includeDirs) {
|
||||
entries ~= DirEntry(appendPath(dir, ".."));
|
||||
}
|
||||
|
||||
try {
|
||||
DirEntry[] dirs;
|
||||
DirEntry[] files;
|
||||
foreach (DirEntry e; dirEntries(dir, SpanMode.shallow)) {
|
||||
string fn = baseName(e.name);
|
||||
if (!showHiddenFiles && fn.startsWith("."))
|
||||
continue;
|
||||
if (e.isDir) {
|
||||
dirs ~= e;
|
||||
} else if (e.isFile) {
|
||||
files ~= e;
|
||||
}
|
||||
}
|
||||
if (includeDirs)
|
||||
foreach(DirEntry e; dirs)
|
||||
entries ~= e;
|
||||
if (includeFiles)
|
||||
foreach(DirEntry e; files)
|
||||
if (filterFilename(e.name, filters))
|
||||
entries ~= e;
|
||||
return true;
|
||||
} catch (FileException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Returns true if char ch is / or \ slash */
|
||||
bool isPathDelimiter(char ch) {
|
||||
return ch == '/' || ch == '\\';
|
||||
}
|
||||
|
||||
/// Returns current directory
|
||||
@property string currentDir() {
|
||||
return getcwd();
|
||||
}
|
||||
|
||||
/** Returns current executable path only, including last path delimiter - removes executable name from result of std.file.thisExePath() */
|
||||
@property string exePath() {
|
||||
string path = thisExePath();
|
||||
int lastSlash = 0;
|
||||
for (int i = 0; i < path.length; i++)
|
||||
if (path[i] == PATH_DELIMITER)
|
||||
lastSlash = i;
|
||||
return path[0 .. lastSlash + 1];
|
||||
}
|
||||
|
||||
/// Returns user's home directory
|
||||
@property string homePath() {
|
||||
string path;
|
||||
version (Windows) {
|
||||
path = environment.get("USERPROFILE");
|
||||
if (path is null)
|
||||
path = environment.get("HOME");
|
||||
} else {
|
||||
path = environment.get("HOME");
|
||||
}
|
||||
if (path is null)
|
||||
path = "."; // fallback to current directory
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Returns application data directory
|
||||
|
||||
On unix, it will return path to subdirectory in home directory - e.g. /home/user/.subdir if ".subdir" is passed as a paramter.
|
||||
|
||||
On windows, it will return path to subdir in APPDATA directory - e.g. C:\Users\User\AppData\Roaming\.subdir.
|
||||
|
||||
*/
|
||||
string appDataPath(string subdir = null) {
|
||||
string path;
|
||||
version (Windows) {
|
||||
path = environment.get("APPDATA");
|
||||
}
|
||||
if (path is null)
|
||||
path = homePath;
|
||||
if (subdir !is null) {
|
||||
path ~= PATH_DELIMITER;
|
||||
path ~= subdir;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/// Converts path delimiters to standard for platform inplace in buffer(e.g. / to \ on windows, \ to / on posix), returns buf
|
||||
char[] convertPathDelimiters(char[] buf) {
|
||||
foreach(ref ch; buf) {
|
||||
version (Windows) {
|
||||
if (ch == '/')
|
||||
ch = '\\';
|
||||
} else {
|
||||
if (ch == '\\')
|
||||
ch = '/';
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
/** Converts path delimiters to standard for platform (e.g. / to \ on windows, \ to / on posix) */
|
||||
string convertPathDelimiters(string src) {
|
||||
char[] buf = src.dup;
|
||||
return cast(string)convertPathDelimiters(buf);
|
||||
}
|
||||
|
||||
/** Appends file path parts with proper delimiters e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
|
||||
string appendPath(string[] pathItems ...) {
|
||||
char[] buf;
|
||||
foreach (s; pathItems) {
|
||||
if (buf.length && !isPathDelimiter(buf[$-1]))
|
||||
buf ~= PATH_DELIMITER;
|
||||
buf ~= s;
|
||||
}
|
||||
return convertPathDelimiters(buf).dup;
|
||||
}
|
||||
|
||||
/** Appends file path parts with proper delimiters (as well converts delimiters inside path to system) to buffer e.g. appendPath("/home/user", ".myapp", "config") => "/home/user/.myapp/config" */
|
||||
char[] appendPath(char[] buf, string[] pathItems ...) {
|
||||
foreach (s; pathItems) {
|
||||
if (buf.length && !isPathDelimiter(buf[$-1]))
|
||||
buf ~= PATH_DELIMITER;
|
||||
buf ~= s;
|
||||
}
|
||||
return convertPathDelimiters(buf);
|
||||
}
|
||||
|
||||
/** Split path into elements, e.g. /home/user/dir1 -> ["home", "user", "dir1"], "c:\dir1\dir2" -> ["c:", "dir1", "dir2"] */
|
||||
string[] splitPath(string path) {
|
||||
string[] res;
|
||||
int start = 0;
|
||||
for (int i = 0; i <= path.length; i++) {
|
||||
char ch = i < path.length ? path[i] : 0;
|
||||
if (ch == '\\' || ch == '/' || ch == 0) {
|
||||
if (start < i)
|
||||
res ~= path[start .. i].dup;
|
||||
start = i + 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
|
@ -0,0 +1,408 @@
|
|||
/**
|
||||
This module contains UI internationalization support implementation.
|
||||
|
||||
UIString struct provides string container which can be either plain unicode string or id of string resource.
|
||||
|
||||
Translation strings are being stored in translation files, consisting of simple key=value pair lines:
|
||||
---
|
||||
STRING_RESOURCE_ID=Translation text 1
|
||||
ANOTHER_STRING_RESOURCE_ID=Translation text 2
|
||||
---
|
||||
|
||||
Supports fallback to another translation file (e.g. default language).
|
||||
|
||||
If string resource is not found neither in main nor fallback translation files, UNTRANSLATED: RESOURCE_ID will be returned.
|
||||
|
||||
String resources must be placed in i18n subdirectory inside one or more resource directories (set using Platform.instance.resourceDirs
|
||||
property on application initialization).
|
||||
|
||||
File names must be language code with extension .ini (e.g. en.ini, fr.ini, es.ini)
|
||||
|
||||
If several files for the same language are found in (different directories) their content will be merged. It's useful to merge string resources
|
||||
from DLangUI framework with resources of application.
|
||||
|
||||
Set interface language using Platform.instance.uiLanguage in UIAppMain during initialization of application settings:
|
||||
---
|
||||
Platform.instance.uiLanguage = "en";
|
||||
---
|
||||
|
||||
|
||||
Synopsis:
|
||||
|
||||
----
|
||||
import exlib.i18n;
|
||||
|
||||
// use global i18n object to get translation for string ID
|
||||
dstring translated = i18n.get("STR_FILE_OPEN");
|
||||
// as well, you can specify fallback value - to return if translation is not found
|
||||
dstring translated = i18n.get("STR_FILE_OPEN", "Open..."d);
|
||||
|
||||
// UIString type can hold either string resource id or dstring raw value.
|
||||
UIString text;
|
||||
|
||||
// assign resource id as string (will remove dstring value if it was here)
|
||||
text = "ID_FILE_EXIT";
|
||||
// or assign raw value as dstring (will remove id if it was here)
|
||||
text = "some text"d;
|
||||
// assign both resource id and fallback value - to use if string resource is not found
|
||||
text = UIString("ID_FILE_EXIT", "Exit"d);
|
||||
|
||||
// i18n.get() will automatically be invoked when getting UIString value (e.g. using alias this).
|
||||
dstring translated = text;
|
||||
|
||||
----
|
||||
|
||||
Copyright: Vadim Lopatin, 2014
|
||||
License: Boost License 1.0
|
||||
Authors: Vadim Lopatin, coolreader.org@gmail.com
|
||||
*/
|
||||
module exlib.i18n;
|
||||
|
||||
import exlib.logger;
|
||||
import exlib.files;
|
||||
|
||||
private import exlib.linestream;
|
||||
private import std.utf;
|
||||
private import std.algorithm;
|
||||
|
||||
/**
|
||||
Container for UI string - either raw value or string resource ID
|
||||
|
||||
Set resource id (string) or plain unicode text (dstring) to it, and get dstring.
|
||||
|
||||
*/
|
||||
struct UIString {
|
||||
/** if not null, use it, otherwise lookup by id */
|
||||
private dstring _value;
|
||||
/** id to find value in translator */
|
||||
private string _id;
|
||||
|
||||
/** create string with i18n resource id */
|
||||
this(string id) {
|
||||
_id = id;
|
||||
}
|
||||
/** create string with raw value */
|
||||
this(dstring value) {
|
||||
_value = value;
|
||||
}
|
||||
/** create string with resource id and raw value as fallback for missing translations */
|
||||
this(string id, dstring fallbackValue) {
|
||||
_id = id;
|
||||
_value = fallbackValue;
|
||||
}
|
||||
|
||||
|
||||
/// Returns string resource id
|
||||
@property string id() const { return _id; }
|
||||
/// Sets string resource id
|
||||
@property void id(string ID) {
|
||||
_id = ID;
|
||||
_value = null;
|
||||
}
|
||||
/** Get value (either raw or translated by id) */
|
||||
@property dstring value() const {
|
||||
if (_id !is null) // translate ID to dstring
|
||||
return i18n.get(_id, _value); // get from resource, use _value as fallback
|
||||
return _value;
|
||||
}
|
||||
/** Set raw value using property */
|
||||
@property void value(dstring newValue) {
|
||||
_value = newValue;
|
||||
}
|
||||
/** Assign raw value */
|
||||
ref UIString opAssign(dstring rawValue) {
|
||||
_value = rawValue;
|
||||
_id = null;
|
||||
return this;
|
||||
}
|
||||
/** Assign string resource id */
|
||||
ref UIString opAssign(string ID) {
|
||||
_id = ID;
|
||||
_value = null;
|
||||
return this;
|
||||
}
|
||||
/** Default conversion to dstring */
|
||||
alias value this;
|
||||
}
|
||||
|
||||
/**
|
||||
UIString item collection
|
||||
|
||||
Based on array.
|
||||
*/
|
||||
struct UIStringCollection {
|
||||
private UIString[] _items;
|
||||
private int _length;
|
||||
|
||||
/** Returns number of items */
|
||||
@property int length() { return _length; }
|
||||
/** Slice */
|
||||
UIString[] opIndex() {
|
||||
return _items[0 .. _length];
|
||||
}
|
||||
/** Slice */
|
||||
UIString[] opSlice() {
|
||||
return _items[0 .. _length];
|
||||
}
|
||||
/** Slice */
|
||||
UIString[] opSlice(size_t start, size_t end) {
|
||||
return _items[start .. end];
|
||||
}
|
||||
/** Read item by index */
|
||||
UIString opIndex(size_t index) {
|
||||
return _items[index];
|
||||
}
|
||||
/** Modify item by index */
|
||||
UIString opIndexAssign(UIString value, size_t index) {
|
||||
_items[index] = value;
|
||||
return _items[index];
|
||||
}
|
||||
/** Return unicode string for item by index */
|
||||
dstring get(size_t index) {
|
||||
return _items[index].value;
|
||||
}
|
||||
/** Assign UIStringCollection */
|
||||
void opAssign(ref UIStringCollection items) {
|
||||
clear();
|
||||
addAll(items);
|
||||
}
|
||||
/** Append UIStringCollection */
|
||||
void addAll(ref UIStringCollection items) {
|
||||
foreach (UIString item; items) {
|
||||
add(item);
|
||||
}
|
||||
}
|
||||
/** Assign array of string resource IDs */
|
||||
void opAssign(string[] items) {
|
||||
clear();
|
||||
addAll(items);
|
||||
}
|
||||
/** Append array of string resource IDs */
|
||||
void addAll(string[] items) {
|
||||
foreach (string item; items) {
|
||||
add(item);
|
||||
}
|
||||
}
|
||||
/** Assign array of unicode strings */
|
||||
void opAssign(dstring[] items) {
|
||||
clear();
|
||||
addAll(items);
|
||||
}
|
||||
/** Append array of unicode strings */
|
||||
void addAll(dstring[] items) {
|
||||
foreach (dstring item; items) {
|
||||
add(item);
|
||||
}
|
||||
}
|
||||
/** Remove all items */
|
||||
void clear() {
|
||||
_items.length = 0;
|
||||
_length = 0;
|
||||
}
|
||||
/** Insert resource id item into specified position */
|
||||
void add(string item, int index = -1) {
|
||||
UIString s;
|
||||
s = item;
|
||||
add(s, index);
|
||||
}
|
||||
/** Insert unicode string item into specified position */
|
||||
void add(dstring item, int index = -1) {
|
||||
UIString s;
|
||||
s = item;
|
||||
add(s, index);
|
||||
}
|
||||
/** Insert UIString item into specified position */
|
||||
void add(UIString item, int index = -1) {
|
||||
if (index < 0 || index > _length)
|
||||
index = _length;
|
||||
if (_items.length < _length + 1) {
|
||||
if (_items.length < 8)
|
||||
_items.length = 8;
|
||||
else
|
||||
_items.length = _items.length * 2;
|
||||
}
|
||||
for (size_t i = _length; i > index; i--) {
|
||||
_items[i] = _items[i + 1];
|
||||
}
|
||||
_items[index] = item;
|
||||
_length++;
|
||||
}
|
||||
/** Remove item with specified index */
|
||||
void remove(int index) {
|
||||
if (index < 0 || index >= _length)
|
||||
return;
|
||||
for (size_t i = index; i < _length - 1; i++)
|
||||
_items[i] = _items[i + 1];
|
||||
_length--;
|
||||
}
|
||||
/** Return index of first item with specified text or -1 if not found. */
|
||||
int indexOf(dstring str) {
|
||||
for (int i = 0; i < _length; i++) {
|
||||
if (_items[i].value.equal(str))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/** Return index of first item with specified string resource id or -1 if not found. */
|
||||
int indexOf(string strId) {
|
||||
for (int i = 0; i < _length; i++) {
|
||||
if (_items[i].id.equal(strId))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
/** Return index of first item with specified string or -1 if not found. */
|
||||
int indexOf(UIString str) {
|
||||
if (str.id !is null)
|
||||
return indexOf(str.id);
|
||||
return indexOf(str.value);
|
||||
}
|
||||
}
|
||||
|
||||
/** UI Strings internationalization translator */
|
||||
synchronized class UIStringTranslator {
|
||||
|
||||
private UIStringList _main;
|
||||
private UIStringList _fallback;
|
||||
private string[] _resourceDirs;
|
||||
|
||||
/** Looks for i18n directory inside one of passed dirs, and uses first found as directory to read i18n files from */
|
||||
void findTranslationsDir(string[] dirs ...) {
|
||||
_resourceDirs.length = 0;
|
||||
import std.file;
|
||||
foreach(dir; dirs) {
|
||||
string path = appendPath(dir, "i18n/");
|
||||
if (exists(path) && isDir(path)) {
|
||||
Log.i("Adding i18n dir ", path);
|
||||
_resourceDirs ~= path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Convert resource path - append resource dir if necessary */
|
||||
string[] convertResourcePaths(string filename) {
|
||||
if (filename is null)
|
||||
return null;
|
||||
bool hasPathDelimiters = false;
|
||||
foreach(char ch; filename)
|
||||
if (ch == '/' || ch == '\\')
|
||||
hasPathDelimiters = true;
|
||||
string[] res;
|
||||
if (!hasPathDelimiters && _resourceDirs.length) {
|
||||
foreach (dir; _resourceDirs)
|
||||
res ~= dir ~ filename;
|
||||
} else {
|
||||
res ~= filename;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// create empty translator
|
||||
this() {
|
||||
_main = new shared UIStringList();
|
||||
_fallback = new shared UIStringList();
|
||||
}
|
||||
|
||||
/** Load translation file(s) */
|
||||
bool load(string mainFilename, string fallbackFilename = null) {
|
||||
_main.clear();
|
||||
_fallback.clear();
|
||||
bool res = _main.load(convertResourcePaths(mainFilename));
|
||||
if (fallbackFilename !is null) {
|
||||
res = _fallback.load(convertResourcePaths(fallbackFilename)) || res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** Translate string ID to string (returns "UNTRANSLATED: id" for missing values) */
|
||||
dstring get(string id, dstring fallbackValue = null) {
|
||||
if (id is null)
|
||||
return null;
|
||||
dstring s = _main.get(id);
|
||||
if (s !is null)
|
||||
return s;
|
||||
s = _fallback.get(id);
|
||||
if (s !is null)
|
||||
return s;
|
||||
if (fallbackValue.length > 0)
|
||||
return fallbackValue;
|
||||
return "UNTRANSLATED: "d ~ toUTF32(id);
|
||||
}
|
||||
}
|
||||
|
||||
/** UI string translator */
|
||||
private shared class UIStringList {
|
||||
private dstring[string] _map;
|
||||
/// remove all items
|
||||
void clear() {
|
||||
_map.destroy();
|
||||
}
|
||||
/// set item value
|
||||
void set(string id, dstring value) {
|
||||
_map[id] = value;
|
||||
}
|
||||
/// get item value, null if translation is not found for id
|
||||
dstring get(string id) const {
|
||||
if (id in _map)
|
||||
return _map[id];
|
||||
return null;
|
||||
}
|
||||
/// load strings from stream
|
||||
bool load(std.stream.InputStream stream) {
|
||||
dlangui.core.linestream.LineStream lines = dlangui.core.linestream.LineStream.create(stream, "");
|
||||
int count = 0;
|
||||
for (;;) {
|
||||
dchar[] s = lines.readLine();
|
||||
if (s is null)
|
||||
break;
|
||||
int eqpos = -1;
|
||||
int firstNonspace = -1;
|
||||
int lastNonspace = -1;
|
||||
for (int i = 0; i < s.length; i++)
|
||||
if (s[i] == '=') {
|
||||
eqpos = i;
|
||||
break;
|
||||
} else if (s[i] != ' ' && s[i] != '\t') {
|
||||
if (firstNonspace == -1)
|
||||
firstNonspace = i;
|
||||
lastNonspace = i;
|
||||
}
|
||||
if (eqpos > 0 && firstNonspace != -1) {
|
||||
string id = toUTF8(s[firstNonspace .. lastNonspace + 1]);
|
||||
dstring value = s[eqpos + 1 .. $].dup;
|
||||
set(id, value);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
/// load strings from file (utf8, id=value lines)
|
||||
bool load(string[] filenames) {
|
||||
clear();
|
||||
bool res = false;
|
||||
foreach(filename; filenames) {
|
||||
import std.stream;
|
||||
import std.file;
|
||||
try {
|
||||
debug Log.d("Loading string resources from file ", filename);
|
||||
if (!exists(filename) || !isFile(filename)) {
|
||||
Log.e("File does not exist: ", filename);
|
||||
continue;
|
||||
}
|
||||
std.stream.File f = new std.stream.File(filename);
|
||||
scope(exit) { f.close(); }
|
||||
res = load(f) || res;
|
||||
} catch (StreamFileException e) {
|
||||
Log.e("Cannot read string resources from file ", filename);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
/** Global UI translator object */
|
||||
shared UIStringTranslator i18n;
|
||||
shared static this() {
|
||||
i18n = new shared UIStringTranslator();
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
// Written in the D programming language.
|
||||
|
||||
/**
|
||||
This module provides logging utilities.
|
||||
|
||||
Use Log class static methods.
|
||||
|
||||
Synopsis:
|
||||
|
||||
----
|
||||
import exlib.logger;
|
||||
|
||||
// setup:
|
||||
|
||||
// use stderror for logging
|
||||
setStderrLogger();
|
||||
// set log level
|
||||
setLogLevel(LogLeve.Debug);
|
||||
|
||||
// usage:
|
||||
|
||||
// log debug message
|
||||
Log.d("mouse clicked at ", x, ",", y);
|
||||
// log error message
|
||||
Log.e("exception while reading file", e);
|
||||
----
|
||||
|
||||
Copyright: Vadim Lopatin, 2014
|
||||
License: Boost License 1.0
|
||||
Authors: Vadim Lopatin, coolreader.org@gmail.com
|
||||
*/
|
||||
module exlib.logger;
|
||||
|
||||
import std.stdio;
|
||||
import std.datetime;
|
||||
|
||||
/// Log levels
|
||||
enum LogLevel : int {
|
||||
/// Fatal error, cannot resume
|
||||
Fatal,
|
||||
/// Error
|
||||
Error,
|
||||
/// Warning
|
||||
Warn,
|
||||
/// Informational message
|
||||
Info,
|
||||
/// Debug message
|
||||
Debug,
|
||||
/// Tracing message
|
||||
Trace
|
||||
}
|
||||
|
||||
/// Returns timestamp in milliseconds since 1970 UTC similar to Java System.currentTimeMillis()
|
||||
long currentTimeMillis() {
|
||||
return std.datetime.Clock.currStdTime / 10000;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Logging utilities
|
||||
|
||||
Setup example:
|
||||
----
|
||||
// use stderror for logging
|
||||
setStderrLogger();
|
||||
// set log level
|
||||
setLogLevel(LogLeve.Debug);
|
||||
----
|
||||
|
||||
Logging example:
|
||||
----
|
||||
// log debug message
|
||||
Log.d("mouse clicked at ", x, ",", y);
|
||||
// log error message
|
||||
Log.e("exception while reading file", e);
|
||||
----
|
||||
|
||||
*/
|
||||
synchronized class Log {
|
||||
static:
|
||||
private LogLevel logLevel = LogLevel.Info;
|
||||
private std.stdio.File logFile;
|
||||
|
||||
/// Redirects output to stdout
|
||||
void setStdoutLogger() {
|
||||
logFile = stdout;
|
||||
}
|
||||
|
||||
/// Redirects output to stderr
|
||||
void setStderrLogger() {
|
||||
logFile = stderr;
|
||||
}
|
||||
|
||||
/// Redirects output to file
|
||||
void setFileLogger(File file) {
|
||||
logFile = file;
|
||||
}
|
||||
|
||||
/// Sets log level (one of LogLevel)
|
||||
void setLogLevel(LogLevel level) {
|
||||
logLevel = level;
|
||||
}
|
||||
|
||||
/// Log level to name helper function
|
||||
string logLevelName(LogLevel level) {
|
||||
switch (level) {
|
||||
case LogLevel.Fatal: return "F";
|
||||
case LogLevel.Error: return "E";
|
||||
case LogLevel.Warn: return "W";
|
||||
case LogLevel.Info: return "I";
|
||||
case LogLevel.Debug: return "D";
|
||||
case LogLevel.Trace: return "V";
|
||||
default: return "?";
|
||||
}
|
||||
}
|
||||
/// Log message with arbitrary log level
|
||||
void log(S...)(LogLevel level, S args) {
|
||||
if (logLevel >= level && logFile.isOpen) {
|
||||
SysTime ts = Clock.currTime();
|
||||
logFile.writef("%04d-%02d-%02d %02d:%02d:%02d.%03d %s ", ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, ts.fracSec.msecs, logLevelName(level));
|
||||
logFile.writeln(args);
|
||||
logFile.flush();
|
||||
}
|
||||
}
|
||||
/// Log verbose / trace message
|
||||
void v(S...)(S args) {
|
||||
if (logLevel >= LogLevel.Trace && logFile.isOpen)
|
||||
log(LogLevel.Trace, args);
|
||||
}
|
||||
/// Log debug message
|
||||
void d(S...)(S args) {
|
||||
if (logLevel >= LogLevel.Debug && logFile.isOpen)
|
||||
log(LogLevel.Debug, args);
|
||||
}
|
||||
/// Log info message
|
||||
void i(S...)(S args) {
|
||||
if (logLevel >= LogLevel.Info && logFile.isOpen)
|
||||
log(LogLevel.Info, args);
|
||||
}
|
||||
/// Log warn message
|
||||
void w(S...)(S args) {
|
||||
if (logLevel >= LogLevel.Warn && logFile.isOpen)
|
||||
log(LogLevel.Warn, args);
|
||||
}
|
||||
/// Log error message
|
||||
void e(S...)(S args) {
|
||||
if (logLevel >= LogLevel.Error && logFile.isOpen)
|
||||
log(LogLevel.Error, args);
|
||||
}
|
||||
/// Log fatal error message
|
||||
void f(S...)(S args) {
|
||||
if (logLevel >= LogLevel.Fatal && logFile.isOpen)
|
||||
log(LogLevel.Fatal, args);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
#!/usr/bin/env rdmd
|
||||
// Computes average line length for standard input.
|
||||
import std.stdio;
|
||||
import exlib.logger;
|
||||
import exlib.files;
|
||||
import exlib.i18n;
|
||||
|
||||
void main()
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue