scriptlike 0.10.2

Utility library to help you write script-like programs.

To use this package, put the following dependency into your project's dependencies section:


Scriptlike Build Status

Scriptlike is a utility library to help you write script-like programs in the D Programming Language.

Officially supported compiler versions are shown in .travis.yml.




[Disambiguating write and write](#disambiguating-write-and-write)


Automatic Phobos Import

For most typical Phobos modules. Unless you don't want to. Who needs rows and rows of standard lib imports for a mere script?

import scriptlike;
//import scriptlike.only; // In case you don't want Phobos auto-imported
void main() {

See: `scriptlike`, `scriptlike.only`, `scriptlike.std`

User Input Prompts

Easy prompting for and verifying command-line user input with the `interact` module:

auto name = userInput!string("Please enter your name");
auto age = userInput!int("And your age");

if(userInput!bool("Do you want to continue?"))
	string outputFolder = pathLocation("Where you do want to place the output?");
	auto color = menu!string("What color would you like to use?", ["Blue", "Green"]);

auto num = require!(int, "a > 0 && a <= 10")("Enter a number from 1 to 10");

pause(); // Prompt "Press Enter to continue...";
pause("Hit Enter again, dood!!");

See: `userInput`, `pathLocation`, `menu`, `require`, `pause`

String Interpolation

Variable (and expression) expansion inside strings:

// Output: The number 21 doubled is 42!
int num = 21;
writeln( mixin(interp!"The number ${num} doubled is ${num * 2}!") );

// Output: Empty braces output nothing.
writeln( mixin(interp!"Empty ${}braces ${}output nothing.") );

// Output: Multiple params: John Doe.
auto first = "John", last = "Doe";
writeln( mixin(interp!`Multiple params: ${first, " ", last}.`) );

See: `interp`


Simple, reliable, cross-platform. No more worrying about slashes, paths-with-spaces, buildPath, normalizing, or getting paths mixed up with ordinary strings:

// This is AUTOMATICALLY kept normalized (via std.path.buildNormalizedPath)
auto dir = Path("foo/bar");
dir ~= "subdir"; // Append a subdirectory

// No worries about trailing slashes!
assert(Path("foo/bar") == Path("foo/bar/"));
assert(Path("foo/bar/") == Path("foo/bar//"));

// No worries about forward/backslashes!
assert(dir == Path("foo/bar/subdir"));
assert(dir == Path("foo\\bar\\subdir"));

// No worries about spaces!
auto file = dir.up ~ "different subdir\\Filename with spaces.txt";
assert(file == Path("foo/bar/different subdir/Filename with spaces.txt"));
writeln(file); // Path.toString() always properly escapes for current platform!
writeln(file.toRawString()); // Don't escape!

// Even file extentions are type-safe!
Ext ext = file.extension;
auto anotherFile = Path("path/to/file") ~ ext;
assert(anotherFile.baseName == Path("file.txt"));

// std.path and std.file are wrapped to offer Path/Ext support
assert(dirName(anotherFile) == Path("path/to"));
copy(anotherFile, Path("target/path/new file.txt"));

See: `Path`, `Path.toString`, `Path.toRawString`, `Path.up`, `Ext`, `dirName`, `copy`, `buildNormalizedPath`

Try/As Filesystem Operations

Less pedantic, when you don't care if there's nothing to do:

// Just MAKE SURE this exists! If it's already there, then GREAT!
assertThrown( mkdir("somedir") ); // Exception: Already exists!
tryMkdir("somedir"); // Works fine!

// Just MAKE SURE this is gone! If it's already gone, then GREAT!
assertThrown( rmdir("somedir") ); // Exception: Already gone!
tryRmdir("somedir"); // Works fine!

// Just MAKE SURE it doesn't exist. Don't bother me if it doesn't!

// Copy if it exists, otherwise don't worry about it.
tryCopy("file", "file-copy");

// Is this a directory? If it doesn't even exist,
// then it's obviously NOT a directory.
assertThrown( isDir("foo/bar") ); // Exception: Doesn't exist!
if(existsAsDir("foo/bar")) // Works fine!
	{/+ stuff... +/}

// Bonus! Single function to delete files OR directories!
writeFile("file.txt", "abc");
writeFile("foo/bar/dir/file.txt", "123");
// Delete with the same function!
removePath("file.txt"); // Calls 'remove'
removePath("foo");      // Calls 'rmdirRecurse'
tryRemovePath("file.txt"); // Also comes in try flavor!

See: `tryMkdir`, `mkdir`, `tryMkdirRecurse`, `mkdir`, `tryRmdir`, `rmdir`, `tryRemove`, `tryCopy`, `existsAsDir`, `removePath`, `tryRemovePath`, `writeFile` and more...

Script-Style Shell Commands

Invoke a command script-style: synchronously with forwarded stdout/in/err from any working directory. Or capture the output instead. Automatically throw on non-zero status code if you want.

One simple call, `run`, to run a shell command script-style (ie, synchronously with forwarded stdout/in/err) from any working directory, and automatically throw if it fails. Or `runCollect` to capture the output instead of displaying it. Or `tryRun`/`tryRunCollect` if you want to receive the status code instead of automatically throwing on non-zero.

run("dmd --help"); // Display DMD help screen
pause(); // Wait for user to hit Enter

// Automatically throws ErrorLevelException(1, "dmd --bad-flag")
assertThrown!ErrorLevelException( run("dmd --bad-flag") );

// Automatically throws ErrorLevelException(-1, "this-cmd-does-not-exist")
assertThrown!ErrorLevelException( run("this-cmd-does-not-exist") );

// Don't bail on error
int statusCode = tryRun("dmd --bad-flag");

// Collect output instead of showing it
string dmdHelp = runCollect("dmd --help");
auto isDMD_2_068_1 = dmdHelp.canFind("D Compiler v2.068.1");

// Don't bail on error
auto result = tryRunCollect("dmd --help");
if(result.status == 0 && result.output.canFind("D Compiler v2.068.1"))
	writeln("Found DMD v2.068.1!");

// Use any working directory:
auto myProjectDir = Path("my/proj/dir");
auto mainFile = Path("src/main.d");"dmd ", mainFile, " -O")); // mainFile is properly escaped!

// Verify it actually IS running from a different working directory:
version(Posix)        enum pwd = "pwd";
else version(Windows) enum pwd = "cd";
else static assert(0);
auto output = myProjectDir.runCollect(pwd);
auto expected = getcwd() ~ myProjectDir;
assert( Path(output.strip()) == expected );

See: `run`, `tryRun`, `runCollect`, `tryRunCollect`, `pause`, `Path`, `getcwd`, `ErrorLevelException`, `assertThrown`, `canFind`, `text`, `strip`

Command Echoing

Optionally enable automatic command echoing (including shell commands, changing/creating directories and deleting/copying/moving/linking/renaming both directories and files) by setting one simple flag: `bool scriptlikeEcho`

Echoing can be customized via `scriptlikeCustomEcho`.

run: echo Hello > file.txt
mkdirRecurse: some/new/dir
copy: file.txt -> 'some/new/dir/target name.txt'
Gonna run foo() now...
foo: i = 42

scriptlikeEcho = true; // Enable automatic echoing

run("echo Hello > file.txt");

auto newDir = Path("some/new/dir");
mkdirRecurse(newDir.toRawString()); // Even works with non-Path overloads
copy("file.txt", newDir ~ "target name.txt");

void foo(int i = 42) {
	yapFunc("i = ", i); // Evaluated lazily

// yap and yapFunc ONLY output when echoing is enabled
yap("Gonna run foo() now...");

See: `scriptlikeEcho`, `yap`, `yapFunc`, `run`, `Path`, `Path.toRawString`, `mkdirRecurse`, `copy`

Dry Run Assistance

Scriptlike can help you create a dry-run mode, by automatically echoing (even if `scriptlikeEcho` is disabled) and disabling all functions that launch external commands or modify the filesystem. Just enable the `scriptlikeDryRun` flag.

Note, if you choose to use this, you still must ensure your program logic behaves sanely in dry-run mode.

scriptlikeDryRun = true;

// When dry-run is enabled, this echoes but doesn't actually copy or invoke DMD.
copy("original.d", "app.d");
run("dmd app.d -ofbin/app");

// Works fine in dry-run, since it doesn't modify the filesystem.
bool isItThere = exists("another-file");

	// This won't work right if we're running in dry-run mode,
	// since it'll be out-of-date, if it even exists at all.
	auto source = read("app.d");

See: `scriptlikeDryRun`, `copy`, `run`, `exists`, `read`


Single function to bail out with an error message, exception-safe.

$ test
test: ERROR: Need two args, not 0!
$ test abc 123
test: ERROR: First arg must be 'foobar', not 'abc'!

import scriptlike;

void main(string[] args) {

// Throws a Fail exception on bad args:
void helper(string[] args) {
	// Like std.exception.enforce, but bails with no ugly stack trace,
	// and if uncaught, outputs the program name and "ERROR: "
	failEnforce(args.length == 3, "Need two args, not ", args.length-1, "!");

	if(args[1] != "foobar")
		fail("First arg must be 'foobar', not '", args[1], "'!");

See: `fail`, `failEnforce`, `Fail`

Disambiguating write and write

Since they're both imported by default, you may get symbol conflict errors when trying to use `scriptlike.file.wrappers.write` (which wraps `std.file.write`) or `std.stdio.write`. And unfortunately, DMD issue #11847 currently makes it impossible to use a qualified name lookup for `scriptlike.file.wrappers.write`.

Here's how to easily avoid symbol conflict errors with Scriptlike and `write`:

// Save file
write("filename.txt", "content");  // Error: Symbols conflict!
// Change line above to...
writeFile("filename.txt", "content"); // Convenience alias included in scriptlike

// Output to stdout with no newline
write("Hello ", "world");  // Error: Symbols conflict!
// Change line above to...
std.stdio.write("Hello ", "world");
// or...
stdout.write("Hello ", "world");

See: `scriptlike.file.wrappers.writeFile`, `scriptlike.file.wrappers.readFile`, `scriptlike.file.wrappers.write`, `std.file.write`, `std.stdio.write`

Authors: Nick Sabalausky, Jesse Phillips

Dependencies: none

0.10.2 2017-Mar-03
0.10.1 2017-Feb-26
0.10.0 2017-Feb-26
0.9.7 2017-Feb-23
0.9.6 2016-May-29
Show all 18 versions
  • 0 downloads today

  • 2 downloads this week

  • 32 downloads this month

  • 1019 downloads total