mirror of
https://github.com/dlang/phobos.git
synced 2025-04-28 14:10:30 +03:00
2158 lines
60 KiB
D
2158 lines
60 KiB
D
// Written in the D programming language.
|
|
|
|
/* Written by Walter Bright and Andrei Alexandrescu
|
|
* http://www.digitalmars.com/d
|
|
* Placed in the Public Domain.
|
|
*/
|
|
|
|
/********************************
|
|
Standard I/O functions that extend $(B std.c.stdio). $(B std.c.stdio)
|
|
is $(D_PARAM public)ally imported when importing $(B std.stdio).
|
|
|
|
Authors: $(WEB digitalmars.com, Walter Bright), $(WEB erdani.org,
|
|
Andrei Alexandrescu)
|
|
|
|
Macros:
|
|
WIKI=Phobos/StdStdio
|
|
*/
|
|
|
|
module std.stdio;
|
|
|
|
public import core.stdc.stdio;
|
|
import core.memory, core.stdc.errno, core.stdc.stddef,
|
|
core.stdc.stdlib, core.stdc.string, core.stdc.wchar_;
|
|
import std.stdiobase;
|
|
import std.algorithm, std.array, std.contracts, std.conv, std.file, std.format,
|
|
std.metastrings, std.range, std.string, std.traits, std.typecons,
|
|
std.typetuple, std.utf;
|
|
|
|
version (DigitalMars) version (Windows)
|
|
{
|
|
// Specific to the way Digital Mars C does stdio
|
|
version = DIGITAL_MARS_STDIO;
|
|
import std.c.stdio : __fhnd_info, FHND_WCHAR;
|
|
}
|
|
|
|
version (linux)
|
|
{
|
|
// Specific to the way Gnu C does stdio
|
|
version = GCC_IO;
|
|
extern(C) FILE* fopen64(const char*, const char*);
|
|
}
|
|
|
|
version (OSX)
|
|
{
|
|
version = GENERIC_IO;
|
|
alias core.stdc.stdio.fopen fopen64;
|
|
}
|
|
|
|
version(Windows)
|
|
{
|
|
alias core.stdc.stdio.fopen fopen64;
|
|
}
|
|
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
extern (C)
|
|
{
|
|
/* **
|
|
* Digital Mars under-the-hood C I/O functions.
|
|
* Use _iobuf* for the unshared version of FILE*,
|
|
* usable when the FILE is locked.
|
|
*/
|
|
int _fputc_nlock(int, _iobuf*);
|
|
int _fputwc_nlock(int, _iobuf*);
|
|
int _fgetc_nlock(_iobuf*);
|
|
int _fgetwc_nlock(_iobuf*);
|
|
int __fp_lock(FILE*);
|
|
void __fp_unlock(FILE*);
|
|
}
|
|
alias _fputc_nlock FPUTC;
|
|
alias _fputwc_nlock FPUTWC;
|
|
alias _fgetc_nlock FGETC;
|
|
alias _fgetwc_nlock FGETWC;
|
|
|
|
alias __fp_lock FLOCK;
|
|
alias __fp_unlock FUNLOCK;
|
|
}
|
|
else version (GCC_IO)
|
|
{
|
|
/* **
|
|
* Gnu under-the-hood C I/O functions; see
|
|
* http://www.gnu.org/software/libc/manual/html_node/I_002fO-on-Streams.html#I_002fO-on-Streams
|
|
*/
|
|
//private import std.c.linux.linux;
|
|
private import core.sys.posix.stdio;
|
|
extern (C)
|
|
{
|
|
int fputc_unlocked(int, _iobuf*);
|
|
int fputwc_unlocked(wchar_t, _iobuf*);
|
|
int fgetc_unlocked(_iobuf*);
|
|
int fgetwc_unlocked(_iobuf*);
|
|
void flockfile(FILE*);
|
|
void funlockfile(FILE*);
|
|
ssize_t getline(char**, size_t*, FILE*);
|
|
ssize_t getdelim (char**, size_t*, int, FILE*);
|
|
|
|
private size_t fwrite_unlocked(const(void)* ptr,
|
|
size_t size, size_t n, _iobuf *stream);
|
|
}
|
|
|
|
version (linux)
|
|
{
|
|
// declare fopen64 if not already
|
|
static if (!is(typeof(fopen64)))
|
|
extern (C) FILE* fopen64(in char*, in char*);
|
|
}
|
|
|
|
alias fputc_unlocked FPUTC;
|
|
alias fputwc_unlocked FPUTWC;
|
|
alias fgetc_unlocked FGETC;
|
|
alias fgetwc_unlocked FGETWC;
|
|
|
|
alias flockfile FLOCK;
|
|
alias funlockfile FUNLOCK;
|
|
}
|
|
else version (GENERIC_IO)
|
|
{
|
|
extern (C)
|
|
{
|
|
void flockfile(FILE*);
|
|
void funlockfile(FILE*);
|
|
}
|
|
|
|
int fputc_unlocked(int c, _iobuf* fp) { return fputc(c, cast(shared) fp); }
|
|
int fputwc_unlocked(wchar_t c, _iobuf* fp) { return fputwc(c, cast(shared) fp); }
|
|
int fgetc_unlocked(_iobuf* fp) { return fgetc(cast(shared) fp); }
|
|
int fgetwc_unlocked(_iobuf* fp) { return fgetwc(cast(shared) fp); }
|
|
|
|
alias fputc_unlocked FPUTC;
|
|
alias fputwc_unlocked FPUTWC;
|
|
alias fgetc_unlocked FGETC;
|
|
alias fgetwc_unlocked FGETWC;
|
|
|
|
alias flockfile FLOCK;
|
|
alias funlockfile FUNLOCK;
|
|
}
|
|
else
|
|
{
|
|
static assert(0, "unsupported C I/O system");
|
|
}
|
|
/**
|
|
Encapsulates a $(D FILE*). Generally D does not attempt to provide
|
|
thin wrappers over equivalent functions in the C standard library, but
|
|
manipulating $(D FILE*) values directly is unsafe and error-prone in
|
|
many ways. The $(D File) type ensures safe manipulation, automatic
|
|
file closing, and a lot of convenience.
|
|
|
|
The underlying $(D FILE*) handle is maintained in a reference-counted
|
|
manner, such that as soon as the last $(D File) variable bound to a
|
|
given $(D FILE*) goes out of scope, the underlying $(D FILE*) is
|
|
automatically closed.
|
|
|
|
Example:
|
|
----
|
|
// test.d
|
|
void main(string args[])
|
|
{
|
|
auto f = File("test.txt", "w"); // open for writing
|
|
f.write("Hello");
|
|
if (args.length > 1)
|
|
{
|
|
auto g = f; // now g and f write to the same file
|
|
// internal reference count is 2
|
|
g.write(", ", args[1]);
|
|
// g exits scope, reference count decreases to 1
|
|
}
|
|
f.writeln("!");
|
|
// f exits scope, reference count falls to zero,
|
|
// underlying $(D FILE*) is closed.
|
|
}
|
|
----
|
|
<pre class=console>
|
|
% rdmd test.d Jimmy
|
|
% cat test.txt
|
|
Hello, Jimmy!
|
|
% __
|
|
</pre>
|
|
*/
|
|
struct File
|
|
{
|
|
/*private*/ struct Impl
|
|
{
|
|
FILE * handle;
|
|
uint refs;
|
|
string name;
|
|
this(FILE* h, uint r, string n)
|
|
{
|
|
handle = h;
|
|
refs = r;
|
|
name = n;
|
|
}
|
|
}
|
|
/*private*/ Impl * p;
|
|
|
|
/**
|
|
Constructor taking the name of the file to open and the open mode
|
|
(with the same semantics as in the C standard library $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen)
|
|
function). Throws an exception if the file could not be opened.
|
|
|
|
Copying one $(D File) object to another results in the two $(D File)
|
|
objects referring to the same underlying file.
|
|
|
|
The destructor automatically closes the file as soon as no $(D File)
|
|
object refers to it anymore.
|
|
*/
|
|
|
|
this(string name, in char[] stdioOpenmode = "rb")
|
|
{
|
|
p = new Impl(errnoEnforce(.fopen(name, stdioOpenmode),
|
|
"Cannot open file `"~name
|
|
~"' in mode `"~stdioOpenmode.idup~"'"),
|
|
1, name);
|
|
}
|
|
|
|
// ~this()
|
|
// {
|
|
// if (!p) return;
|
|
// // @@@BUG@@@ These lines prematurely close the file
|
|
// if (p.refs == 1) close;
|
|
// else --p.refs;
|
|
// }
|
|
|
|
// this(this)
|
|
// {
|
|
// //printf("Copying file with %d refs\n", _refs ? *_refs : 9999);
|
|
// if (!p) return;
|
|
// enforce(p.refs);
|
|
// ++p.refs;
|
|
// }
|
|
|
|
/**
|
|
Assigns a file to another. The target of the assignment gets detached
|
|
from whatever file it was attached to, and attaches itself to the new
|
|
file.
|
|
*/
|
|
void opAssign(File rhs)
|
|
{
|
|
// @@@BUG@@@
|
|
//swap(p, rhs.p);
|
|
p = rhs.p;
|
|
}
|
|
|
|
/**
|
|
First calls $(D detach) (throwing on failure), and then attempts to
|
|
_open file $(D name) with mode $(D stdioOpenmode). The mode has the
|
|
same semantics as in the C standard library $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/fopen.html, fopen) function.
|
|
Throws exception in case of error.
|
|
*/
|
|
void open(string name, string stdioOpenmode = "rb")
|
|
{
|
|
detach;
|
|
auto another = File(name, stdioOpenmode);
|
|
swap(this, another);
|
|
}
|
|
|
|
/**
|
|
First calls $(D detach) (throwing on failure), and then runs a command
|
|
by calling the C standard library function $(WEB
|
|
opengroup.org/onlinepubs/007908799/xsh/_popen.html, _popen).
|
|
*/
|
|
version(linux) void popen(string command, string stdioOpenmode = "r")
|
|
{
|
|
detach;
|
|
p = new Impl(errnoEnforce(.popen(command, stdioOpenmode),
|
|
"Cannot run command `"~command~"'"),
|
|
1, command);
|
|
}
|
|
|
|
/** Returns $(D true) if the file is opened. */
|
|
bool isOpen() const
|
|
{
|
|
return p !is null && p.handle;
|
|
}
|
|
|
|
/**
|
|
Returns $(D true) if the file is at end (see $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/feof.html, feof)). The file
|
|
must be opened, otherwise an exception is thrown.
|
|
*/
|
|
bool eof() const
|
|
{
|
|
enforce(p && p.handle, "Calling eof() against an unopened file.");
|
|
return .feof(cast(FILE*) p.handle) != 0;
|
|
}
|
|
|
|
/** Returns the name of the file, if any. */
|
|
string name() const
|
|
{
|
|
return p.name;
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, returns $(D false). Otherwise, returns
|
|
$(WEB cplusplus.com/reference/clibrary/cstdio/ferror.html, ferror) for
|
|
the file handle.
|
|
*/
|
|
bool error() const
|
|
{
|
|
return !p.handle || .ferror(cast(FILE*) p.handle);
|
|
}
|
|
|
|
/**
|
|
Detaches from the underlying file. If the sole owner, calls $(D close)
|
|
and throws if that fails.
|
|
*/
|
|
void detach()
|
|
{
|
|
if (!p) return;
|
|
// @@@BUG
|
|
//if (p.refs == 1) close;
|
|
p = null;
|
|
}
|
|
|
|
/**
|
|
If the file was unopened, succeeds vacuously. Otherwise closes the
|
|
file (by calling $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/fclose.html, fclose)),
|
|
throwing on error. Even if an exception is thrown, afterwards the $(D
|
|
File) object is empty. This is different from $(D detach) in that it
|
|
always closes the file; consequently, all other $(D File) objects
|
|
referring to the same handle will see a closed file henceforth.
|
|
*/
|
|
void close()
|
|
{
|
|
if (!p) return; // succeed vacuously
|
|
if (!p.handle)
|
|
{
|
|
p = null; // start a new life
|
|
return;
|
|
}
|
|
scope(exit)
|
|
{
|
|
p.handle = null; // nullify the handle anyway
|
|
p.name = null;
|
|
--p.refs;
|
|
p = null;
|
|
}
|
|
//fprintf(std.c.stdio.stderr, ("Closing file `"~name~"`.\n\0").ptr);
|
|
errnoEnforce(.fclose(p.handle) == 0,
|
|
"Could not close file `"~p.name~"'");
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, succeeds vacuously. Otherwise, returns
|
|
$(WEB cplusplus.com/reference/clibrary/cstdio/_clearerr.html,
|
|
_clearerr) for the file handle.
|
|
*/
|
|
void clearerr()
|
|
{
|
|
p is null || p.handle is null ||
|
|
.clearerr(p.handle);
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, calls $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/_fflush.html, _fflush) for the
|
|
file handle and throws on error.
|
|
*/
|
|
void flush()
|
|
{
|
|
errnoEnforce
|
|
(.fflush(enforce(p.handle, "Calling fflush() on an unopened file"))
|
|
== 0);
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, calls $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/fread.html, fread) for the
|
|
file handle and throws on error.
|
|
*/
|
|
T[] rawRead(T)(T[] buffer)
|
|
{
|
|
enforce(buffer.length);
|
|
invariant result =
|
|
.fread(buffer.ptr, T.sizeof, buffer.length, p.handle);
|
|
errnoEnforce(!error);
|
|
return result ? buffer[0 .. result] : null;
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, calls $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/fwrite.html, fwrite) for the
|
|
file handle and throws on error.
|
|
*/
|
|
void rawWrite(T)(in T[] buffer)
|
|
{
|
|
auto result =
|
|
.fwrite(buffer.ptr, T.sizeof, buffer.length, p.handle);
|
|
if (result == result.max) result = 0;
|
|
errnoEnforce(result == buffer.length,
|
|
text("Wrote ", result, " instead of ", buffer.length,
|
|
" objects of type ", T.stringof, " to file `",
|
|
p.name, "'"));
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, calls $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/fseek.html, fseek) for the
|
|
file handle. Throws on error.
|
|
*/
|
|
void seek(long offset, int origin = SEEK_SET)
|
|
{
|
|
enforce(p && p.handle,
|
|
"Attempting to seek() in an unopened file");
|
|
// @@@ Dubious: why is fseek in std.c.stdio taking an int???
|
|
errnoEnforce(core.stdc.stdio.fseek(
|
|
p.handle, to!int(offset), origin) == 0,
|
|
"Could not seek in file `"~p.name~"'");
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, calls $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/ftell.html, ftell) for the
|
|
managed file handle. Throws on error.
|
|
*/
|
|
ulong tell() const
|
|
{
|
|
enforce(p && p.handle,
|
|
"Attempting to tell() in an unopened file");
|
|
immutable result = .ftell(cast(FILE*) p.handle);
|
|
errnoEnforce(result != -1,
|
|
"Query ftell() failed for file `"~p.name~"'");
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, calls $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/_rewind.html, _rewind) for the
|
|
file handle. Throws on error.
|
|
*/
|
|
void rewind()
|
|
{
|
|
enforce(p && p.handle,
|
|
"Attempting to rewind() an unopened file");
|
|
.rewind(p.handle);
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, calls $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/_setvbuf.html, _setvbuf) for
|
|
the file handle.
|
|
*/
|
|
void setvbuf(size_t size, int mode = _IOFBF)
|
|
{
|
|
errnoEnforce(
|
|
.setvbuf(enforce(p.handle,
|
|
"Attempting to call setvbuf() on an unopened file"),
|
|
null, mode, size) == 0,
|
|
"Could not set buffering for file `"~p.name~"'");
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, calls
|
|
$(WEB cplusplus.com/reference/clibrary/cstdio/_setvbuf.html,
|
|
_setvbuf) for the file handle. */
|
|
void setvbuf(void[] buf, int mode = _IOFBF)
|
|
{
|
|
errnoEnforce(
|
|
.setvbuf(enforce(p.handle,
|
|
"Attempting to call setvbuf() on an unopened file"),
|
|
cast(char*) buf.ptr, mode, buf.length) == 0,
|
|
"Could not set buffering for file `"~p.name~"'");
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, writes its
|
|
arguments in text format to the file. */
|
|
void write(S...)(S args)
|
|
{
|
|
//writef("", args[0 .. $]);
|
|
auto w = lockingTextWriter;
|
|
foreach (arg; args)
|
|
{
|
|
static if (isSomeString!(typeof(arg)))
|
|
{
|
|
w.put(arg);
|
|
}
|
|
else
|
|
{
|
|
std.format.formattedWrite(w, "%s", arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, writes its
|
|
arguments in text format to the file, followed by a newline. */
|
|
void writeln(S...)(S args)
|
|
{
|
|
write(args, '\n');
|
|
.fflush(p.handle);
|
|
}
|
|
|
|
/**
|
|
If the file is not opened, throws an exception. Otherwise, writes its
|
|
arguments in text format to the file, according to the format in the
|
|
first argument. */
|
|
void writef(S...)(S args) if (isSomeString!(S[0]))
|
|
{
|
|
assert(p);
|
|
assert(p.handle);
|
|
enum errorMessage =
|
|
"You must pass a formatting string as the first"
|
|
" argument to writef. If no formatting is needed,"
|
|
" you may want to use write.";
|
|
static assert(isSomeString!(S[0]), errorMessage);
|
|
auto w = lockingTextWriter;
|
|
std.format.formattedWrite(w, args);
|
|
}
|
|
|
|
/**
|
|
Same as writef, plus adds a newline. */
|
|
void writefln(S...)(S args)
|
|
{
|
|
auto w = lockingTextWriter;
|
|
std.format.formattedWrite(w, args);
|
|
w.put('\n');
|
|
.fflush(p.handle);
|
|
}
|
|
|
|
/**********************************
|
|
Read line from stream $(D fp) and write it to $(D buf[]), including
|
|
terminating character.
|
|
|
|
This is often faster than $(D File.readln(dchar)) because the buffer
|
|
is reused each call. Note that reusing the buffer means that the
|
|
previous contents of it has to be copied if needed.
|
|
|
|
Params:
|
|
fp = input stream
|
|
buf = buffer used to store the resulting line data. buf is
|
|
resized as necessary.
|
|
|
|
Returns:
|
|
0 for end of file, otherwise number of characters read
|
|
|
|
Throws: $(D StdioException) on error
|
|
|
|
Example:
|
|
---
|
|
// Reads $(D stdin) and writes it to $(D stdout).
|
|
import std.stdio;
|
|
|
|
int main()
|
|
{
|
|
char[] buf;
|
|
while (readln(stdin, buf))
|
|
write(buf);
|
|
return 0;
|
|
}
|
|
---
|
|
This method is more efficient than the one in the previous example
|
|
because $(D readln(stdin, buf)) reuses (if possible) memory
|
|
allocated by $(D buf), whereas $(D buf = readln()) makes a
|
|
new memory allocation with every line.
|
|
*/
|
|
size_t readln(ref char[] buf, dchar terminator = '\n')
|
|
{
|
|
enforce(p && p.handle, "Attempt to read from an unopened file.");
|
|
return readlnImpl(p.handle, buf, terminator);
|
|
}
|
|
|
|
/** ditto */
|
|
string readln(dchar terminator = '\n')
|
|
{
|
|
char[] buf;
|
|
readln(buf, terminator);
|
|
return assumeUnique(buf);
|
|
}
|
|
|
|
/** ditto */
|
|
// TODO: optimize this
|
|
size_t readln(inout wchar[] buf, dchar terminator = '\n')
|
|
{
|
|
string s = readln(terminator);
|
|
if (!s.length) return 0;
|
|
buf.length = 0;
|
|
foreach (wchar c; s)
|
|
{
|
|
buf ~= c;
|
|
}
|
|
return buf.length;
|
|
}
|
|
|
|
/** ditto */
|
|
// TODO: fold this together with wchar
|
|
size_t readln(inout dchar[] buf, dchar terminator = '\n')
|
|
{
|
|
string s = readln(terminator);
|
|
if (!s.length) return 0;
|
|
buf.length = 0;
|
|
foreach (dchar c; s)
|
|
{
|
|
buf ~= c;
|
|
}
|
|
return buf.length;
|
|
}
|
|
|
|
/**
|
|
Returns a temporary file by calling $(WEB
|
|
cplusplus.com/reference/clibrary/cstdio/_tmpfile.html, _tmpfile). */
|
|
static File tmpfile()
|
|
{
|
|
auto h = errnoEnforce(core.stdc.stdio.tmpfile,
|
|
"Could not create temporary file with tmpfile()");
|
|
File result = void;
|
|
result.p = new Impl(h, 1, null);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Unsafe function that wraps an existing $(D FILE*). The resulting $(D
|
|
File) never takes the initiative in closing the file. */
|
|
/*private*/ static File wrapFile(FILE* f)
|
|
{
|
|
File result = void;
|
|
//result.p = new Impl(f, uint.max / 2, null);
|
|
result.p = new Impl(f, 9999, null);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
Returns the $(D FILE*) corresponding to this object.
|
|
*/
|
|
FILE* getFP()
|
|
{
|
|
enforce(p && p.handle,
|
|
"Attempting to call getFP() on an unopened file");
|
|
return p.handle;
|
|
}
|
|
|
|
unittest
|
|
{
|
|
assert(stdout.getFP == std.c.stdio.stdout);
|
|
}
|
|
|
|
/**
|
|
Returns the file number corresponding to this object.
|
|
*/
|
|
version(Posix) int fileno() const
|
|
{
|
|
enforce(p && p.handle,
|
|
"Attempting to call fileno() on an unopened file");
|
|
return core.stdc.stdio.fileno(cast(FILE*) p.handle);
|
|
}
|
|
|
|
/**
|
|
Range that reads one line at a time. */
|
|
enum KeepTerminator : bool { no, yes }
|
|
/// ditto
|
|
struct ByLine(Char, Terminator)
|
|
{
|
|
File file;
|
|
Char[] line;
|
|
Terminator terminator;
|
|
KeepTerminator keepTerminator;
|
|
|
|
this(File f, KeepTerminator kt = KeepTerminator.no,
|
|
Terminator terminator = '\n')
|
|
{
|
|
file = f;
|
|
this.terminator = terminator;
|
|
keepTerminator = kt;
|
|
popFront; // prime the range
|
|
// @@@BUG@@@ line below should not exist
|
|
//if (file.p) ++file.p.refs;
|
|
}
|
|
|
|
/// Range primitive implementations.
|
|
ByLine!(Char, Terminator) opSlice()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
/// Ditto
|
|
bool empty() const
|
|
{
|
|
return !file.isOpen;
|
|
}
|
|
|
|
/// Ditto
|
|
Char[] front()
|
|
{
|
|
return line;
|
|
}
|
|
|
|
/// Ditto
|
|
void popFront()
|
|
{
|
|
enforce(file.isOpen);
|
|
file.readln(line, terminator);
|
|
if (!line.length)
|
|
file.detach;
|
|
else if (keepTerminator == KeepTerminator.no
|
|
&& std.algorithm.endsWith(line, terminator))
|
|
line.length = line.length - 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Convenience function that returns the $(D LinesReader) corresponding
|
|
to this file. */
|
|
ByLine!(Char, Terminator) byLine(Terminator = char, Char = char)
|
|
(KeepTerminator keepTerminator = KeepTerminator.no,
|
|
Terminator terminator = '\n')
|
|
{
|
|
return typeof(return)(this, keepTerminator, terminator);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//printf("Entering test at line %d\n", __LINE__);
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
std.file.write("testingByLine", "asd\ndef\nasdf");
|
|
auto witness = [ "asd", "def", "asdf" ];
|
|
uint i;
|
|
auto f = File("testingByLine");
|
|
scope(exit)
|
|
{
|
|
f.close;
|
|
assert(!f.isOpen);
|
|
//std.file.remove("testingByLine");
|
|
}
|
|
foreach (line; f.byLine())
|
|
{
|
|
assert(line == witness[i++]);
|
|
}
|
|
assert(i == witness.length);
|
|
i = 0;
|
|
f.rewind;
|
|
foreach (line; f.byLine(KeepTerminator.yes))
|
|
{
|
|
assert(line == witness[i++] ~ '\n' || i == witness.length);
|
|
}
|
|
assert(i == witness.length);
|
|
}
|
|
|
|
template byRecord(Fields...)
|
|
{
|
|
ByRecord!(Fields) byRecord(string format)
|
|
{
|
|
return typeof(return)(this, format);
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
std.file.write("deleteme", "1 2\n4 1\n5 100");
|
|
scope(exit) std.file.remove("deleteme");
|
|
File f = File("deleteme");
|
|
scope(exit) f.close;
|
|
auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ];
|
|
uint i;
|
|
foreach (e; f.byRecord!(int, int)("%s %s"))
|
|
{
|
|
//.writeln(e);
|
|
assert(e == t[i++]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Iterates through a file a chunk at a time by using $(D foreach).
|
|
|
|
Example:
|
|
|
|
---------
|
|
void main()
|
|
{
|
|
foreach (ubyte[] buffer; stdin.byChunk(4096))
|
|
{
|
|
... use buffer ...
|
|
}
|
|
}
|
|
---------
|
|
|
|
The content of $(D buffer) is reused across calls. In the example
|
|
above, $(D buffer.length) is 4096 for all iterations, except for the
|
|
last one, in which case $(D buffer.length) may be less than 4096 (but
|
|
always greater than zero).
|
|
|
|
In case of an I/O error, an $(D StdioException) is thrown.
|
|
*/
|
|
chunks byChunk(size_t chunkSize)
|
|
{
|
|
return chunks(this, chunkSize);
|
|
}
|
|
|
|
/**
|
|
$(D Range) that locks the file and allows fast writing to it.
|
|
*/
|
|
struct LockingTextWriter {
|
|
//@@@ Hacky implementation due to bugs, see the correct
|
|
//implementation at the end of this struct
|
|
FILE* fps; // the shared file handle
|
|
_iobuf* handle; // the unshared version of fps
|
|
int orientation;
|
|
|
|
this(ref File f)
|
|
{
|
|
enforce(f.p && f.p.handle);
|
|
fps = f.p.handle;
|
|
orientation = fwide(fps, 0);
|
|
FLOCK(fps);
|
|
handle = cast(_iobuf*)fps;
|
|
}
|
|
|
|
~this()
|
|
{
|
|
FUNLOCK(fps);
|
|
fps = null;
|
|
handle = null;
|
|
}
|
|
|
|
/// Range primitive implementations.
|
|
void put(A)(A writeme) if (is(ElementType!A : dchar))
|
|
{
|
|
alias ElementType!A C;
|
|
static assert(!is(C == void));
|
|
// writeln("typeof(A.init[0]) = ", typeof(A.init[0]),
|
|
// ", ElementType!A = ", ElementType!A);
|
|
if (writeme[0].sizeof == 1 && orientation <= 0)
|
|
{
|
|
//file.write(writeme); causes infinite recursion!!!
|
|
//file.rawWrite(writeme);
|
|
auto result =
|
|
.fwrite(writeme.ptr, C.sizeof, writeme.length, fps);
|
|
//if (result == result.max) result = 0;
|
|
if (result != writeme.length) errnoEnforce(0);
|
|
}
|
|
else
|
|
{
|
|
// put each character in turn
|
|
foreach (dchar c; writeme)
|
|
{
|
|
put(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
// @@@BUG@@@ 2340
|
|
//void front(C)(C c) if (is(C : dchar)) {
|
|
/// ditto
|
|
void put(C)(C c) if (is(C : dchar))
|
|
{
|
|
static if (c.sizeof == 1)
|
|
{
|
|
// simple char
|
|
if (orientation <= 0) FPUTC(c, handle);
|
|
else FPUTWC(c, handle);
|
|
}
|
|
else static if (c.sizeof == 2)
|
|
{
|
|
if (orientation <= 0)
|
|
{
|
|
if (c <= 0x7F)
|
|
{
|
|
FPUTC(c, backend);
|
|
}
|
|
else
|
|
{
|
|
char[4] buf;
|
|
auto b = std.utf.toUTF8(buf, c);
|
|
foreach (i ; 0 .. b.length)
|
|
FPUTC(b[i], backend);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FPUTWC(c, backend);
|
|
}
|
|
}
|
|
else // 32-bit characters
|
|
{
|
|
if (orientation <= 0)
|
|
{
|
|
if (c <= 0x7F)
|
|
{
|
|
FPUTC(c, handle);
|
|
}
|
|
else
|
|
{
|
|
char[4] buf;
|
|
auto b = std.utf.toUTF8(buf, c);
|
|
foreach (i ; 0 .. b.length)
|
|
FPUTC(b[i], handle);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
version (Windows)
|
|
{
|
|
assert(isValidDchar(c));
|
|
if (c <= 0xFFFF)
|
|
{
|
|
FPUTWC(c, handle);
|
|
}
|
|
else
|
|
{
|
|
FPUTWC(cast(wchar)
|
|
((((c - 0x10000) >> 10) & 0x3FF)
|
|
+ 0xD800), handle);
|
|
FPUTWC(cast(wchar)
|
|
(((c - 0x10000) & 0x3FF) + 0xDC00),
|
|
handle);
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
FPUTWC(c, handle);
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//@@@BUG correct implementation is below:
|
|
|
|
// File file;
|
|
// int orientation;
|
|
|
|
// this(File f)
|
|
// {
|
|
// enforce(f.isOpen);
|
|
// swap(file, f);
|
|
// // @@@BUG@@@ This line should NOT be there!
|
|
// file.p.refs++;
|
|
// orientation = fwide(file.p.handle, 0);
|
|
// //FLOCK(file.p.handle);
|
|
// }
|
|
|
|
// // @@@BUG@@@ uncomment and you get a linker error
|
|
// // this(this)
|
|
// // {
|
|
// // //FLOCK(file.p.handle);
|
|
// // }
|
|
|
|
// ~this()
|
|
// {
|
|
// if (!file.p.handle) return;
|
|
// //FUNLOCK(file.p.handle);
|
|
// }
|
|
|
|
// void opAssign(LockingTextWriter rhs)
|
|
// {
|
|
// swap(this, rhs);
|
|
// }
|
|
}
|
|
|
|
/// Convenience function.
|
|
LockingTextWriter lockingTextWriter()
|
|
{
|
|
// @@@BUG2341@@@
|
|
return LockingTextWriter(this);
|
|
// The code below avoids bug 2341
|
|
//printf("Entering fn with %d refs\n", *_refs);
|
|
// auto result = LockingTextWriter(this);
|
|
// return result;
|
|
}
|
|
}
|
|
|
|
private
|
|
void writefx(FILE* fps, TypeInfo[] arguments, void* argptr, int newline=false)
|
|
{
|
|
int orientation = fwide(fps, 0); // move this inside the lock?
|
|
|
|
/* Do the file stream locking at the outermost level
|
|
* rather than character by character.
|
|
*/
|
|
FLOCK(fps);
|
|
scope(exit) FUNLOCK(fps);
|
|
|
|
auto fp = cast(_iobuf*)fps; // fp is locked version
|
|
|
|
if (orientation <= 0) // byte orientation or no orientation
|
|
{
|
|
void putc(dchar c)
|
|
{
|
|
if (c <= 0x7F)
|
|
{
|
|
FPUTC(c, fp);
|
|
}
|
|
else
|
|
{
|
|
char[4] buf = void;
|
|
foreach (i; 0 .. std.utf.toUTF8(buf, c).length)
|
|
FPUTC(buf[i], fp);
|
|
}
|
|
}
|
|
|
|
std.format.doFormat(&putc, arguments, argptr);
|
|
if (newline)
|
|
FPUTC('\n', fp);
|
|
}
|
|
else if (orientation > 0) // wide orientation
|
|
{
|
|
version (Windows)
|
|
{
|
|
void putcw(dchar c)
|
|
{
|
|
assert(isValidDchar(c));
|
|
if (c <= 0xFFFF)
|
|
{
|
|
FPUTWC(c, fp);
|
|
}
|
|
else
|
|
{
|
|
FPUTWC(cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) +
|
|
0xD800), fp);
|
|
FPUTWC(cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00), fp);
|
|
}
|
|
}
|
|
}
|
|
else version (Posix)
|
|
{
|
|
void putcw(dchar c)
|
|
{
|
|
FPUTWC(c, fp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
|
|
std.format.doFormat(&putcw, arguments, argptr);
|
|
if (newline)
|
|
FPUTWC('\n', fp);
|
|
}
|
|
}
|
|
|
|
template isStreamingDevice(T)
|
|
{
|
|
enum isStreamingDevice = is(T : FILE*) ||
|
|
is(T : File);
|
|
}
|
|
|
|
/***********************************
|
|
For each argument $(D arg) in $(D args), format the argument (as per
|
|
$(LINK2 std_conv.html, to!(string)(arg))) and write the resulting
|
|
string to $(D args[0]). A call without any arguments will fail to
|
|
compile.
|
|
|
|
Throws: In case of an I/O error, throws an $(D StdioException).
|
|
*/
|
|
void write(T...)(T args) if (!is(T[0] : File))
|
|
{
|
|
stdout.write(args);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//printf("Entering test at line %d\n", __LINE__);
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
void[] buf;
|
|
write(buf);
|
|
// // test write
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
auto f = File(file, "w");
|
|
scope(exit) { std.file.remove(file); }
|
|
f.write("Hello, ", "world number ", 42, "!");
|
|
f.close;
|
|
assert(cast(char[]) std.file.read(file) == "Hello, world number 42!");
|
|
// // test write on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
//stdout.open(file, "w");
|
|
Object obj;
|
|
//write("Hello, ", "world number ", 42, "! ", obj);
|
|
//stdout.close;
|
|
// auto result = cast(char[]) std.file.read(file);
|
|
// assert(result == "Hello, world number 42! null", result);
|
|
}
|
|
|
|
/***********************************
|
|
* Equivalent to $(D write(args, '\n')). Calling $(D writeln) without
|
|
* arguments is valid and just prints a newline to the standard
|
|
* output.
|
|
*/
|
|
void writeln(T...)(T args)
|
|
{
|
|
stdout.write(args, '\n');
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//printf("Entering test at line %d\n", __LINE__);
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
// test writeln
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
auto f = File(file, "w");
|
|
scope(exit) { std.file.remove(file); }
|
|
f.writeln("Hello, ", "world number ", 42, "!");
|
|
f.close;
|
|
version (Windows)
|
|
assert(cast(char[]) std.file.read(file) ==
|
|
"Hello, world number 42!\r\n");
|
|
else
|
|
assert(cast(char[]) std.file.read(file) ==
|
|
"Hello, world number 42!\n");
|
|
// test writeln on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
stdout.open(file, "w");
|
|
writeln("Hello, ", "world number ", 42, "!");
|
|
stdout.close;
|
|
version (Windows)
|
|
assert(cast(char[]) std.file.read(file) ==
|
|
"Hello, world number 42!\r\n");
|
|
else
|
|
assert(cast(char[]) std.file.read(file) ==
|
|
"Hello, world number 42!\n");
|
|
}
|
|
|
|
/***********************************
|
|
* If the first argument $(D args[0]) is a $(D FILE*), use
|
|
* $(LINK2 std_format.html#format-string, the format specifier) in
|
|
* $(D args[1]) to control the formatting of $(D
|
|
* args[2..$]), and write the resulting string to $(D args[0]).
|
|
* If $(D arg[0]) is not a $(D FILE*), the call is
|
|
* equivalent to $(D writef(stdout, args)).
|
|
*
|
|
|
|
IMPORTANT:
|
|
|
|
New behavior starting with D 2.006: unlike previous versions,
|
|
$(D writef) (and also $(D writefln)) only scans its first
|
|
string argument for format specifiers, but not subsequent string
|
|
arguments. This decision was made because the old behavior made it
|
|
unduly hard to simply print string variables that occasionally
|
|
embedded percent signs.
|
|
|
|
Also new starting with 2.006 is support for positional
|
|
parameters with
|
|
$(LINK2 http://opengroup.org/onlinepubs/009695399/functions/printf.html,
|
|
POSIX) syntax.
|
|
|
|
Example:
|
|
|
|
-------------------------
|
|
writef("Date: %2$s %1$s", "October", 5); // "Date: 5 October"
|
|
------------------------
|
|
|
|
The positional and non-positional styles can be mixed in the same
|
|
format string. (POSIX leaves this behavior undefined.) The internal
|
|
counter for non-positional parameters tracks the popFront parameter after
|
|
the largest positional parameter already used.
|
|
|
|
New starting with 2.008: raw format specifiers. Using the "%r"
|
|
specifier makes $(D writef) simply write the binary
|
|
representation of the argument. Use "%-r" to write numbers in little
|
|
endian format, "%+r" to write numbers in big endian format, and "%r"
|
|
to write numbers in platform-native format.
|
|
|
|
*/
|
|
|
|
void writef(T...)(T args)
|
|
{
|
|
stdout.writef(args);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//printf("Entering test at line %d\n", __LINE__);
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
// test writef
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
auto f = File(file, "w");
|
|
scope(exit) { std.file.remove(file); }
|
|
f.writef("Hello, %s world number %s!", "nice", 42);
|
|
f.close;
|
|
assert(cast(char[]) std.file.read(file) == "Hello, nice world number 42!");
|
|
// test write on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
stdout.open(file, "w");
|
|
writef("Hello, %s world number %s!", "nice", 42);
|
|
stdout.close;
|
|
assert(cast(char[]) std.file.read(file) == "Hello, nice world number 42!");
|
|
}
|
|
|
|
/***********************************
|
|
* Equivalent to $(D writef(args, '\n')).
|
|
*/
|
|
void writefln(T...)(T args)
|
|
{
|
|
stdout.writefln(args);
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//printf("Entering test at line %d\n", __LINE__);
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
// test writefln
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
auto f = File(file, "w");
|
|
scope(exit) { std.file.remove(file); }
|
|
f.writefln("Hello, %s world number %s!", "nice", 42);
|
|
f.close;
|
|
version (Windows)
|
|
assert(cast(char[]) std.file.read(file) ==
|
|
"Hello, nice world number 42!\r\n");
|
|
else
|
|
assert(cast(char[]) std.file.read(file) ==
|
|
"Hello, nice world number 42!\n",
|
|
cast(char[]) std.file.read(file));
|
|
// test write on stdout
|
|
auto saveStdout = stdout;
|
|
scope(exit) stdout = saveStdout;
|
|
stdout.open(file, "w");
|
|
assert(stdout.isOpen);
|
|
writefln("Hello, %s world number %s!", "nice", 42);
|
|
foreach (F ; TypeTuple!(ifloat, idouble, ireal))
|
|
{
|
|
F a = 5i;
|
|
F b = a % 2;
|
|
writeln(b);
|
|
}
|
|
stdout.close;
|
|
auto read = cast(char[]) std.file.read(file);
|
|
version (Windows)
|
|
assert(read == "Hello, nice world number 42!\r\n1\r\n1\r\n1\r\n", read);
|
|
else
|
|
assert(read == "Hello, nice world number 42!\n1\n1\n1\n", "["~read~"]");
|
|
}
|
|
|
|
/**********************************
|
|
* Read line from stream $(D fp).
|
|
* Returns:
|
|
* $(D null) for end of file,
|
|
* $(D char[]) for line read from $(D fp), including terminating character
|
|
* Params:
|
|
* $(D fp) = input stream
|
|
* $(D terminator) = line terminator, '\n' by default
|
|
* Throws:
|
|
* $(D StdioException) on error
|
|
* Example:
|
|
* Reads $(D stdin) and writes it to $(D stdout).
|
|
---
|
|
import std.stdio;
|
|
|
|
int main()
|
|
{
|
|
char[] buf;
|
|
while ((buf = readln()) != null)
|
|
write(buf);
|
|
return 0;
|
|
}
|
|
---
|
|
*/
|
|
string readln(dchar terminator = '\n')
|
|
{
|
|
return stdin.readln(terminator);
|
|
}
|
|
|
|
/** ditto */
|
|
size_t readln(inout char[] buf, dchar terminator = '\n')
|
|
{
|
|
return stdin.readln(buf, terminator);
|
|
}
|
|
|
|
/*
|
|
* Convenience function that forwards to $(D std.c.stdio.fopen)
|
|
* with appropriately-constructed C-style strings.
|
|
*/
|
|
private FILE* fopen(in char[] name, in char[] mode = "r")
|
|
{
|
|
const namez = toStringz(name), modez = toStringz(mode);
|
|
return fopen64(namez, modez);
|
|
}
|
|
|
|
version (Posix)
|
|
{
|
|
extern(C) FILE* popen(const char*, const char*);
|
|
|
|
/***********************************
|
|
* Convenience function that forwards to $(D std.c.stdio.popen)
|
|
* with appropriately-constructed C-style strings.
|
|
*/
|
|
FILE* popen(in char[] name, in char[] mode = "r")
|
|
{
|
|
return popen(toStringz(name), toStringz(mode));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Convenience function that forwards to $(D std.c.stdio.fwrite)
|
|
* and throws an exception upon error
|
|
*/
|
|
private void binaryWrite(T)(FILE* f, T obj)
|
|
{
|
|
immutable result = fwrite(obj.ptr, obj[0].sizeof, obj.length, f);
|
|
if (result != obj.length) StdioException();
|
|
}
|
|
|
|
/**
|
|
* Iterates through the lines of a file by using $(D foreach).
|
|
*
|
|
* Example:
|
|
*
|
|
---------
|
|
void main()
|
|
{
|
|
foreach (string line; lines(stdin))
|
|
{
|
|
... use line ...
|
|
}
|
|
}
|
|
---------
|
|
The line terminator ('\n' by default) is part of the string read (it
|
|
could be missing in the last line of the file). Several types are
|
|
supported for $(D line), and the behavior of $(D lines)
|
|
changes accordingly:
|
|
|
|
$(OL $(LI If $(D line) has type $(D string), $(D
|
|
wstring), or $(D dstring), a new string of the respective type
|
|
is allocated every read.) $(LI If $(D line) has type $(D
|
|
char[]), $(D wchar[]), $(D dchar[]), the line's content
|
|
will be reused (overwritten) across reads.) $(LI If $(D line)
|
|
has type $(D immutable(ubyte)[]), the behavior is similar to
|
|
case (1), except that no UTF checking is attempted upon input.) $(LI
|
|
If $(D line) has type $(D ubyte[]), the behavior is
|
|
similar to case (2), except that no UTF checking is attempted upon
|
|
input.))
|
|
|
|
In all cases, a two-symbols versions is also accepted, in which case
|
|
the first symbol (of integral type, e.g. $(D ulong) or $(D
|
|
uint)) tracks the zero-based number of the current line.
|
|
|
|
Example:
|
|
----
|
|
foreach (ulong i, string line; lines(stdin))
|
|
{
|
|
... use line ...
|
|
}
|
|
----
|
|
|
|
In case of an I/O error, an $(D StdioException) is thrown.
|
|
*/
|
|
|
|
struct lines
|
|
{
|
|
private File f;
|
|
private dchar terminator = '\n';
|
|
private string fileName;
|
|
|
|
static lines opCall(File f, dchar terminator = '\n')
|
|
{
|
|
lines result;
|
|
result.f = f;
|
|
result.terminator = terminator;
|
|
return result;
|
|
}
|
|
|
|
// Keep these commented lines for later, when Walter fixes the
|
|
// exception model.
|
|
|
|
// static lines opCall(string fName, dchar terminator = '\n')
|
|
// {
|
|
// auto f = enforce(fopen(fName),
|
|
// new StdioException("Cannot open file `"~fName~"' for reading"));
|
|
// auto result = lines(f, terminator);
|
|
// result.fileName = fName;
|
|
// return result;
|
|
// }
|
|
|
|
int opApply(D)(D dg)
|
|
{
|
|
// scope(exit) {
|
|
// if (fileName.length && fclose(f))
|
|
// StdioException("Could not close file `"~fileName~"'");
|
|
// }
|
|
alias ParameterTypeTuple!(dg) Parms;
|
|
static if (isSomeString!(Parms[$ - 1]))
|
|
{
|
|
enum bool duplicate = is(Parms[$ - 1] == string)
|
|
|| is(Parms[$ - 1] == wstring) || is(Parms[$ - 1] == dstring);
|
|
int result = 0;
|
|
static if (is(Parms[$ - 1] : const(char)[]))
|
|
alias char C;
|
|
else static if (is(Parms[$ - 1] : const(wchar)[]))
|
|
alias wchar C;
|
|
else static if (is(Parms[$ - 1] : const(dchar)[]))
|
|
alias dchar C;
|
|
C[] line;
|
|
static if (Parms.length == 2)
|
|
Parms[0] i = 0;
|
|
for (;;)
|
|
{
|
|
if (!f.readln(line, terminator)) break;
|
|
auto copy = to!(Parms[$ - 1])(line);
|
|
static if (Parms.length == 2)
|
|
{
|
|
result = dg(i, copy);
|
|
++i;
|
|
}
|
|
else
|
|
{
|
|
result = dg(copy);
|
|
}
|
|
if (result != 0) break;
|
|
}
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// raw read
|
|
return opApplyRaw(dg);
|
|
}
|
|
}
|
|
// no UTF checking
|
|
int opApplyRaw(D)(D dg)
|
|
{
|
|
alias ParameterTypeTuple!(dg) Parms;
|
|
enum duplicate = is(Parms[$ - 1] : immutable(ubyte)[]);
|
|
int result = 1;
|
|
int c = void;
|
|
FLOCK(f.p.handle);
|
|
scope(exit) FUNLOCK(f.p.handle);
|
|
ubyte[] buffer;
|
|
static if (Parms.length == 2)
|
|
Parms[0] line = 0;
|
|
while ((c = FGETC(cast(_iobuf*)f.p.handle)) != -1)
|
|
{
|
|
buffer ~= to!(ubyte)(c);
|
|
if (c == terminator)
|
|
{
|
|
static if (duplicate)
|
|
auto arg = assumeUnique(buffer);
|
|
else
|
|
alias buffer arg;
|
|
// unlock the file while calling the delegate
|
|
FUNLOCK(f.p.handle);
|
|
scope(exit) FLOCK(f.p.handle);
|
|
static if (Parms.length == 1)
|
|
{
|
|
result = dg(arg);
|
|
}
|
|
else
|
|
{
|
|
result = dg(line, arg);
|
|
++line;
|
|
}
|
|
if (result) break;
|
|
static if (!duplicate)
|
|
buffer.length = 0;
|
|
}
|
|
}
|
|
// can only reach when FGETC returned -1
|
|
if (!f.eof) throw new StdioException("Error in reading file"); // error occured
|
|
return result;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//printf("Entering test at line %d\n", __LINE__);
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
scope(exit) { std.file.remove(file); }
|
|
alias TypeTuple!(string, wstring, dstring,
|
|
char[], wchar[], dchar[])
|
|
TestedWith;
|
|
foreach (T; TestedWith) {
|
|
// test looping with an empty file
|
|
std.file.write(file, "");
|
|
auto f = File(file, "r");
|
|
foreach (T line; lines(f))
|
|
{
|
|
assert(false);
|
|
}
|
|
f.close;
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(file, "Line one\nline two\nline three\n");
|
|
f.open(file, "r");
|
|
uint i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(line == "Line one\n");
|
|
else if (i == 1) assert(line == "line two\n");
|
|
else if (i == 2) assert(line == "line three\n");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close;
|
|
|
|
// test looping with a file with three lines, last without a newline
|
|
std.file.write(file, "Line one\nline two\nline three");
|
|
f.open(file, "r");
|
|
i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(line == "Line one\n");
|
|
else if (i == 1) assert(line == "line two\n");
|
|
else if (i == 2) assert(line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close;
|
|
}
|
|
|
|
// test with ubyte[] inputs
|
|
//@@@BUG 2612@@@
|
|
//alias TypeTuple!(immutable(ubyte)[], ubyte[]) TestedWith2;
|
|
alias TypeTuple!(invariant(ubyte)[], ubyte[]) TestedWith2;
|
|
foreach (T; TestedWith2) {
|
|
// test looping with an empty file
|
|
std.file.write(file, "");
|
|
auto f = File(file, "r");
|
|
foreach (T line; lines(f))
|
|
{
|
|
assert(false);
|
|
}
|
|
f.close;
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(file, "Line one\nline two\nline three\n");
|
|
f.open(file, "r");
|
|
uint i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n",
|
|
T.stringof ~ " " ~ cast(char[]) line);
|
|
else if (i == 2) assert(cast(char[]) line == "line three\n");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close;
|
|
|
|
// test looping with a file with three lines, last without a newline
|
|
std.file.write(file, "Line one\nline two\nline three");
|
|
f.open(file, "r");
|
|
i = 0;
|
|
foreach (T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n");
|
|
else if (i == 2) assert(cast(char[]) line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close;
|
|
|
|
}
|
|
|
|
foreach (T; TypeTuple!(ubyte[]))
|
|
{
|
|
// test looping with a file with three lines, last without a newline
|
|
// using a counter too this time
|
|
std.file.write(file, "Line one\nline two\nline three");
|
|
auto f = File(file, "r");
|
|
uint i = 0;
|
|
foreach (ulong j, T line; lines(f))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Line one\n");
|
|
else if (i == 1) assert(cast(char[]) line == "line two\n");
|
|
else if (i == 2) assert(cast(char[]) line == "line three");
|
|
else assert(false);
|
|
++i;
|
|
}
|
|
f.close;
|
|
}
|
|
}
|
|
|
|
/**
|
|
Iterates through a file a chunk at a time by using $(D
|
|
foreach).
|
|
|
|
Example:
|
|
|
|
---------
|
|
void main()
|
|
{
|
|
foreach (ubyte[] buffer; chunks(stdin, 4096))
|
|
{
|
|
... use buffer ...
|
|
}
|
|
}
|
|
---------
|
|
|
|
The content of $(D buffer) is reused across calls. In the
|
|
example above, $(D buffer.length) is 4096 for all iterations,
|
|
except for the last one, in which case $(D buffer.length) may
|
|
be less than 4096 (but always greater than zero).
|
|
|
|
In case of an I/O error, an $(D StdioException) is thrown.
|
|
*/
|
|
|
|
struct chunks
|
|
{
|
|
private File f;
|
|
private size_t size;
|
|
private string fileName;
|
|
|
|
static chunks opCall(File f, size_t size)
|
|
{
|
|
assert(size);
|
|
chunks result;
|
|
result.f = f;
|
|
result.size = size;
|
|
return result;
|
|
}
|
|
|
|
// static chunks opCall(string fName, size_t size)
|
|
// {
|
|
// auto f = enforce(fopen(fName),
|
|
// new StdioException("Cannot open file `"~fName~"' for reading"));
|
|
// auto result = chunks(f, size);
|
|
// result.fileName = fName;
|
|
// return result;
|
|
// }
|
|
|
|
int opApply(D)(D dg)
|
|
{
|
|
const maxStackSize = 1024 * 16;
|
|
ubyte[] buffer = void;
|
|
if (size < maxStackSize)
|
|
buffer = (cast(ubyte*) alloca(size))[0 .. size];
|
|
else
|
|
buffer = new ubyte[size];
|
|
size_t r = void;
|
|
int result = 1;
|
|
uint tally = 0;
|
|
while ((r = core.stdc.stdio.fread(buffer.ptr,
|
|
buffer[0].sizeof, size, f.p.handle)) > 0)
|
|
{
|
|
assert(r <= size);
|
|
if (r != size)
|
|
{
|
|
// error occured
|
|
if (!f.eof) throw new StdioException(null);
|
|
buffer.length = r;
|
|
}
|
|
static if (is(typeof(dg(tally, buffer)))) {
|
|
if ((result = dg(tally, buffer)) != 0) break;
|
|
} else {
|
|
if ((result = dg(buffer)) != 0) break;
|
|
}
|
|
++tally;
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
//printf("Entering test at line %d\n", __LINE__);
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
string file = "dmd-build-test.deleteme.txt";
|
|
scope(exit) { std.file.remove(file); }
|
|
// test looping with an empty file
|
|
std.file.write(file, "");
|
|
auto f = File(file, "r");
|
|
foreach (ubyte[] line; chunks(f, 4))
|
|
{
|
|
assert(false);
|
|
}
|
|
f.close;
|
|
|
|
// test looping with a file with three lines
|
|
std.file.write(file, "Line one\nline two\nline three\n");
|
|
f = File(file, "r");
|
|
uint i = 0;
|
|
foreach (ubyte[] line; chunks(f, 3))
|
|
{
|
|
if (i == 0) assert(cast(char[]) line == "Lin");
|
|
else if (i == 1) assert(cast(char[]) line == "e o");
|
|
else if (i == 2) assert(cast(char[]) line == "ne\n");
|
|
else break;
|
|
++i;
|
|
}
|
|
f.close;
|
|
}
|
|
|
|
/*********************
|
|
* Thrown if I/O errors happen.
|
|
*/
|
|
class StdioException : Exception
|
|
{
|
|
/// Operating system error code.
|
|
uint errno;
|
|
|
|
/**
|
|
Initialize with a message and an error code. */
|
|
this(string message, uint e = .getErrno)
|
|
{
|
|
errno = e;
|
|
version (linux)
|
|
{
|
|
char[256] buf = void;
|
|
assert(false);
|
|
auto s = std.c.string.strerror_r(errno, buf.ptr, buf.length);
|
|
}
|
|
else
|
|
{
|
|
auto s = std.c.string.strerror(errno);
|
|
}
|
|
auto sysmsg = to!string(s);
|
|
super(message ? message ~ "(" ~ sysmsg ~ ")" : sysmsg);
|
|
}
|
|
|
|
/** Convenience functions that throw an $(D StdioException). */
|
|
static void opCall(string msg)
|
|
{
|
|
throw new StdioException(msg);
|
|
}
|
|
|
|
/// ditto
|
|
static void opCall()
|
|
{
|
|
throw new StdioException(null, .getErrno);
|
|
}
|
|
}
|
|
|
|
extern(C) void std_stdio_static_this()
|
|
{
|
|
//printf("std_stdio_static_this()\n");
|
|
|
|
//Bind stdin, stdout, stderr
|
|
__gshared File.Impl stdinImpl = { null, uint.max / 2, null };
|
|
stdinImpl.handle = core.stdc.stdio.stdin;
|
|
.stdin.p = &stdinImpl;
|
|
// stdout
|
|
__gshared File.Impl stdoutImpl = { null, uint.max / 2, null };
|
|
stdoutImpl.handle = core.stdc.stdio.stdout;
|
|
.stdout.p = &stdoutImpl;
|
|
// stderr
|
|
__gshared File.Impl stderrImpl = { null, uint.max / 2, null };
|
|
stderrImpl.handle = core.stdc.stdio.stderr;
|
|
.stderr.p = &stderrImpl;
|
|
}
|
|
|
|
//---------
|
|
__gshared
|
|
{
|
|
File stdin;
|
|
File stdout;
|
|
File stderr;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
struct ByRecord(Fields...)
|
|
{
|
|
File file;
|
|
char[] line;
|
|
Tuple!(Fields) current;
|
|
string format;
|
|
|
|
this(File f, string format)
|
|
{
|
|
assert(f.isOpen);
|
|
file = f;
|
|
this.format = format;
|
|
popFront; // prime the range
|
|
}
|
|
|
|
/// Range primitive implementations.
|
|
ref auto opSlice()
|
|
{
|
|
return this;
|
|
}
|
|
|
|
/// Ditto
|
|
bool empty() const
|
|
{
|
|
return !file.isOpen;
|
|
}
|
|
|
|
/// Ditto
|
|
ref Tuple!(Fields) front()
|
|
{
|
|
return current;
|
|
}
|
|
|
|
/// Ditto
|
|
void popFront()
|
|
{
|
|
enforce(file.isOpen);
|
|
file.readln(line);
|
|
if (!line.length)
|
|
{
|
|
file.detach;
|
|
}
|
|
else
|
|
{
|
|
auto slack = formattedRead(chomp(line), format, ¤t);
|
|
enforce(slack.empty, text("Leftover characters in record: `",
|
|
slack, "'"));
|
|
}
|
|
}
|
|
}
|
|
|
|
template byRecord(Fields...)
|
|
{
|
|
ByRecord!(Fields) byRecord(File f, string format)
|
|
{
|
|
return typeof(return)(f, format);
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
scope(failure) printf("Failed test at line %d\n", __LINE__);
|
|
std.file.write("deleteme", "1 2\n4 1\n5 100");
|
|
scope(exit) std.file.remove("deleteme");
|
|
{
|
|
File f = File("deleteme");
|
|
scope(exit) f.close;
|
|
auto t = [ tuple(1, 2), tuple(4, 1), tuple(5, 100) ];
|
|
uint i;
|
|
foreach (e; f.byRecord!(int, int)("%s %s"))
|
|
{
|
|
//writeln(e);
|
|
assert(e == t[i++]);
|
|
}
|
|
assert(i == 3);
|
|
}
|
|
{
|
|
std.file.write("deleteme", "1:2 3\n4:1 5\n5:100");
|
|
File f = File("deleteme");
|
|
scope(exit) f.close;
|
|
auto t = [ tuple(1, [2,3][]), tuple(4, [1,5][]), tuple(5, [100][]) ];
|
|
uint i;
|
|
foreach (e; f.byRecord!(int, int[])("%s:%(s )"))
|
|
{
|
|
//writeln(e);
|
|
assert(e == t[i++]);
|
|
}
|
|
assert(i == 3);
|
|
}
|
|
}
|
|
|
|
// Private implementation of readln
|
|
private size_t readlnImpl(FILE* fps, ref char[] buf, dchar terminator = '\n')
|
|
{
|
|
version (DIGITAL_MARS_STDIO)
|
|
{
|
|
FLOCK(fps);
|
|
scope(exit) FUNLOCK(fps);
|
|
|
|
/* Since fps is now locked, we can create an "unshared" version
|
|
* of fp.
|
|
*/
|
|
auto fp = cast(_iobuf*)fps;
|
|
|
|
if (__fhnd_info[fp._file] & FHND_WCHAR)
|
|
{ /* Stream is in wide characters.
|
|
* Read them and convert to chars.
|
|
*/
|
|
static assert(wchar_t.sizeof == 2);
|
|
buf.length = 0;
|
|
int c2;
|
|
for (int c = void; (c = FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
{ buf ~= cast(char)c;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c >= 0xD800 && c <= 0xDBFF)
|
|
{
|
|
if ((c2 = FGETWC(fp)) != -1 ||
|
|
c2 < 0xDC00 && c2 > 0xDFFF)
|
|
{
|
|
StdioException("unpaired UTF-16 surrogate");
|
|
}
|
|
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
|
|
}
|
|
std.utf.encode(buf, c);
|
|
}
|
|
}
|
|
if (ferror(fps))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
|
|
auto sz = GC.sizeOf(buf.ptr);
|
|
//auto sz = buf.length;
|
|
buf = buf.ptr[0 .. sz];
|
|
if (fp._flag & _IONBF)
|
|
{
|
|
/* Use this for unbuffered I/O, when running
|
|
* across buffer boundaries, or for any but the common
|
|
* cases.
|
|
*/
|
|
L1:
|
|
char *p;
|
|
|
|
if (sz)
|
|
{
|
|
p = buf.ptr;
|
|
}
|
|
else
|
|
{
|
|
sz = 64;
|
|
p = cast(char*) GC.malloc(sz, GC.BlkAttr.NO_SCAN);
|
|
buf = p[0 .. sz];
|
|
}
|
|
size_t i = 0;
|
|
for (int c = void; (c = FGETC(fp)) != -1; )
|
|
{
|
|
if ((p[i] = cast(char)c) != terminator)
|
|
{
|
|
i++;
|
|
if (i < sz)
|
|
continue;
|
|
buf = p[0 .. i];
|
|
{
|
|
char[] buf2;
|
|
// This recursively does an unnecessary lock
|
|
readlnImpl(fps, buf2, terminator);
|
|
buf ~= buf2;
|
|
}
|
|
return buf.length;
|
|
}
|
|
else
|
|
{
|
|
buf = p[0 .. i + 1];
|
|
return i + 1;
|
|
}
|
|
}
|
|
if (ferror(fps))
|
|
StdioException();
|
|
buf = p[0 .. i];
|
|
return i;
|
|
}
|
|
else
|
|
{
|
|
int u = fp._cnt;
|
|
char* p = fp._ptr;
|
|
int i;
|
|
if (fp._flag & _IOTRAN)
|
|
{ /* Translated mode ignores \r and treats ^Z as end-of-file
|
|
*/
|
|
char c;
|
|
while (1)
|
|
{
|
|
if (i == u) // if end of buffer
|
|
goto L1; // give up
|
|
c = p[i];
|
|
i++;
|
|
if (c != '\r')
|
|
{
|
|
if (c == terminator)
|
|
break;
|
|
if (c != 0x1A)
|
|
continue;
|
|
goto L1;
|
|
}
|
|
else
|
|
{ if (i != u && p[i] == terminator)
|
|
break;
|
|
goto L1;
|
|
}
|
|
}
|
|
if (i > sz)
|
|
{
|
|
buf = cast(char[])GC.malloc(i, GC.BlkAttr.NO_SCAN)[0 .. i];
|
|
}
|
|
if (i - 1)
|
|
memcpy(buf.ptr, p, i - 1);
|
|
buf[i - 1] = cast(char)terminator;
|
|
buf = buf[0 .. i];
|
|
if (terminator == '\n' && c == '\r')
|
|
i++;
|
|
}
|
|
else
|
|
{
|
|
while (1)
|
|
{
|
|
if (i == u) // if end of buffer
|
|
goto L1; // give up
|
|
auto c = p[i];
|
|
i++;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
if (i > sz)
|
|
{
|
|
buf = cast(char[])GC.malloc(i, GC.BlkAttr.NO_SCAN)[0 .. i];
|
|
}
|
|
memcpy(buf.ptr, p, i);
|
|
buf = buf[0 .. i];
|
|
}
|
|
fp._cnt -= i;
|
|
fp._ptr += i;
|
|
return i;
|
|
}
|
|
}
|
|
else version (GCC_IO)
|
|
{
|
|
if (fwide(fps, 0) > 0)
|
|
{ /* Stream is in wide characters.
|
|
* Read them and convert to chars.
|
|
*/
|
|
FLOCK(fps);
|
|
scope(exit) FUNLOCK(fps);
|
|
auto fp = cast(_iobuf*)fps;
|
|
version (Windows)
|
|
{
|
|
buf.length = 0;
|
|
int c2;
|
|
for (int c = void; (c = FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
{ buf ~= c;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c >= 0xD800 && c <= 0xDBFF)
|
|
{
|
|
if ((c2 = FGETWC(fp)) != -1 ||
|
|
c2 < 0xDC00 && c2 > 0xDFFF)
|
|
{
|
|
StdioException("unpaired UTF-16 surrogate");
|
|
}
|
|
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
|
|
}
|
|
std.utf.encode(buf, c);
|
|
}
|
|
}
|
|
if (ferror(fp))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
buf.length = 0;
|
|
for (int c; (c = FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
buf ~= c;
|
|
else
|
|
std.utf.encode(buf, cast(dchar)c);
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
if (ferror(fps))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
|
|
char *lineptr = null;
|
|
size_t n = 0;
|
|
auto s = getdelim(&lineptr, &n, terminator, fps);
|
|
scope(exit) free(lineptr);
|
|
if (s < 0)
|
|
{
|
|
if (ferror(fps))
|
|
StdioException();
|
|
buf.length = 0; // end of file
|
|
return 0;
|
|
}
|
|
buf = buf.ptr[0 .. GC.sizeOf(buf.ptr)];
|
|
if (s <= buf.length)
|
|
{
|
|
buf.length = s;
|
|
buf[] = lineptr[0 .. s];
|
|
}
|
|
else
|
|
{
|
|
buf = lineptr[0 .. s].dup;
|
|
}
|
|
return s;
|
|
}
|
|
else version (GENERIC_IO)
|
|
{
|
|
FLOCK(fps);
|
|
scope(exit) FUNLOCK(fps);
|
|
auto fp = cast(_iobuf*)fps;
|
|
if (fwide(fps, 0) > 0)
|
|
{ /* Stream is in wide characters.
|
|
* Read them and convert to chars.
|
|
*/
|
|
version (Windows)
|
|
{
|
|
buf.length = 0;
|
|
int c2;
|
|
for (int c; (c = FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
{ buf ~= c;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (c >= 0xD800 && c <= 0xDBFF)
|
|
{
|
|
if ((c2 = FGETWC(fp)) != -1 ||
|
|
c2 < 0xDC00 && c2 > 0xDFFF)
|
|
{
|
|
StdioException("unpaired UTF-16 surrogate");
|
|
}
|
|
c = ((c - 0xD7C0) << 10) + (c2 - 0xDC00);
|
|
}
|
|
std.utf.encode(buf, c);
|
|
}
|
|
}
|
|
if (ferror(fp))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else version (Posix)
|
|
{
|
|
buf.length = 0;
|
|
for (int c; (c = FGETWC(fp)) != -1; )
|
|
{
|
|
if ((c & ~0x7F) == 0)
|
|
buf ~= c;
|
|
else
|
|
std.utf.encode(buf, cast(dchar)c);
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
if (ferror(fps))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
|
|
buf.length = 0;
|
|
for (int c; (c = FGETC(fp)) != -1; )
|
|
{
|
|
buf ~= c;
|
|
if (c == terminator)
|
|
break;
|
|
}
|
|
if (ferror(fps))
|
|
StdioException();
|
|
return buf.length;
|
|
}
|
|
else
|
|
{
|
|
static assert(0);
|
|
}
|
|
}
|
|
|