arsd.core
note: the api and behavior of this module is not externally stable at this time. See the documentation on specific functions for details.
Shared core functionality including exception helpers, library loader, event loop, and possibly more. Maybe command line processor and uda helper and some basic shared annotation types.
I'll probably move the url, websocket, and ssl stuff in here too as they are often shared. Maybe a small internationalization helper type (a hook for external implementation) and COM helpers too. I might move the process helpers out to their own module - even things in here are not considered stable to library users at this time!
If you use this directly outside the arsd library despite its current instability caveats, you might consider using static import since names in here are likely to clash with Phobos if you use them together. static import will let you easily disambiguate and avoid name conflict errors if I add more here. Some names even clash deliberately to remind me to avoid some antipatterns inside the arsd modules!
Contributor notes
arsd.core should be focused on things that enable interoperability primarily and secondarily increased code quality between other, otherwise independent arsd modules. As a foundational library, it is not permitted to import anything outside the druntime core namespace, except in templates and examples not normally compiled in. This keeps it independent and avoids transitive dependency spillover to end users while also keeping compile speeds fast. To help keep builds snappy, also avoid significant use of ctfe inside this module.
On my linux computer, dmd -unittest -main core.d takes about a quarter second to run. We do not want this to grow.
@safe compatibility is ok when it isn't too big of a hassle. @nogc is a non-goal. I might accept it on some of the trivial functions but if it means changing the logic in any way to support, you will need a compelling argument to justify it. The arsd libs are supposed to be reliable and easy to use. That said, of course, don't be unnecessarily wasteful - if you can easily provide a reliable and easy to use way to let advanced users do their thing without hurting the other cases, let's discuss it.
If functionality is not needed by multiple existing arsd modules, consider adding a new module instead of adding it to the core.
Unittests should generally be hidden behind a special version guard so they don't interfere with end user tests.
History
Types 70
Does math as a 64 bit number, but saturates at int.min and int.max when converting back to a 32 bit int.
History
ditto
ditto
private long valueNonOverflowingInt opBinary(string op)(long rhs)NonOverflowingInt opBinary(string op)(NonOverflowingInt rhs)NonOverflowingInt opUnary(string op)()NonOverflowingInt opOpAssign(string op)(long rhs)NonOverflowingInt opOpAssign(string op)(NonOverflowingInt rhs)int getValue() constthis(long v)A wrapper around a const(char)* to indicate that it is a zero-terminated C string.
private const(char) * rawconst(char)[] borrow() const @systemBorrows a slice of the pointer up to (but not including) the zero terminator.this(const(char) * raw)Wraps the given pointer in the struct. Note that it retains a copy of the pointer.A packed date/time/datetime representation added for use with LimitedVariant.
You should probably not use this much directly, it is mostly an internal storage representation.
private ulong packedDataubyte[12] daysInMonthTablestring toString() constPackedDateTime withDateAndTime(int year, int month, int day = 1, int hours = 0, int minutes = 0, int seconds = 0, int fractionalSeconds = 0) staticdittobool isLeapYear(int year) staticint daysInMonth(int year, int month) staticint daysInYear(int year) staticvoid setTime(int hours, int minutes, int seconds, int fractionalSeconds = 0)Sets the whole date and time portions in one function call.void setDate(int year, int month, int day)dittovoid clearTime()dittovoid clearDate()dittoint fractionalSeconds() constvoid fractionalSeconds(int a)dittovoid seconds(int a)dittovoid minutes(int a)dittovoid hours(int a)dittovoid day(int a)dittovoid month(int a)dittovoid year(int a)dittovoid hasTime(bool a)dittovoid hasDate(bool a)dittovoid setWithMask(int a, int bitOffset, int bitCount)long opCmp(PackedDateTime rhs) constA PackedInterval can be thought of as the difference between [PackedDateTime]s, similarly to how a [Duration] is a difference between [MonoTime]s or [SimplifiedUtcTimestamp]s.
The key speciality is in how it treats months and days separately. Months are not a consistent length, and neither are days when you consider daylight saving time. This thing assumes that if you add those, the month/day number will always increase, just the exact details since then might be truncated. (E.g., January 31st + 1 month = February 28/29 depending on leap year). If you multiply, the parts are done individually, so January 31st + 1 month * 2 = March 31st, despite + 1 month truncating to the shorter day in February.
Internally, this stores months and days as 16 bit signed shorts each, then the milliseconds is stored as a 32 bit signed int. It applies by first adding months, truncating days as needed, then adding days, then adding milliseconds.
If you iterate over intervals, be careful not to allow month truncation to change the result. (Jan 31st + 1 month) + 1 month will not actually give the same result as Jan 31st + 2 months. You want to add to the interval, then apply to the original date again, not to some accumulated date.
History
private ulong packedDatavoid months(int v)dittovoid days(int v)dittovoid milliseconds(int v)dittoPackedInterval opBinary(string op : "*")(int iterations) constthis(int months, int days = 0, int milliseconds = 0)Basically a Phobos SysTime but standing alone as a simple 64 bit integer (but wrapped) for compatibility with LimitedVariant.
long timestampstring toString() constSimplifiedUtcTimestamp fromUnixTime(time_t t) statictime_t toUnixTime() constlong stdTime() constSimplifiedUtcTimestamp opBinary(string op : "+")(Duration d) constthis(long hnsecTimestamp)A little builder pattern helper that is meant for use by other library code.
History
T valuesauto opDispatch(string name, Arg)(Arg value)Represents a generic raw element to be embedded in an interpolated sequence.
Use with caution, its exact meaning is dependent on the specific function being called, but it generally is meant to disable encoding protections the function normally provides.
History
string sthis(string s)A limited variant to hold just a few types. It is made for the use of packing a small amount of extra data into error messages and some transit across virtual function boundaries.
Note that empty strings and null values are indistinguishable unless you explicitly slice the end of some other existing string!
private const(void) * lengthprivate const(ubyte) * ptrconst(char)[] getString() const returngetString gets a reference to the string stored internally, which may be a temporary. See [toString] to get a normal string representation or whatever is inside.string toString() constthis(string s)Note for integral types that are not `int` and `long` (for example, `short` or `ubyte`), you might want to explicitly convert them to `int`.this(const(char) * stringz)dittothis(const(ubyte)[] b)dittothis(long l, int base = 10)dittothis(int i, int base = 10)dittothis(bool i)dittothis(double d)dittothis(Object o)dittothis(SimplifiedUtcTimestamp a)dittothis(PackedDateTime a)dittothis(DynamicDecimal a)dittoContainsvoid * eA DynamicDecimal is a fixed-point object whose precision is dynamically typed.
It packs everything into a 64 bit value. It uses one bit for sign, three bits for precision, then the rest of them for the value. This means the precision (that is, the number of digits after the decimal) can be from 0 to 7, and there can be a total of 18 digits.
Numbers can be added and subtracted only if they have matching precision. They can be multiplied and divided only by whole numbers.
History
private ulong storagebool isNegative()int precision()long value()typeof(this) opBinary(string op)(typeof(this) rhs) if(op == "+" || op == "-")Some basic arithmetic operators are defined on this: +, -, *, and /, but only between numbers of the same precision. Note that division always returns the quotient and remainder together in one ret...typeof(this) opBinary(string op)(int rhs) if(op == "*")dittoDivisionResult opBinary(string op)(int rhs) if(op == "/")dittotypeof(this) opUnary(string op : "-")()dittolong opCmp(typeof(this) rhs)dittodouble toFloatingPoint()Converts to a floating point type. There's potentially a loss of precision here.string toString(int minimumNumberOfDigitsLeftOfDecimal = 1) @systemDivisionResultdittoThis is a dummy type to indicate the end of normal arguments and the beginning of the file/line inferred args. It is meant to ensure you don't accidentally send a string that is interpreted as a filename when it was meant to be a normal argument to the function and trigger the wrong overload.
Basically a scope class you can return from a function or embed in another aggregate.
ubyte[__traits(classInstanceSize, Class)] rawDataAlternative for toStringz
History
See Also
private char[] bufferprivate char[128] staticBuffersize_t length()Length of the string, excluding the zero terminator.char * ptr()char[] slice()Returns the slice of the internal buffer, excluding the zero terminator (though there is one present right off the end of the slice). You must assume its lifetime is less than that of the CharzBuffer.ditto
private ubyte padToprivate char padWithprivate ubyte radixprivate char tenprivate ubyte groupSizeprivate char separatorIntToStringArgs withPadding(int padTo, char padWith = '0')IntToStringArgs withRadix(int radix, char ten = 'a')IntToStringArgs withGroupSeparator(int groupSize, char separator = '_')private ubyte padToprivate char padWithprivate ubyte groupSizeprivate char separatorprivate ubyte minimumPrecisionprivate ubyte maximumPrecisionprivate bool useScientificNotationFloatToStringArgs withPadding(int padTo, char padWith = '0')FloatToStringArgs withGroupSeparator(int groupSize, char separator = '_')FloatToStringArgs withPrecision(int minDigits, int maxDigits = 0)FloatToStringArgs withScientificNotation(bool enabled)ditto
bool thereDeclares a delegate property with several setters to allow for handlers that don't care about the arguments.
Throughout the arsd library, you will often see types of these to indicate that you can set listeners with or without arguments. If you care about the details of the callback event, you can set a delegate that declares them. And if you don't, you can set one that doesn't even declare them and it will be ignored.
Base class representing my exceptions. You should almost never work with this directly, but you might catch it as a generic thing. Catch it before generic object.Exception or object.Throwable in any catch chains.
General guidelines for exceptions
The purpose of an exception is to cancel a task that has proven to be impossible and give the programmer enough information to use at a higher level to decide what to do about it.
Cancelling a task is accomplished with the throw keyword. The transmission of information to a higher level is done by the language runtime. The decision point is marked by the catch keyword. The part missing - the job of the Exception class you construct and throw - is to gather the information that will be useful at a later decision point.
It is thus important that you gather as much useful information as possible and keep it in a way that the code catching the exception can still interpret it when constructing an exception. Other concerns are secondary to this to this primary goal.
With this in mind, here's some guidelines for exception handling in arsd code.
Allocations and lifetimes
Don't get clever with exception allocations. You don't know what the catcher is going to do with an exception and you don't want the error handling scheme to introduce its own tricky bugs. Remember, an exception object's first job is to deliver useful information up the call chain in a way this code can use it. You don't know what this code is or what it is going to do.
Keep your memory management schemes simple and let the garbage collector do its job.
- All thrown exceptions should be allocated with the
newkeyword. - Members inside the exception should be value types or have infinite lifetime (that is, be GC managed).
- While this document is concerned with throwing, you might want to add additional information to an in-flight exception, and this is done by catching, so you need to know how that works too, and there is a global compiler switch that can change things, so even inside arsd we can't completely avoid its implications.
DIP1008's presence complicates things a bit on the catch side - if you catch an exception and return it from a function, remember to ex.refcount = ex.refcount + 1; so you don't introduce more use-after-free woes for those unfortunate souls.
Error strings
Strings can deliver useful information to people reading the message, but are often suboptimal for delivering useful information to other chunks of code. Remember, an exception's first job is to be caught by another block of code. Printing to users is a last resort; even if you want a user-readable error message, an exception is not the ideal way to deliver one since it is constructed in the guts of a failed task, without the higher level context of what the user was actually trying to do. User error messages ought to be made from information in the exception, combined with higher level knowledge. This is best done in a catch block, not a throw statement.
As such, I recommend that you:
- Don't concatenate error strings at the throw site. Instead, pass the data you would have used to build the string as actual data to the constructor. This lets catchers see the original data without having to try to extract it from a string. For unique data, you will likely need a unique exception type. More on this in the next section.
- Don't construct error strings in a constructor either, for the same reason. Pass the useful data up the call chain, as exception members, to the maximum extent possible. Exception: if you are passed some data with a temporary lifetime that is important enough to pass up the chain. You may
.iduporto!stringto preserve as much data as you can before it is lost, but still store it in a separate member of the Exception subclass object. - Do construct strings out of public members in [getAdditionalPrintableInformation]. When this is called, the user has requested as much relevant information as reasonable in string format. Still, avoid concatenation - it lets you pass as many key/value pairs as you like to the caller. They can concatenate as needed. However, note the words "public members" - everything you do in
getAdditionalPrintableInformationought to also be possible for code that caught your exception via your public methods and properties.
Subclasses
Any exception with unique data types should be a unique class. Whenever practical, this should be one you write and document at the top-level of a module. But I know we get lazy - me too - and this is why in standard D we'd often fall back to throw new Exception("some string " ~ some info). To help resist these urges, I offer some helper functions to use instead that better achieve the key goal of exceptions - passing structured data up a call chain - while still being convenient to write.
See: [ArsdException], [Win32Enforce]
void toString(scope void delegate(in char[]) sink) constThe toString method will print out several components:string toString()dittovoid getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) constUsers might like to see additional information with the exception. API consumers should pull this out of properties on your child class, but the parent class might not be able to deal with the arbi...string printableExceptionName() constThis is the name of the exception class, suitable for printing. This should be static data (e.g. a string literal). Override it in subclasses.const(char)[] message() constthis(string operation, string file = __FILE__, size_t line = __LINE__, Throwable next = null)Don't call this except from other exceptions; this is essentially an abstract class.InvalidArgument[] invalidArgumentsvoid getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) constthis(InvalidArgument[] invalidArguments, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)this(string argumentName, string argumentDescription, LimitedVariant givenArgumentValue = LimitedVariant.init, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)this(string argumentName, string argumentDescription, string functionName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)InvalidArgumentBase class for when you've requested a feature that is not available. It may not be available because it is possible, but not yet implemented, or it might be because it is impossible on your operating system.
this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)This means the feature could be done, but I haven't gotten around to implementing it yet. If you email me, I might be able to add it somewhat quickly and get back to you.
this(string featureName = __PRETTY_FUNCTION__, string file = __FILE__, size_t line = __LINE__, Throwable next = null)This means the feature is not supported by your current operating system. You might be able to get it in an update, but you might just have to find an alternate way of doing things.
this(string featureName, string file = __FILE__, size_t line = __LINE__, Throwable next = null)A tagged union that holds an error code from system apis, meaning one from Windows GetLastError() or C's errno.
You construct it with SystemErrorCode(thing) and the overloaded constructor tags and stores it.
string errorString() const @trustedA text explanation of the code. See [codeAsString] for a string representation of the numeric representation.this(int errno)C/unix error are typed as signed ints... Windows' errors are typed DWORD, aka unsigned...this(uint win32)dittoTypevoid getAdditionalPrintableInformation(scope void delegate(string name, in char[] value) sink) constthis(string msg, int originalErrorNo, scope SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null)this(string msg, SystemErrorCode code, SavedArgument[] args = null, string file = __FILE__, size_t line = __LINE__, Throwable next = null)The low level use of this would look like throw new WindowsApiException("MsgWaitForMultipleObjectsEx", GetLastError()) but it is meant to be used from higher level things like [Win32Enforce].
History
History
You can also pass a handle to a specific thread, if you have one.
int function() getTimeoutprivate bool exitApplicationRequestedvoid run(scope bool delegate() until)Runs the event loop for this thread until the `until` delegate returns `true`.void exitApplication() staticRunOnceResult runOnce(Duration timeout = Duration.max)Runs a single iteration of the event loop for this thread. It will return when the first thing happens, but that thing might be totally uninteresting to anyone, or it might trigger significant work...UnregisterToken addDelegateOnLoopIteration(void delegate() dg, uint timingFlags)Adds a delegate to be called on each loop iteration, called based on the `timingFlags`.UnregisterToken addDelegateOnLoopIteration(void function() dg, uint timingFlags)RunOnceResultReturns details from a call to [runOnce]. Use the named methods here for details, or it can be used in a `while` loop directly thanks to its `opCast` automatic conversion to `bool`.The internal types that will be exposed through other api things.
This represents a file. Technically, file paths aren't actually strings (for example, on Linux, they need not be valid utf-8, while a D string is supposed to be), even though we almost always use them like that.
This type is meant to represent a filename / path. I might not keep it around.
private string pathbool isNull() constbool opCast(T: bool)() conststring toString() constFilePath makeAbsolute(FilePath base, TreatAsWindowsPath treatAsWindowsPath = TreatAsWindowsPath.guess) constvoid foreachPathComponent(scope bool delegate(size_t index, in char[] component) dg) constbool isUNC() conststring driveName() conststring filename()Returns the path with the directory cut off.string directoryName()Returns the path with the filename cut off.string extension()Returns the file extension, if present, including the last dot.string contentTypeFromFileExtension()Guesses the media (aka mime) content type from the file extension for this path.this(string path)ditto
Specialization of string to indicate it is a URI. You should generally use arsd.uri.Uri instead of this in user code.
History
string uristring toString()Shared base code for web socket client in arsd.http2 and server in arsd.cgi.
History
int readyState_ubyte[] receiveBuffersize_t receiveBufferUsedLengthConfig config CONNECTINGSocket has been created. The connection is not yet open. OPENThe connection is open and ready to communicate. CLOSINGThe connection is in the process of closing. CLOSEDThe connection is closed or couldn't be opened.bool closeCalledubyte continuingTypeubyte[] continuingDataarsd.core.FlexibleDelegate!(void delegate(CloseEvent event)) oncloseThe `CloseEvent` you get references a temporary buffer that may be overwritten after your handler returns. If you want to keep it or the `event.reason` member, remember to `.idup` it.void delegate() onerrorvoid delegate(in char[]) ontextmessagevoid delegate(in ubyte[]) onbinarymessagevoid delegate() onopenbool isClientint readyState()Returns one of [CONNECTING], [OPEN], [CLOSING], or [CLOSED].void close()Closes the connection, sending a graceful teardown message to the other side. If you provide no arguments, it sends code 1000, normal closure. If you provide a code, you should also provide a short...void close(int code, string reason)dittovoid close(int code)void ping(in ubyte[] data = null)Sends a ping message to the server. This is done automatically by the library if you set a non-zero [Config.pingFrequency], but you can also send extra pings explicitly as well with this function. ...void pong(in ubyte[] data = null)Sends a pong message to the server. This is normally done automatically in response to pings. Group: foundationalvoid send(in char[] textData)Sends a text message through the websocket. Group: foundationalvoid send(in ubyte[] binaryData)Sends a binary message through the websocket. Group: foundationalWebSocketFrame waitForNextMessage()Waits for and returns the next complete message on the socket.bool waitForNextMessageWouldBlock()Tells if [waitForNextMessage] would block. Group: blocking_apibool isMessageBuffered()Is there a message in the buffer already? If `true`, [waitForNextMessage] is guaranteed to return immediately. If `false`, check [isDataPending] as the next step. Group: blocking_apivoid onmessage(void delegate(in char[]) dg)Group: browser_apivoid onmessage(void delegate(in ubyte[]) dg)dittovoid llsend(ubyte[] d)ubyte getRandomByte() @trustedvoid llclose()void llshutdown()bool lowLevelReceive()bool isDataPending(Duration timeout = 0.seconds)void unregisterAsActiveSocket()ConfigGroup: foundationalCloseEventArguments for the close event. The `code` and `reason` are provided from the close message on the websocket, if they are present. The spec says code 1000 indicates a normal, default reason close, b...bool populatedbool finbool rsv1bool rsv2bool rsv3WebSocketOpcode opcodebool maskedubyte lengthIndicatorulong realLengthubyte[4] maskingKeyubyte[] dataWebSocketFrame simpleMessage(WebSocketOpcode opcode, in void[] data) staticvoid send(scope void delegate(ubyte[]) llsend, scope ubyte delegate() getRandomByte)WebSocketFrame read(ref ubyte[] d) staticvoid unmaskInPlace()char[] textData()Represents a generic async, waitable request.
void start()Actually issues the request, starting the operation.void cancel()Cancels the request. This will cause `isComplete` to return true once the cancellation has been processed, but [AsyncOperationResponse.wasSuccessful] will return `false` (unless it completed before...bool isComplete()Returns `true` if the operation has been completed. It may be completed successfully, cancelled, or have errored out - to check this, call [waitForCompletion] and check the members on the response ...AsyncOperationResponse waitForCompletion()Waits until the request has completed - successfully or otherwise - and returns the response object. It will run an ad-hoc event loop that may call other callbacks while waiting.bool wasSuccessful()Returns true if the request completed successfully, finishing what it was supposed to.ditto
AsyncOperationRequest[] requeststhis(AsyncOperationRequest[] requests)Some functions that return arrays allow you to provide your own buffer. These are indicated in the type system as UserProvidedBuffer!Type, and you get to decide what you want to happen if the buffer is too small via the [OnOutOfSpace] parameter.
These are usually optional, since an empty user provided buffer with the default policy of reallocate will also work fine for whatever needs to be returned, thanks to the garbage collector taking care of it for you.
The API inside UserProvidedBuffer is all private to the arsd library implementation; your job is just to provide the buffer to it with provideBuffer or a constructor call and decide on your on-out-of-space policy.
properly size a buffer, I suggest looking at what covers about 80% of cases. Trying to cover everything often leads to wasted buffer space, and if you use a reallocate policy it can cover the rest. You might be surprised how far just two elements can go!
History
this(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate)Possible policies for [UserProvidedBuffer]s that run out of space.
ditto
PARTIALLY IMPLEMENTED / NOT STABLE
private FilePath pathprivate string globPatternprivate bool recursiveprivate void delegate(FilePath filename, ChangeOperation op) changeHandlerthis(FilePath directoryToWatch, string globPattern, bool recursive, void delegate(FilePath pathModified, ChangeOperation op) dg)Watches a directory and its contents. If the `globPattern` is `null`, it will not attempt to add child items but also will not filter it, meaning you will be left with platform-specific behavior.ChangeOperationThis starts up a local pipe. If it is already claimed, it just communicates with the existing one through the interface.
bool wasSuccessful()this(SystemErrorCode errorCode, const(ubyte)[] bufferWritten)bool wasSuccessful()this(SystemErrorCode errorCode, const(ubyte)[] bufferRead)this()private LoopIterationDelegate[] loopIterationDelegatesvoid runLoopIterationDelegates(bool isAfter)UnregisterToken addDelegateOnLoopIteration(void delegate() dg, uint timingFlags)void unregisterDg(void delegate() dg)void setTimeout()void addFileOrDirectoryChangeListener(FilePath name, uint flags, bool recursive = false)LoopIterationDelegateThis is primarily a helper for the event queues. It is public in the hope it might be useful, but subject to change without notice; I will treat breaking it the same as if it is private. (That said, it is a simple little utility that does its job, so it is unlikely to change much. The biggest change would probably be letting it grow and changing from inline to dynamic array.)
It is a fixed-size ring buffer that synchronizes on a given object you give it in the constructor.
After enqueuing something, you should probably set an event to notify the other threads. This is left as an exercise to you (or another wrapper).
private T[maxSize] ringprivate int frontprivate int backprivate SynchronizableObject synchronizedOnbool isEmpty()Note the potential race condition between calling this and actually dequeuing something. You might want to acquire the lock on the object before calling this (nested synchronized things are allowed...bool isFull()Note the potential race condition between calling this and actually queuing something.bool enqueue(T what)If this returns true, you should signal listening threads (with an event or a semaphore, depending on how you dequeue it). If it returns false, the queue was full and your thing was NOT added. You ...T dequeue()If you are using a semaphore to signal, you can call this once for each count of it and you can do that separately from this call (though they should be paired).T[] dequeueSeveral(return T[] buffer, scope void delegate() runInsideLockIfEmpty = null)Note that if you use a semaphore to signal waiting threads, you should probably not call this.A class to help write a stream of binary data to some target.
NOT YET FUNCTIONAL
private ubyte[] bufferprivate int bufferPositionprivate bool isClosed_void put(T)(T value, ByteOrder byteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__)void put(T : E[], E)(T value, ByteOrder elementByteOrder = ByteOrder.irrelevant, string file = __FILE__, size_t line = __LINE__)dittovoid close()Performs a final flush() call, then marks the stream as closed, meaning no further data will be written to it.void flush()Writes what is currently in the buffer to the target and waits for the target to accept it. Please note: if you are subclassing this to go to a different targetbool isClosed()Returns true if either you closed it or if the receiving end closed their side, indicating they don't want any more data.final writeOneByte(ubyte value)UNSTABLE, NOT FULLY IMPLEMENTED. DO NOT USE YET.
You might use this like:
auto proc = new ExternalProcess();
auto stdoutStream = new ReadableStream();
// to use a stream you can make one and have a task consume it
runTask({
while(!stdoutStream.isClosed) {
auto line = stdoutStream.get!string(e => e == '\n');
}
});
// then make the process feed into the stream
proc.onStdoutAvailable = (got) {
stdoutStream.feedData(got); // send it to the stream for processing
stdout.rawWrite(got); // forward it through to our own thing
// could also append it to a buffer to return it on complete
};
proc.start();Please note that this does not currently and I have no plans as of this writing to add support for any kind of direct file descriptor passing. It always pipes them back to the parent for processing. If you don't want this, call the lower level functions yourself; the reason this class is here is to aid integration in the arsd.core event loop. Of course, I might change my mind on this.
private AsyncFile _stdinprivate AsyncFile _stdoutprivate AsyncFile _stderrprivate bool coredumpedprivate bool stoppedprivate bool completedprivate int _statusvoid delegate(ExternalProcess) oncompletevoid start()void markComplete(int status)void markTerminatedBySignal(int signal, bool coredumped)void markStoppedBySignal(int signal)void markContinued()void waitForCompletion()void waitForChange()History: Added November 23, 2025bool isComplete()bool isStopped()History: Added November 23, 2025bool leftCoreDump()History: Added November 23, 2025int status()DO NOT USE THIS YET IT IS NOT FUNCTIONAL NOR STABLE
The arsd.core logger works differently than many in that it works as a ring buffer of objects that are consumed (or missed; buffer overruns are possible) by a different thread instead of strings written to some file.
A library (or an application) defines a log source. They write to this source.
Applications then define log listeners, zero or more, which reads from various sources and does something with them.
Log calls, in this sense, are quite similar to asynchronous events that can be subscribed to by event handlers. The difference is events are generally not dropped - they might coalesce but are usually not just plain dropped in a buffer overrun - whereas logs can be. If the log consumer can't keep up, the details are just lost. The log producer will not wait for the consumer to catch up.
An application can also set a default subscriber which applies to all log objects throughout.
All log message objects must be capable of being converted to strings and to json.
Ad-hoc messages can be done with interpolated sequences.
Messages automatically get a timestamp. They can also have file/line and maybe even a call stack.
Examples
auto logger = new shared LoggerOf!GenericEmbeddableInterpolatedSequence;
mylogger.info(i"$this heartbeat");History
Not actually implemented until February 6, 2025, when it changed from mixin template to class.
private LoggedMessage!T[bufferSize] ringprivate ulong writeBufferPositionprivate Mutex mutexprivate Condition conditionprivate bool activeprivate int listenerCountvoid close() sharedCloses the log channel and waits for all listeners to finish pending work before returning.LogListenerController addListener(void delegate(LoggedMessage!T message, int missedMessages) dg) sharedExamples:void log(LoggedMessage!T message) sharedvoid log(LogLevel level, T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shareddittovoid info(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shareddittovoid trace(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shareddittovoid warn(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shareddittovoid error(T message, SourceLocation sourceLocation = SourceLocation(__MODULE__, __LINE__)) shareddittothis()ditto
void disconnect()Disconnects from the log producer as soon as possible, possibly leaving messages behind in the log buffer. Once disconnected, the log listener will terminate asynchronously and cannot be reused. Us...void waitForCompletion()Waits for the listener to finish its pending work and terminate. You should call [disconnect] first to make it start to exit.ditto
string moduleNamesize_t lineditto
LogLevel levelSourceLocation sourceLocationSimplifiedUtcTimestamp timestampThread originatingThreadT messagestring toString()ditto
Represents a translatable string.
This depends on interpolated expression sequences to be ergonomic to use and in most cases, a function that uses this should take it as tstring name...; a typesafe variadic (this is also why it is a class rather than a struct - D only supports this particular feature on classes).
You can use null as a tstring. You can also construct it with UFCS: i"foo".tstring.
The actual translation engine should be set on the application level.
It is possible to get all translatable string templates compiled into the application at runtime.
History
private GenericEmbeddableInterpolatedSequence geisprivate shared(TranslationEngine) translationEngine_TranslatableElement * firstprivate templateStringForstring toString()tstring opCall(Args...)(InterpolationHeader hdr, Args args, InterpolationFooter ftr) staticshared(TranslationEngine) translationEngine() staticvoid translationEngine(shared TranslationEngine e) staticthis(InterpolationHeader hdr, Args args, InterpolationFooter ftr)For a case where there is no plural specialization.TranslatableElementtstringTemplateProcessorditto
string translate(GenericEmbeddableInterpolatedSequence geis) sharedvoid handleTemplate(tstring.TranslatableElement t) sharedIf the translation engine has been set early in the module construction process (which it should be!)Element[8] seqstring toString()this(InterpolationHeader, Args args, InterpolationFooter)Elementditto
ValueWithFormattingArgs opBinary(string op)(double value)ValueWithFormattingArgs opOpAssign(string op)(double value)ValueWithFormattingArgs opUnary(string op)()Functions 68
T reinterpretCast(T, V)(return ref V value) ref @systemTreats the memory of one variable as if it is the type of another variable.bool isSliceOf(T1, T2)(scope const(T1)[] needle, scope const(T2)[] haystack) @trusted pure nothrow @nogcDetermines whether `needle` is a slice of `haystack`.int countOfBitsSet(ulong v)Counts the number of bits set to `1` in a value, using intrinsics when available.void filenameStringSort(string[] names)Sorts the list of names according to how I like to sort filenames.T[] mallocSlice(T)(size_t n)A trivial wrapper around C's malloc that creates a D slice. It multiples n by T.sizeof and returns the slice of the pointer from 0 to n.char[] mallocedStringz(in char[] original)Uses C's malloc to allocate a copy of `original` with an attached zero terminator. It may return a slice with a `null` pointer (but non-zero length!) if `malloc` fails and you are responsible for f...int findIndexOfZero(in wchar[] str)int findIndexOfZero(in char[] str)char[] intToString(long value, char[] buffer, IntToStringArgs args = IntToStringArgs.init)An int printing function that doesn't need to import Phobos. Can do some of the things std.conv.to and std.format.format do.char[] floatToString(double value, char[] buffer, FloatToStringArgs args = FloatToStringArgs.init)inout(char)[] stripInternal(return inout(char)[] s) nothrow @safe @nogc pureHistory: Moved from color.d to core.d in March 2023 (dub v11.0).string toStringInternal(T)(T t)Shortcut for converting some types to string without invoking Phobos (but it may as a last resort).string flagsToString(Flags)(ulong value)char[] transcodeUtf(scope const wchar[] input, char[] buffer)If it fits in the provided buffer, it will use it, otherwise, it will reallocate as-needed with the append operator.inout(char)[] transcodeUtf(inout(char)[] input)string encodeUriComponent(string s)string decodeUriComponent(string s, bool translatePlusToSpace = false)auto toDelegate(T)(T t)void arsd_core_init(int numberOfWorkers = 0)Initializes the arsd core event loop and creates its worker threads. You don't actually have to call this, since the first use of an arsd.core function that requires it will call it implicitly, but...ICoreEventLoop getThisThreadEventLoop(EventLoopType type = EventLoopType.AdHoc)Get the event loop associated with this threadinout(char)[] trimSlashesRight(inout(char)[] txt)AsyncOperationRequest waitForFirstToComplete(AsyncOperationRequest[] requests...)It returns the request so you can identify it more easily. `request.waitForCompletion()` is guaranteed to return the response without any actual wait, since it is already complete when this functio...AsTheyCompleteRange asTheyComplete(AsyncOperationRequest[] requests...)Waits for all the `requests` to complete, giving each one through the range interface as it completes.void writeFile(string filename, const(void)[] contents)Reads or writes a file in one call. It might internally yield, but is generally blocking if it returns values. The callback ones depend on the implementation.UserProvidedBuffer!T provideBuffer(T)(scope T[] buffer, OnOutOfSpace policy = OnOutOfSpace.reallocate)dittobool matchesFilePattern(scope const(char)[] name, scope const(char)[] pattern, char star = '*')This is currently a simplified glob where only the wildcard in the first or last position gets special treatment or a single in the middle.int indexOf(scope const(char)[] haystack, scope const(char)[] needle)int indexOf(scope const(ubyte)[] haystack, scope const(char)[] needle)bool endsWith(scope const(char)[] haystack, scope const(char)[] needle)bool startsWith(scope const(char)[] haystack, scope const(char)[] needle)bool postMessage(ThreadToRunIn destination, void delegate() code)bool postMessage(ThreadToRunIn destination, Object message)shared(LoggerOf!GenericEmbeddableInterpolatedSequence) logger()void dumpParamsImpl(T...)(string func, T args)Don't call this directly, use `mixin(dumpParams);` insteadvoid writeln(T...)(T t)A `writeln` (and friends) that actually works, at least for some basic types.ValueWithFormattingArgs!double formatArgs(double value, FloatToStringArgs args)ValueWithFormattingArgs!double formatArgs(double value, int precision)string enumNameForValue(T)(T t)const(char)[] writeGuts(T...)(scope char[] buffer, string prefix, string suffix, string argSeparator, bool printInterpolatedCode, bool quoteStrings, string function(scope char[] result) writer, T t)string getEnvironmentVariable(scope const(char)[] name)As the C function it calls, this is not thread safe.void setEnvironmentVariable(scope const(char)[] name, scope const(char)[] value)As the C function it calls, this is not thread safe. Always overwrites existing values.Variables 9
char_invalid = '\xFF'wchar_invalid = '\uFFFF'dchar_invalid = '\U0000FFFF'replacementDchar = '\uFFFD'CleanupQueue cleanupQueueint eventLoopRound = - 1void delegate(Throwable t) taskUncaughtExceptiondumpParams = q{
{
import arsd.core;
arsd.core.dumpParamsImpl(__FUNCTION__, __traits(parameters));
}
}You can use mixin(dumpParams); to put out a debug print of your current function call w/ params.
Templates 5
This populates a struct from a list of values (or other expressions, but it only looks at the values) based on types of the members, with one exception: bool members.. maybe.
It is intended for collecting a record of relevant UDAs off a symbol in a single call like this:
struct Name {
string n;
}
struct Validator {
string regex;
}
struct FormInfo {
Name name;
Validator validator;
}
@Name("foo") @Validator(".*")
void foo() {}
auto info = populateFromUdas!(FormInfo, __traits(getAttributes, foo));
assert(info.name == Name("foo"));
assert(info.validator == Validator(".*"));Note that instead of UDAs, you can also pass a variadic argument list and get the same result, but the function is populateFromArgs and you pass them as the runtime list to bypass "args cannot be evaluated at compile time" errors:
void foo(T...)(T t) {
auto info = populateFromArgs!(FormInfo)(t);
// assuming the call below
assert(info.name == Name("foo"));
assert(info.validator == Validator(".*"));
}
foo(Name("foo"), Validator(".*"));The benefit of this over constructing the struct directly is that the arguments can be reordered or missing. Its value is diminished with named arguments in the language.
Given a function and a parameter index, returns a function that only takes that parameter and returns it. If called with zero arguments, it returns the default value, if it exists. If not, you'll get a compiler error.
Note that it is a function because function parameters in D can have function calls, including side effects, as a default argument.
firstDefaultParam returns the index of the first defaultParam for the given function fn that can be called without an argument.
On older compilers, firstDefaultParam may always be parameters.length, since defaultParam requires a relatively new language feature (__traits(parameters), introduced in upstream D 2.099.0, released March 6, 2022).
History
This is a generic exception with attached arguments. It is used when I had to throw something but didn't want to write a new class.
You can catch an ArsdException to get its passed arguments out.
You can pass either a base class or a string as Type.
See the examples for how to use it.
Calls the C API function fn. If it returns an error value, it throws an [ErrnoApiException] (or subclass) after getting errno.