├── tests
├── testExample
├── testExample.bat
├── testExample.sh
└── testExample.d
├── dub.selections.json
├── dummy
└── dummy.d
├── CHANGELOG.md
├── docs
└── public
│ ├── images
│ └── ddox
│ │ ├── alias.png
│ │ ├── class.png
│ │ ├── enum.png
│ │ ├── module.png
│ │ ├── struct.png
│ │ ├── function.png
│ │ ├── package.png
│ │ ├── private.png
│ │ ├── property.png
│ │ ├── template.png
│ │ ├── variable.png
│ │ ├── enummember.png
│ │ ├── inherited.png
│ │ ├── interface.png
│ │ └── protected.png
│ ├── prettify
│ ├── prettify.css
│ └── prettify.js
│ ├── scripts
│ ├── ddox.js
│ └── mousetrap.js
│ └── styles
│ └── ddox.css
├── examples
├── dub-project
│ ├── dub.selections.json
│ ├── dub.sdl
│ ├── dub.json
│ ├── myscript.d
│ └── README.txt
├── features
│ ├── AutomaticPhobosImport.d
│ ├── README.txt
│ ├── StringInterpolation.d
│ ├── DryRunAssistance.d
│ ├── UserInputPrompts.d
│ ├── DisambiguatingWrite.d
│ ├── Fail.d
│ ├── CommandEchoing.d
│ ├── TryAsFilesystemOperations.d
│ ├── Filepaths.d
│ └── ScriptStyleShellCommands.d
└── single-file
│ ├── myscript.d
│ └── README.txt
├── .gitignore
├── dub.sdl
├── src
└── scriptlike
│ ├── only.d
│ ├── std.d
│ ├── package.d
│ ├── fail.d
│ ├── path
│ ├── wrappers.d
│ ├── extras.d
│ └── package.d
│ ├── file
│ ├── package.d
│ └── extras.d
│ ├── interact.d
│ ├── process.d
│ └── core.d
├── .travis.yml
├── LICENSE.txt
├── makedocs
├── makedocs.bat
├── ddoc
├── macros.ddoc
└── changelog.d
├── USAGE.md
├── appveyor.yml
└── README.md
/tests/testExample:
--------------------------------------------------------------------------------
1 | testExample.sh
--------------------------------------------------------------------------------
/dub.selections.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileVersion": 1,
3 | "versions": {}
4 | }
5 |
--------------------------------------------------------------------------------
/dummy/dummy.d:
--------------------------------------------------------------------------------
1 | // This file/dir just keeps DUB from using it's own built-in buildsystem.
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Scriptlike's changelog has [moved here](http://semitwist.com/scriptlike/changelog.html).
2 |
--------------------------------------------------------------------------------
/docs/public/images/ddox/alias.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/alias.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/class.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/class.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/enum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/enum.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/module.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/module.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/struct.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/struct.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/function.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/function.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/package.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/package.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/private.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/private.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/property.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/property.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/template.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/template.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/variable.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/variable.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/enummember.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/enummember.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/inherited.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/inherited.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/interface.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/interface.png
--------------------------------------------------------------------------------
/docs/public/images/ddox/protected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Abscissa/scriptlike/HEAD/docs/public/images/ddox/protected.png
--------------------------------------------------------------------------------
/examples/dub-project/dub.selections.json:
--------------------------------------------------------------------------------
1 | {
2 | "fileVersion": 1,
3 | "versions": {
4 | "scriptlike": "0.10.2"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/examples/dub-project/dub.sdl:
--------------------------------------------------------------------------------
1 | name "myscript"
2 | targetType "executable"
3 | mainSourceFile "myscript.d"
4 | dependency "scriptlike" version="~>0.10.2"
5 |
--------------------------------------------------------------------------------
/tests/testExample.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | IF [%DMD%] == [] set DMD=dmd
3 | rdmd --compiler=%DMD% --force -debug -g -I%~dp0../src/ -of%~dp0.testExample %~dp0testExample.d %*
4 |
--------------------------------------------------------------------------------
/examples/features/AutomaticPhobosImport.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 | //import scriptlike.only; // In case you don't want Phobos auto-imported
3 | void main() {
4 | writeln("Works!");
5 | }
6 |
--------------------------------------------------------------------------------
/examples/dub-project/dub.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "myscript",
3 | "targetType": "executable",
4 | "mainSourceFile": "myscript.d",
5 | "dependencies": {
6 | "scriptlike": "~>0.10.2"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/examples/features/README.txt:
--------------------------------------------------------------------------------
1 | To build and run any of the examples in this directory:
2 |
3 | $ rdmd -I../../src (WhateverFile).d [program args]
4 |
5 | Examples:
6 | $ rdmd -I../../src StringInterpolation.d
7 | $ rdmd -I../../src Fail.d abc 123
8 |
--------------------------------------------------------------------------------
/examples/dub-project/myscript.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 |
3 | void main(string[] args) {
4 | writeln("This script is in directory: ", thisExePath.dirName);
5 |
6 | string name;
7 | if(args.length > 1)
8 | name = args[1];
9 | else
10 | name = userInput!string("What's your name?");
11 |
12 | writeln("Hello, ", name, "!");
13 | }
14 |
--------------------------------------------------------------------------------
/examples/dub-project/README.txt:
--------------------------------------------------------------------------------
1 | Note, you only need "dub.json" OR "dub.sdl", but both are provided here for convenience.
2 |
3 | To run this example:
4 |
5 | First, ensure you have DMD and DUB installed:
6 | - DMD: http://dlang.org/download.html#dmd
7 | - DUB: http://code.dlang.org/download
8 |
9 | And then:
10 | $ dub
11 | or
12 | $ dub -- Frank
13 |
--------------------------------------------------------------------------------
/examples/single-file/myscript.d:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env dub
2 | /+ dub.sdl:
3 | name "myscript"
4 | dependency "scriptlike" version="~>0.10.2"
5 | +/
6 | import scriptlike;
7 |
8 | void main(string[] args) {
9 | string name;
10 | if(args.length > 1)
11 | name = args[1];
12 | else
13 | name = userInput!string("What's your name?");
14 |
15 | writeln("Hello, ", name, "!");
16 | }
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .dub/
2 | bin/
3 | deleteme/
4 | docs/docs.json
5 | docs/public/file_hashes.json
6 | docs/public/*.html
7 | docs/public/sitemap.xml
8 | docs/public/symbols.js
9 | docs/public/scriptlike/
10 |
11 | src/scriptlike/packageVersion.d
12 | ddoc/packageVersion.ddoc
13 |
14 | /examples/dub-project/myscript
15 | /examples/single-file/myscript
16 | tests/.testExample*
17 | tests/bin/
18 |
19 | *.exe
20 | /tests/.o
21 |
--------------------------------------------------------------------------------
/tests/testExample.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | SCRIPT_DIR="$(dirname "$(dirname "$0")/$(readlink "$0")")"
3 | if [ -z "$DMD" ]; then
4 | DMD=dmd
5 | fi
6 | # RDMD isn't built-in on travis-ci's LDC/GDC
7 | $DMD -debug -g -I$SCRIPT_DIR/../src $SCRIPT_DIR/../src/**/*.d $SCRIPT_DIR/../src/scriptlike/**/*.d -of$SCRIPT_DIR/.testExample $SCRIPT_DIR/testExample.d && $SCRIPT_DIR/.testExample "$@"
8 |
9 | TEST_STATUS=$?
10 | rm $SCRIPT_DIR/.o -f
11 | exit $TEST_STATUS
12 |
--------------------------------------------------------------------------------
/examples/single-file/README.txt:
--------------------------------------------------------------------------------
1 | To run this example:
2 |
3 | First, ensure you have a DUB installed, and a D compiler such as DMD:
4 | - DMD: http://dlang.org/download.html#dmd
5 | - DUB: http://code.dlang.org/download
6 |
7 | DUB must be at least v1.0.0.
8 |
9 | And then...
10 |
11 | On Linux/OSX:
12 | -------------
13 | $ chmod +x myscript.d
14 | $ ./myscript.d
15 | or
16 | $ ./myscript.d Frank
17 |
18 | On *any* OS:
19 | -------------
20 | $ dub myscript.d
21 | or
22 | $ dub myscript.d Frank
23 |
--------------------------------------------------------------------------------
/examples/features/StringInterpolation.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 |
3 | void main()
4 | {
5 | // Output: The number 21 doubled is 42!
6 | int num = 21;
7 | writeln( mixin(interp!"The number ${num} doubled is ${num * 2}!") );
8 |
9 | // Output: Empty braces output nothing.
10 | writeln( mixin(interp!"Empty ${}braces ${}output nothing.") );
11 |
12 | // Output: Multiple params: John Doe.
13 | auto first = "John", last = "Doe";
14 | writeln( mixin(interp!`Multiple params: ${first, " ", last}.`) );
15 | }
16 |
--------------------------------------------------------------------------------
/examples/features/DryRunAssistance.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 |
3 | void main()
4 | {
5 | scriptlikeDryRun = true;
6 |
7 | // When dry-run is enabled, this echoes but doesn't actually copy or invoke DMD.
8 | copy("original.d", "app.d");
9 | run("dmd app.d -ofbin/app");
10 |
11 | // Works fine in dry-run, since it doesn't modify the filesystem.
12 | bool isItThere = exists("another-file");
13 |
14 | if(!scriptlikeDryRun)
15 | {
16 | // This won't work right if we're running in dry-run mode,
17 | // since it'll be out-of-date, if it even exists at all.
18 | auto source = read("app.d");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/features/UserInputPrompts.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 |
3 | void main()
4 | {
5 | auto name = userInput!string("Please enter your name");
6 | auto age = userInput!int("And your age");
7 |
8 | if(userInput!bool("Do you want to continue?"))
9 | {
10 | string outputFolder = pathLocation("Where you do want to place the output?");
11 | auto color = menu!string("What color would you like to use?", ["Blue", "Green"]);
12 | }
13 |
14 | auto num = require!(int, "a > 0 && a <= 10")("Enter a number from 1 to 10");
15 |
16 | pause(); // Prompt "Press Enter to continue...";
17 | pause("Hit Enter again, dood!!");
18 | }
19 |
--------------------------------------------------------------------------------
/examples/features/DisambiguatingWrite.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 | import std.stdio;
3 |
4 | void main()
5 | {
6 | // Setup and cleanup
7 | chdir(thisExePath.dirName);
8 | scope(exit)
9 | tryRemove("filename.txt");
10 |
11 | // Save file
12 | //write("filename.txt", "content"); // Error: Symbols conflict!
13 | // Change line above to...
14 | writeFile("filename.txt", "content"); // Convenience alias included in scriptlike
15 |
16 | // Output to stdout with no newline
17 | //write("Hello ", "world"); // Error: Symbols conflict!
18 | // Change line above to...
19 | std.stdio.write("Hello ", "world");
20 | // or...
21 | stdout.write("Hello ", "world");
22 | }
23 |
--------------------------------------------------------------------------------
/examples/features/Fail.d:
--------------------------------------------------------------------------------
1 | /++
2 | Example:
3 | --------
4 | $ test
5 | test: ERROR: Need two args, not 0!
6 | $ test abc 123
7 | test: ERROR: First arg must be 'foobar', not 'abc'!
8 | --------
9 | +/
10 |
11 | import scriptlike;
12 |
13 | void main(string[] args) {
14 | helper(args);
15 | }
16 |
17 | // Throws a Fail exception on bad args:
18 | void helper(string[] args) {
19 | // Like std.exception.enforce, but bails with no ugly stack trace,
20 | // and if uncaught, outputs the program name and "ERROR: "
21 | failEnforce(args.length == 3, "Need two args, not ", args.length-1, "!");
22 |
23 | if(args[1] != "foobar")
24 | fail("First arg must be 'foobar', not '", args[1], "'!");
25 | }
26 |
--------------------------------------------------------------------------------
/dub.sdl:
--------------------------------------------------------------------------------
1 | name "scriptlike"
2 | description "Utility library to help you write script-like programs."
3 | authors "Nick Sabalausky" "Jesse Phillips"
4 | homepage "https://github.com/abscissa/scriptlike"
5 | license "zlib/libpng"
6 |
7 | excludedSourceFiles "src/scriptlike/packageVersion.d"
8 |
9 | configuration "library" {
10 | targetType "library"
11 | targetPath "bin"
12 | importPaths "src"
13 | }
14 |
15 | configuration "unittest" {
16 | targetType "executable"
17 | targetPath "bin"
18 | targetName "scriptlike_unittest"
19 | versions "unittest_scriptlike_d"
20 | }
21 |
22 | configuration "no-build" {
23 | targetType "library"
24 | targetPath "deleteme"
25 | importPaths "dummy"
26 | sourcePaths "dummy"
27 | excludedSourceFiles "src/*"
28 | }
29 |
--------------------------------------------------------------------------------
/src/scriptlike/only.d:
--------------------------------------------------------------------------------
1 | /++
2 | $(H2 Scriptlike $(SCRIPTLIKE_VERSION))
3 | Utility to aid in script-like programs.
4 |
5 | Written in the $(LINK2 http://dlang.org, D programming language).
6 |
7 | Import this `scriptlike.only` module instead of `scriptlike` if you want to
8 | import all of Scriptlike, but DON'T want to automatically import any of Phobos.
9 |
10 | Copyright: Copyright (C) 2014-2017 Nick Sabalausky
11 | License: $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/LICENSE.txt, zlib/libpng)
12 | Authors: Nick Sabalausky
13 | +/
14 |
15 |
16 | module scriptlike.only;
17 |
18 | public import scriptlike.core;
19 | public import scriptlike.interact;
20 | public import scriptlike.fail;
21 | public import scriptlike.file;
22 | public import scriptlike.path;
23 | public import scriptlike.process;
24 |
--------------------------------------------------------------------------------
/examples/features/CommandEchoing.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 |
3 | void main()
4 | {
5 | // Setup and cleanup
6 | chdir(thisExePath.dirName);
7 | scope(exit)
8 | {
9 | scriptlikeEcho = false;
10 | tryRemove("file.txt");
11 | tryRmdirRecurse("some");
12 | }
13 |
14 | /++
15 | Output:
16 | --------
17 | run: echo Hello > file.txt
18 | mkdirRecurse: some/new/dir
19 | copy: file.txt -> 'some/new/dir/target name.txt'
20 | Gonna run foo() now...
21 | foo: i = 42
22 | --------
23 | +/
24 |
25 | scriptlikeEcho = true; // Enable automatic echoing
26 |
27 | run("echo Hello > file.txt");
28 |
29 | auto newDir = Path("some/new/dir");
30 | mkdirRecurse(newDir.raw); // Even works with non-Path overloads
31 | copy("file.txt", newDir ~ "target name.txt");
32 |
33 | void foo(int i = 42) {
34 | yapFunc("i = ", i); // Evaluated lazily
35 | }
36 |
37 | // yap and yapFunc ONLY output when echoing is enabled
38 | yap("Gonna run foo() now...");
39 | foo();
40 | }
41 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: d
2 | sudo: false
3 |
4 | before_script:
5 | - dub add-local .
6 |
7 | matrix:
8 | include:
9 | - d: dmd-2.080.0
10 | - d: dmd-2.079.1
11 | - d: dmd-2.078.2
12 | - d: dmd-2.077.1
13 | - d: dmd-2.076.1
14 | - d: dmd-2.075.1
15 | - d: dmd-2.074.1
16 | - d: dmd-2.073.1
17 | - d: dmd-2.072.2
18 | - d: dmd-2.071.2
19 | - d: dmd-2.070.2
20 | - d: dmd-2.069.2
21 | - d: dmd-2.068.1
22 | - d: dmd-2.068.0
23 | - d: dmd-2.067.1
24 | - d: dmd-2.067.0
25 | - d: dmd-2.066.1
26 | - d: dmd-2.066.0
27 | - d: ldc-1.9.0
28 | - d: ldc-1.8.0
29 | - d: ldc-1.7.0
30 | - d: ldc-1.6.0
31 | - d: ldc-1.5.0
32 | - d: ldc-1.4.0
33 | - d: ldc-1.3.0
34 | - d: ldc-1.2.0
35 | - d: ldc-1.1.1
36 | - d: ldc-1.1.0
37 | - d: ldc-1.0.0
38 | - d: ldc-0.17.5
39 | - d: ldc-0.17.1
40 | - d: ldc-0.16.1
41 | - d: gdc-5.2.0
42 | - d: gdc-4.9.3
43 | - d: gdc-4.9.2
44 | - d: gdc-4.8.5
45 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | -----------------------------------------------------
2 | Scriptlike is licensed under The zlib/libpng License:
3 | -----------------------------------------------------
4 |
5 | Copyright (c) 2014-2017 Nick Sabalausky
6 | Portions Copyright (C) 2010 Jesse Phillips
7 |
8 | This software is provided 'as-is', without any express or implied
9 | warranty. In no event will the authors be held liable for any damages
10 | arising from the use of this software.
11 |
12 | Permission is granted to anyone to use this software for any purpose,
13 | including commercial applications, and to alter it and redistribute it
14 | freely, subject to the following restrictions:
15 |
16 | 1. The origin of this software must not be misrepresented; you must not
17 | claim that you wrote the original software. If you use this software
18 | in a product, an acknowledgment in the product documentation would be
19 | appreciated but is not required.
20 |
21 | 2. Altered source versions must be plainly marked as such, and must not be
22 | misrepresented as being the original software.
23 |
24 | 3. This notice may not be removed or altered from any source
25 | distribution.
26 |
--------------------------------------------------------------------------------
/makedocs:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Note, generating Scriptlike's docs requires that dub"
4 | echo " be available on your PATH."
5 | echo
6 | echo "You must also have ddox and gen-package-version available through dub:"
7 | echo "$ dub fetch ddox --version=0.15.18"
8 | echo "$ dub fetch gen-package-version --version=1.0.5"
9 | echo "or:"
10 | echo "$ dub add-local [path/to/ddox]"
11 | echo "$ dub add-local [path/to/gen-package-version]"
12 | echo
13 | echo "You may need to remove any older versions installed"
14 | echo "so they don't get run instead."
15 | echo
16 | echo "If you get errors, double-check you have dub, ddox and gen-package-version"
17 | echo "all installed as described above."
18 | echo
19 |
20 | dub run gen-package-version -- scriptlike --src=src --ddoc=ddoc
21 | rdmd -Isrc -Iddoc --build-only --force -c -Dddocs_tmp -X -Xfdocs/docs.json -version=docs_scriptlike_d src/scriptlike/package.d
22 | rm -rf docs_tmp
23 | rm src/scriptlike/package.o
24 | dub run ddox -- filter docs/docs.json --min-protection=Protected --ex=scriptlike.packageVersion
25 | dub run ddox -- generate-html docs/docs.json docs/public --navigation-type=ModuleTree --override-macros=ddoc/macros.ddoc --override-macros=ddoc/packageVersion.ddoc
26 |
--------------------------------------------------------------------------------
/makedocs.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 |
3 | echo Note, generating Scriptlike's docs requires that dub
4 | echo ^ be available on your PATH.
5 | echo.
6 | echo You must also have ddox and gen-package-version available through dub:
7 | echo ^> dub fetch ddox --version=0.15.18
8 | echo ^> dub fetch gen-package-version --version=1.0.5
9 | echo or:
10 | echo ^> dub add-local [path/to/ddox]
11 | echo ^> dub add-local [path/to/gen-package-version]
12 | echo.
13 | echo You may need to remove any older versions installed
14 | echo so they don't get run instead.
15 | echo.
16 | echo If you get errors, double-check you have dub, ddox and gen-package-version
17 | echo all installed as described above.
18 | echo.
19 |
20 | dub run gen-package-version -- scriptlike --src=src --ddoc=ddoc
21 | rdmd -Isrc -Iddoc --build-only --force -c -Dddocs_tmp -X -Xfdocs\docs.json -version=docs_scriptlike_d src\scriptlike\package.d
22 | rmdir /S /Q docs_tmp > NUL 2> NUL
23 | del src\scriptlike\package.obj
24 | dub run ddox -- filter docs\docs.json --min-protection=Protected --ex=scriptlike.packageVersion
25 | dub run ddox -- generate-html docs\docs.json docs\public --navigation-type=ModuleTree --override-macros=ddoc\macros.ddoc --override-macros=ddoc\packageVersion.ddoc
26 |
--------------------------------------------------------------------------------
/examples/features/TryAsFilesystemOperations.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 |
3 | void main()
4 | {
5 | // Setup
6 | chdir(thisExePath.dirName);
7 |
8 | // Just MAKE SURE this exists! If it's already there, then GREAT!
9 | tryMkdir("somedir");
10 | assertThrown( mkdir("somedir") ); // Exception: Already exists!
11 | tryMkdir("somedir"); // Works fine!
12 |
13 | // Just MAKE SURE this is gone! If it's already gone, then GREAT!
14 | tryRmdir("somedir");
15 | assertThrown( rmdir("somedir") ); // Exception: Already gone!
16 | tryRmdir("somedir"); // Works fine!
17 |
18 | // Just MAKE SURE it doesn't exist. Don't bother me if it doesn't!
19 | tryRemove("file");
20 |
21 | // Copy if it exists, otherwise don't worry about it.
22 | tryCopy("file", "file-copy");
23 |
24 | // Is this a directory? If it doesn't even exist,
25 | // then it's obviously NOT a directory.
26 | assertThrown( isDir("foo/bar") ); // Exception: Doesn't exist!
27 | if(existsAsDir("foo/bar")) // Works fine!
28 | {/+ ...do stuff... +/}
29 |
30 | // Bonus! Single function to delete files OR directories!
31 | writeFile("file.txt", "abc");
32 | tryMkdirRecurse("foo/bar/dir");
33 | writeFile("foo/bar/dir/file.txt", "123");
34 | // Delete with the same function!
35 | removePath("file.txt"); // Calls 'remove'
36 | removePath("foo"); // Calls 'rmdirRecurse'
37 | tryRemovePath("file.txt"); // Also comes in try flavor!
38 | tryRemovePath("foo");
39 | }
40 |
--------------------------------------------------------------------------------
/examples/features/Filepaths.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 |
3 | void main()
4 | {
5 | // Setup and cleanup
6 | chdir(thisExePath.dirName);
7 | scope(exit)
8 | {
9 | tryRmdirRecurse("path");
10 | tryRmdirRecurse("target");
11 | }
12 | mkdirRecurse("path/to");
13 | mkdirRecurse("target/path");
14 | import scriptlike.file.wrappers : write;
15 | write("path/to/file.txt", "abc");
16 |
17 | // This is AUTOMATICALLY kept normalized (via std.path.buildNormalizedPath)
18 | auto dir = Path("foo/bar");
19 | dir ~= "subdir"; // Append a subdirectory
20 |
21 | // No worries about trailing slashes!
22 | assert(Path("foo/bar") == Path("foo/bar/"));
23 | assert(Path("foo/bar/") == Path("foo/bar//"));
24 |
25 |
26 | // No worries about forward/backslashes!
27 | assert(dir == Path("foo/bar/subdir"));
28 | assert(dir == Path("foo\\bar\\subdir"));
29 |
30 | // No worries about spaces!
31 | auto file = dir.up ~ "different subdir\\Filename with spaces.txt";
32 | assert(file == Path("foo/bar/different subdir/Filename with spaces.txt"));
33 | writeln(file); // Path.toString() always properly escapes for current platform!
34 | writeln(file.raw); // Don't escape!
35 |
36 | // Even file extentions are type-safe!
37 | Ext ext = file.extension;
38 | auto anotherFile = Path("path/to/file") ~ ext;
39 | assert(anotherFile.baseName == Path("file.txt"));
40 |
41 | // std.path and std.file are wrapped to offer Path/Ext support
42 | assert(dirName(anotherFile) == Path("path/to"));
43 | copy(anotherFile, Path("target/path/new file.txt"));
44 | }
45 |
--------------------------------------------------------------------------------
/src/scriptlike/std.d:
--------------------------------------------------------------------------------
1 | /++
2 | $(H2 Scriptlike $(SCRIPTLIKE_VERSION))
3 | Utility to aid in script-like programs.
4 |
5 | Written in the $(LINK2 http://dlang.org, D programming language).
6 |
7 | Automatically pulls in anything from Phobos likely to be useful for scripts.
8 |
9 | The public `std.file` and `std.path` imports here are static imports to
10 | avoid name conflicts with the $(API_PATH_EXTR Path)-based wrappers in
11 | `scriptlike.file` and `scriptlike.path`.
12 |
13 | curl is omitted here because it involves an extra link dependency.
14 |
15 | Copyright: Copyright (C) 2014-2017 Nick Sabalausky
16 | License: $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/LICENSE.txt, zlib/libpng)
17 | Authors: Nick Sabalausky
18 | +/
19 |
20 | module scriptlike.std;
21 |
22 | public import std.algorithm;
23 | public import std.array;
24 | public import std.bigint;
25 | public import std.conv;
26 | public import std.datetime;
27 | public import std.exception;
28 | public import std.getopt;
29 | public import std.math;
30 | public import std.process;
31 | public import std.random;
32 | public import std.range;
33 | public import std.regex;
34 | public import std.stdio;
35 | public import std.string;
36 | public import std.system;
37 | public import std.traits;
38 | public import std.typecons;
39 | public import std.typetuple;
40 | public import std.uni;
41 | public import std.variant;
42 |
43 | public import std.path : dirSeparator, pathSeparator, isDirSeparator,
44 | buildPath, buildNormalizedPath;
45 |
46 | public static import std.file;
47 | public static import std.path;
48 |
--------------------------------------------------------------------------------
/examples/features/ScriptStyleShellCommands.d:
--------------------------------------------------------------------------------
1 | import scriptlike;
2 |
3 | void main()
4 | {
5 | // Setup and cleanup
6 | chdir(thisExePath.dirName);
7 | scope(exit)
8 | tryRmdirRecurse("my");
9 | mkdirRecurse("my/proj/dir/src");
10 | import scriptlike.file.wrappers : write;
11 | write("my/proj/dir/src/main.d", `import std.stdio; void main() { writeln("Hello"); }`);
12 |
13 | run("dmd --help"); // Display DMD help screen
14 | pause(); // Wait for user to hit Enter
15 |
16 | // Automatically throws ErrorLevelException(1, "dmd --bad-flag")
17 | assertThrown!ErrorLevelException( run("dmd --bad-flag") );
18 |
19 | // Automatically throws ErrorLevelException(-1, "this-cmd-does-not-exist")
20 | assertThrown!ErrorLevelException( run("this-cmd-does-not-exist") );
21 |
22 | // Don't bail on error
23 | int statusCode = tryRun("dmd --bad-flag");
24 |
25 | // Collect output instead of showing it
26 | string dmdHelp = runCollect("dmd --help");
27 | auto isDMD_2_068_1 = dmdHelp.canFind("D Compiler v2.068.1");
28 |
29 | // Don't bail on error
30 | auto result = tryRunCollect("dmd --help");
31 | if(result.status == 0 && result.output.canFind("D Compiler v2.068.1"))
32 | writeln("Found DMD v2.068.1!");
33 |
34 | // Use any working directory:
35 | auto myProjectDir = Path("my/proj/dir");
36 | auto mainFile = Path("src/main.d");
37 | myProjectDir.run(text("dmd ", mainFile, " -O")); // mainFile is properly escaped!
38 |
39 | // Verify it actually IS running from a different working directory:
40 | version(Posix) enum pwd = "pwd";
41 | else version(Windows) enum pwd = "cd";
42 | else static assert(0);
43 | auto output = myProjectDir.runCollect(pwd);
44 | auto expected = getcwd() ~ myProjectDir;
45 | assert( Path(output.strip()) == expected );
46 | }
47 |
--------------------------------------------------------------------------------
/src/scriptlike/package.d:
--------------------------------------------------------------------------------
1 | /++
2 | $(H2 Scriptlike $(SCRIPTLIKE_VERSION))
3 | Scriptlike is a utility library to help you write script-like programs in D.
4 |
5 | Written in the $(LINK2 http://dlang.org, D programming language) and licensed under
6 | The $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/LICENSE.txt, zlib/libpng) License.
7 |
8 | For the list of officially supported compiler versions, see the
9 | $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/.travis.yml, .travis.yml)
10 | file included with your version of Scriptlike.
11 |
12 | Links:
13 | $(UL
14 | $(LI $(LINK2 https://github.com/Abscissa/scriptlike, Scriptlike Homepage) )
15 | $(LI $(LINK2 http://semitwist.com/scriptlike, Latest API Reference ) )
16 | $(LI $(LINK2 http://semitwist.com/scriptlike-docs, Older API Reference Archives ) )
17 | )
18 |
19 | Import all (including anything from Phobos likely to be useful for scripts):
20 | ------------
21 | import scriptlike;
22 | ------------
23 |
24 | Import all of Scriptlike only, but no Phobos:
25 | ------------
26 | import scriptlike.only;
27 | ------------
28 |
29 | Homepage:
30 | $(LINK https://github.com/abscissa/scriptlike)
31 |
32 | Copyright:
33 | Copyright (C) 2014-2017 Nick Sabalausky.
34 | Portions Copyright (C) 2010 Jesse Phillips.
35 |
36 | License: $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/LICENSE.txt, zlib/libpng)
37 | Authors: Nick Sabalausky, Jesse Phillips
38 | +/
39 |
40 | module scriptlike;
41 |
42 | public import scriptlike.only;
43 | public import scriptlike.std;
44 |
45 | version(docs_scriptlike_d) import changelog;
46 | version(unittest_scriptlike_d) void main() {}
47 |
48 | // Run tests for sample programs in 'examples'
49 | version(unittest_scriptlike_d)
50 | unittest
51 | {
52 | version(Windows)
53 | // This Posix artifact gets in the way of calling .myscript.exe
54 | // Only an issue when Win/Posix machines are operating from the same directory.
55 | tryRemove("tests/.testExample");
56 |
57 | writeln("Testing sample programs in 'examples':");
58 | run(text( Path("tests/testExample"), " All" ));
59 | }
60 |
--------------------------------------------------------------------------------
/docs/public/prettify/prettify.css:
--------------------------------------------------------------------------------
1 | /* Pretty printing styles. Used with prettify.js. */
2 |
3 | /* SPAN elements with the classes below are added by prettyprint. */
4 | .pln { color: #ffffff } /* plain text */
5 | pre .pln { color: #ffffff } /* plain text */
6 |
7 | @media screen {
8 | pre .str { color: #ffe7b6 } /* string content */
9 | pre .typ { color: #9ad452 } /* a type name */
10 | pre .lit { color: #ffe7b6 } /* a literal value */
11 | pre .pun, .opn, .clo { color: #ddd }
12 |
13 | .str { color: #842 } /* string content */
14 | .kwd { color: #ffaa00 } /* a keyword */
15 | .com { color: #888 } /* a comment */
16 | .typ { color: #693 } /* a type name */
17 | .lit { color: #875 } /* a literal value */
18 | /* punctuation, lisp open bracket, lisp close bracket */
19 | .pun, .opn, .clo { color: #ddd }
20 | .tag { color: #ffaa00 } /* a markup tag name */
21 | .atn { color: #9ad452 } /* a markup attribute name */
22 | .atv { color: #ffe7b6 } /* a markup attribute value */
23 | .dec, .var { color: #aaa } /* a declaration; a variable name */
24 | .fun { color: red } /* a function name */
25 | }
26 |
27 | /* Use higher contrast and text-weight for printable form. */
28 | @media print, projection {
29 | .str { color: #060 }
30 | .kwd { color: #006; font-weight: bold }
31 | .com { color: #600; font-style: italic }
32 | .typ { color: #404; font-weight: bold }
33 | .lit { color: #044 }
34 | .pun, .opn, .clo { color: #440 }
35 | .tag { color: #006; font-weight: bold }
36 | .atn { color: #404 }
37 | .atv { color: #060 }
38 | }
39 |
40 | /* Put a border around prettyprinted code snippets. */
41 | pre.prettyprint {
42 | padding: 0.4em 0.4em 0.4em 0.4em;
43 | color: #C0C0C0;
44 | background-color: #222;
45 | border: 1px solid black;
46 | }
47 |
48 | /* Specify class=linenums on a pre to get line numbering */
49 | ol.linenums { margin-top: 0; margin-bottom: 0 } /* IE indents via margin-left */
50 | li.L0,
51 | li.L1,
52 | li.L2,
53 | li.L3,
54 | li.L5,
55 | li.L6,
56 | li.L7,
57 | li.L8 { list-style-type: none }
58 | /* Alternate shading for lines */
59 | li.L1,
60 | li.L3,
61 | li.L5,
62 | li.L7,
63 | li.L9 { background: #222222 }
64 |
--------------------------------------------------------------------------------
/src/scriptlike/fail.d:
--------------------------------------------------------------------------------
1 | // Scriptlike: Utility to aid in script-like programs.
2 | // Written in the D programming language.
3 |
4 | /// Copyright: Copyright (C) 2014-2017 Nick Sabalausky
5 | /// License: $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/LICENSE.txt, zlib/libpng)
6 | /// Authors: Nick Sabalausky
7 |
8 | module scriptlike.fail;
9 |
10 | import std.conv;
11 | import std.file;
12 | import std.path;
13 | import std.traits;
14 |
15 | /// This is the exception thrown by fail(). There's no need to create or throw
16 | /// this directly, but it's public in case you have reason to catch it.
17 | class Fail : Exception
18 | {
19 | private this()
20 | {
21 | super(null);
22 | }
23 |
24 | private static string msg;
25 | private static Fail opCall(string msg, string file=__FILE__, int line=__LINE__)
26 | {
27 | Fail.msg = msg;
28 | static if(__traits(compiles, Fail.classinfo.initializer))
29 | // DMD 2.072 or 2.073 deprecates 'classinfo.init'
30 | throw cast(Fail) cast(void*) Fail.classinfo.initializer;
31 | else
32 | // DMD 2.069.2 and below lack 'classinfo.initializer'
33 | throw cast(Fail) cast(void*) Fail.classinfo.init;
34 | }
35 |
36 | private static string fullMessage(string msg = Fail.msg)
37 | {
38 | auto appName = thisExePath().baseName();
39 |
40 | version(Windows)
41 | appName = appName.stripExtension();
42 |
43 | return appName~": ERROR: "~msg;
44 | }
45 |
46 | override void toString(scope void delegate(in char[]) sink) const
47 | {
48 | sink(fullMessage());
49 | }
50 | }
51 |
52 | /++
53 | Call this to end your program with an error message for the user, and no
54 | ugly stack trace. The error message is sent to stderr and the errorlevel is
55 | set to non-zero.
56 |
57 | This is exception-safe, all cleanup code gets run.
58 |
59 | Your program's name is automatically detected from $(STD_FILE thisExePath).
60 |
61 | Example:
62 | ----------------
63 | auto id = 3;
64 | fail("You forgot to provide a destination for id #", id, "!");
65 |
66 | // Output:
67 | // yourProgramName: ERROR: You forgot to provide a destination for id #3!
68 | ----------------
69 | +/
70 | void fail(T...)(T args)
71 | {
72 | throw Fail( text(args) );
73 | }
74 |
75 | /++
76 | Calls fail() if the condition is false.
77 |
78 | This is much like $(FULL_STD_EXCEPTION enforce), but for for fail() instead of
79 | arbitrary exceptions.
80 |
81 | Example:
82 | ----------------
83 | failEnforce(brokenSquareRoot(4)==2, "Reality broke! Expected 2, not ", brokenSquareRoot(4));
84 |
85 | // Output:
86 | // yourProgramName: ERROR: Reality broke! Expected 2, not 555
87 | ----------------
88 | +/
89 | void failEnforce(T...)(bool cond, T args)
90 | {
91 | if(!cond)
92 | fail(args);
93 | }
94 |
--------------------------------------------------------------------------------
/ddoc/macros.ddoc:
--------------------------------------------------------------------------------
1 | Ddoc
2 |
3 | Macros:
4 | H2 = $0
5 | BR =
6 | DOLLAR = $
7 | COLON = :
8 | EM = $(B $(I $0) )
9 | ISSUE = $(LINK2 https://github.com/Abscissa/scriptlike/issues/$0, #$0)
10 | ENHANCE = $(LI $(B Enhancement$(COLON)) $0)
11 | CHANGE = $(LI $(B Change$(COLON)) $0)
12 | FIXED = $(LI $(B Fixed$(COLON)) $0)
13 | INTERNAL = $(LI $(B Internal$(COLON)) $0)
14 | TESTS = $(LI $(B Tests$(COLON)) $0)
15 | API_BASE = $(DDOX_ROOT_DIR)scriptlike/
16 | API_CORE = $(D_INLINECODE $(LINK2 $(API_BASE)core/$0.html, $0))
17 | API_FAIL = $(D_INLINECODE $(LINK2 $(API_BASE)fail/$0.html, $0))
18 | API_FILE_EXTR = $(D_INLINECODE $(LINK2 $(API_BASE)file/extras/$0.html, $0))
19 | API_FILE_WRAP = $(D_INLINECODE $(LINK2 $(API_BASE)file/wrappers/$0.html, $0))
20 | API_INTERACT = $(D_INLINECODE $(LINK2 $(API_BASE)interact/$0.html, $0))
21 | API_PATH_EXTR = $(D_INLINECODE $(LINK2 $(API_BASE)path/extras/$0.html, $0))
22 | API_PATH_WRAP = $(D_INLINECODE $(LINK2 $(API_BASE)path/wrappers/$0.html, $0))
23 | API_PROCESS = $(D_INLINECODE $(LINK2 $(API_BASE)process/$0.html, $0))
24 | MODULE_CORE = $(D_INLINECODE $(LINK2 $(API_BASE)core.html, scriptlike.core))
25 | MODULE_FAIL = $(D_INLINECODE $(LINK2 $(API_BASE)fail.html, scriptlike.fail))
26 | MODULE_FILE = $(D_INLINECODE $(LINK2 $(API_BASE)file.html, scriptlike.file))
27 | MODULE_FILE_EXTR = $(D_INLINECODE $(LINK2 $(API_BASE)file/extras.html, scriptlike.file.extras))
28 | MODULE_FILE_WRAP = $(D_INLINECODE $(LINK2 $(API_BASE)file/wrappers.html, scriptlike.file.wrappers))
29 | MODULE_INTERACT = $(D_INLINECODE $(LINK2 $(API_BASE)interact.html, scriptlike.interact))
30 | MODULE_PATH = $(D_INLINECODE $(LINK2 $(API_BASE)path.html, scriptlike.path))
31 | MODULE_PATH_EXTR = $(D_INLINECODE $(LINK2 $(API_BASE)path/extras.html, scriptlike.path.extras))
32 | MODULE_PATH_WRAP = $(D_INLINECODE $(LINK2 $(API_BASE)path/wrappers.html, scriptlike.path.wrappers))
33 | MODULE_PROCESS = $(D_INLINECODE $(LINK2 $(API_BASE)process.html, scriptlike.process))
34 | STD_EXCEPTION = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_exception.html#$0, $0))
35 | STD_FILE = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_file.html#$0, $0))
36 | STD_PATH = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_path.html#$0, $0))
37 | STD_PROCESS = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_process.html#$0, $0))
38 | STD_STDIO = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_stdio.html#$0, $0))
39 | FULL_STD_EXCEPTION = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_exception.html#$0, std.exception.$0))
40 | FULL_STD_FILE = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_file.html#$0, std.file.$0))
41 | FULL_STD_PATH = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_path.html#$0, std.path.$0))
42 | FULL_STD_PROCESS = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_process.html#$0, std.process.$0))
43 | FULL_STD_STDIO = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_stdio.html#$0, std.stdio.$0))
44 | DMD = $(LINK2 http://dlang.org/, DMD)
45 | DDOX = $(LINK2 https://github.com/rejectedsoftware/ddox, ddox)
46 | MODULE_STD_FILE = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_file.html, std.file))
47 | MODULE_STD_PATH = $(D_INLINECODE $(LINK2 http://dlang.org/phobos/std_path.html, std.path))
48 | LICENSE = $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/LICENSE.txt, zlib/libpng)
49 |
--------------------------------------------------------------------------------
/USAGE.md:
--------------------------------------------------------------------------------
1 | How to Use Scriptlike in Scripts
2 | ================================
3 |
4 | These examples can be found in the
5 | "[examples](https://github.com/Abscissa/scriptlike/blob/master/examples)" directory.
6 |
7 | * [A Basic Script in D](#a-basic-script-in-d)
8 | * [In a DUB-based project](#in-a-dub-based-project)
9 |
10 | A Basic Script in D
11 | -------------------
12 | Make sure you have [DUB](http://code.dlang.org/download) v1.0.0 or later installed,
13 | as well as a D compiler (DMD, LDC, or GDC). You can check your version of DUB
14 | by running `dub --help`. Then, using Scriptlike in a D script is easy:
15 |
16 | myscript.d:
17 | ```d
18 | #!/usr/bin/env dub
19 | /+ dub.sdl:
20 | name "myscript"
21 | dependency "scriptlike" version="~>0.10.2"
22 | +/
23 | import scriptlike;
24 |
25 | void main(string[] args) {
26 | string name;
27 | if(args.length > 1)
28 | name = args[1];
29 | else
30 | name = userInput!string("What's your name?");
31 |
32 | writeln("Hello, ", name, "!");
33 | }
34 | ```
35 |
36 | You don't even need to download or install Scriptlike! That will be done
37 | automatically by DUB, thanks to the `dependency` line in the special
38 | `dub.sdl` comment section. (Learn
39 | [more about this feature of DUB](http://code.dlang.org/getting_started#single-file-packages),
40 | introduced in DUB v1.0.0.)
41 |
42 | On Linux/OSX, you can then run that script just like any shell script:
43 |
44 | ```bash
45 | $ chmod +x myscript.d
46 | $ ./myscript.d Frank
47 | Hello, Frank!
48 | ```
49 |
50 | As long as you have a D compiler installed (DMD, LDC or GDC), that will
51 | cause DUB to automatically download/install all dependencies (in this case,
52 | just Scriptlike), recompile the script if necessary, and run it.
53 |
54 | Or if you're on Windows (this will also work on Linux/OSX, too):
55 | ```batch
56 | > dub myscript.d Frank
57 | Hello, Frank!
58 | ```
59 |
60 | NOTE: Due to [an issue](https://github.com/dlang/dub/issues/907) in DUB,
61 | if you use this single-file approach and you need to use
62 | [`thisExePath`](http://semitwist.com/scriptlike/scriptlike/file/wrappers/thisExePath.html)
63 | (or the [Phobos equivalent](http://dlang.org/phobos/std_file.html#thisExePath)),
64 | then you won't get the expected result. The `thisExePath` function will NOT
65 | return the path to the `myscript.d` script, it will simply return the temporary
66 | path where DUB stored the compiled binary. I'm not aware of any way to work
67 | around this while using DUB's single-file feature, so if your script needs
68 | to obtain its own path (remember, `args[0]` is famously unreliable for this
69 | in any language), then try one of the older approaches below.
70 |
71 | In a DUB-based project
72 | ----------------------
73 | If your project uses [DUB](http://code.dlang.org/getting_started),
74 | just include the scriptlike as a dependency in your
75 | [dub.json](http://code.dlang.org/package-format?lang=json) or
76 | [dub.sdl](http://code.dlang.org/package-format?lang=sdl) file like this:
77 |
78 | dub.json:
79 | ```json
80 | "dependencies": {
81 | "scriptlike": "~>0.10.2"
82 | }
83 | ```
84 |
85 | dub.sdl:
86 | ```
87 | dependency "scriptlike" version="~>0.10.2"
88 | ```
89 |
90 | And then import with one of these:
91 |
92 | ```d
93 | // Imports all of Scriptlike, plus anything from Phobos likely to
94 | // be useful for scripts:
95 | import scriptlike;
96 |
97 | // Or import only Scriptlike and omit the automatic Phobos imports:
98 | import scriptlike.only;
99 | ```
100 |
101 | Run your project with dub like normal:
102 |
103 | ```bash
104 | $ dub
105 | ```
106 |
--------------------------------------------------------------------------------
/docs/public/scripts/ddox.js:
--------------------------------------------------------------------------------
1 | function setupDdox()
2 | {
3 | $(".tree-view").children(".package").click(toggleTree);
4 | $(".tree-view.collapsed").children("ul").hide();
5 | }
6 |
7 | function toggleTree()
8 | {
9 | node = $(this).parent();
10 | node.toggleClass("collapsed");
11 | if( node.hasClass("collapsed") ){
12 | node.children("ul").hide();
13 | } else {
14 | node.children("ul").show();
15 | }
16 | return false;
17 | }
18 |
19 | var searchCounter = 0;
20 | var lastSearchString = "";
21 |
22 | function performSymbolSearch(maxlen)
23 | {
24 | if (maxlen === 'undefined') maxlen = 26;
25 |
26 | var searchstring = $("#symbolSearch").val().toLowerCase();
27 |
28 | if (searchstring == lastSearchString) return;
29 | lastSearchString = searchstring;
30 |
31 | var scnt = ++searchCounter;
32 | $('#symbolSearchResults').hide();
33 | $('#symbolSearchResults').empty();
34 |
35 | var terms = $.trim(searchstring).split(/\s+/);
36 | if (terms.length == 0 || (terms.length == 1 && terms[0].length < 2)) return;
37 |
38 | var results = [];
39 | for (i in symbols) {
40 | var sym = symbols[i];
41 | var all_match = true;
42 | for (j in terms)
43 | if (sym.name.toLowerCase().indexOf(terms[j]) < 0) {
44 | all_match = false;
45 | break;
46 | }
47 | if (!all_match) continue;
48 |
49 | results.push(sym);
50 | }
51 |
52 | function compare(a, b) {
53 | // prefer non-deprecated matches
54 | var adep = a.attributes.indexOf("deprecated") >= 0;
55 | var bdep = b.attributes.indexOf("deprecated") >= 0;
56 | if (adep != bdep) return adep - bdep;
57 |
58 | // normalize the names
59 | var aname = a.name.toLowerCase();
60 | var bname = b.name.toLowerCase();
61 |
62 | var anameparts = aname.split(".");
63 | var bnameparts = bname.split(".");
64 |
65 | var asname = anameparts[anameparts.length-1];
66 | var bsname = bnameparts[bnameparts.length-1];
67 |
68 | // prefer exact matches
69 | var aexact = terms.indexOf(asname) >= 0;
70 | var bexact = terms.indexOf(bsname) >= 0;
71 | if (aexact != bexact) return bexact - aexact;
72 |
73 | // prefer elements with less nesting
74 | if (anameparts.length < bnameparts.length) return -1;
75 | if (anameparts.length > bnameparts.length) return 1;
76 |
77 | // prefer matches with a shorter name
78 | if (asname.length < bsname.length) return -1;
79 | if (asname.length > bsname.length) return 1;
80 |
81 | // sort the rest alphabetically
82 | if (aname < bname) return -1;
83 | if (aname > bname) return 1;
84 | return 0;
85 | }
86 |
87 | results.sort(compare);
88 |
89 | for (i = 0; i < results.length && i < 100; i++) {
90 | var sym = results[i];
91 |
92 | var el = $(document.createElement("li"));
93 | el.addClass(sym.kind);
94 | for (j in sym.attributes)
95 | el.addClass(sym.attributes[j]);
96 |
97 | var name = sym.name;
98 |
99 | // compute a length limited representation of the full name
100 | var nameparts = name.split(".");
101 | var np = nameparts.length-1;
102 | var shortname = "." + nameparts[np];
103 | while (np > 0 && nameparts[np-1].length + shortname.length <= maxlen) {
104 | np--;
105 | shortname = "." + nameparts[np] + shortname;
106 | }
107 | if (np > 0) shortname = ".." + shortname;
108 | else shortname = shortname.substr(1);
109 |
110 | el.append(''+shortname+'');
111 | $('#symbolSearchResults').append(el);
112 | }
113 |
114 | if (results.length > 100) {
115 | $('#symbolSearchResults').append("…"+(results.length-100)+" additional results");
116 | }
117 |
118 | $('#symbolSearchResults').show();
119 | }
120 |
--------------------------------------------------------------------------------
/docs/public/scripts/mousetrap.js:
--------------------------------------------------------------------------------
1 | /* mousetrap v1.4.6 craig.is/killing/mice */
2 | (function(J,r,f){function s(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent("on"+b,d)}function A(a){if("keypress"==a.type){var b=String.fromCharCode(a.which);a.shiftKey||(b=b.toLowerCase());return b}return h[a.which]?h[a.which]:B[a.which]?B[a.which]:String.fromCharCode(a.which).toLowerCase()}function t(a){a=a||{};var b=!1,d;for(d in n)a[d]?b=!0:n[d]=0;b||(u=!1)}function C(a,b,d,c,e,v){var g,k,f=[],h=d.type;if(!l[a])return[];"keyup"==h&&w(a)&&(b=[a]);for(g=0;gg||h.hasOwnProperty(g)&&(p[h[g]]=g)}e=p[d]?"keydown":"keypress"}"keypress"==e&&f.length&&(e="keydown");return{key:c,modifiers:f,action:e}}function F(a,b,d,c,e){q[a+":"+d]=b;a=a.replace(/\s+/g," ");var f=a.split(" ");1":".","?":"/","|":"\\"},G={option:"alt",command:"meta","return":"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"},p,l={},q={},n={},D,z=!1,I=!1,u=!1;for(f=1;20>f;++f)h[111+f]="f"+f;for(f=0;9>=f;++f)h[f+96]=f;s(r,"keypress",y);s(r,"keydown",y);s(r,"keyup",y);var m={bind:function(a,b,d){a=a instanceof Array?a:[a];for(var c=0;c $null;
169 | popd;
170 | echo "finished.";
171 | }
172 | - ps: SetUpDCompiler
173 | - powershell -Command Invoke-WebRequest https://code.dlang.org/files/dub-1.9.0-windows-x86.zip -OutFile dub.zip
174 | - 7z x dub.zip -odub > nul
175 | - set PATH=%CD%\%binpath%;%CD%\dub;%PATH%
176 | - dub --version
177 | # Some older LDC/GDC compilers don't come with rdmd
178 | - ps: function SetUpRDMD
179 | {
180 | if((Get-Command "rdmd.exe" -ErrorAction SilentlyContinue) -eq $null)
181 | {
182 | Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/2.080.0/dmd.2.080.0.windows.7z" -OutFile "c:\rdmd-dmd.7z";
183 | pushd c:\\;
184 | 7z x -ordmd-dmd rdmd-dmd.7z > $null;
185 | popd;
186 | }
187 | }
188 | - ps: SetUpRDMD
189 | - set PATH=%PATH%;c:\rdmd-dmd\dmd2\windows\bin
190 |
191 | before_build:
192 | - ps: if($env:arch -eq "x86"){
193 | $env:compilersetupargs = "x86";
194 | $env:Darch = "x86";
195 | $env:DConf = "m32";
196 | }elseif($env:arch -eq "x64"){
197 | $env:compilersetupargs = "amd64";
198 | $env:Darch = "x86_64";
199 | $env:DConf = "m64";
200 | }
201 | - ps: $env:compilersetup = "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall";
202 | - '"%compilersetup%" %compilersetupargs%'
203 |
204 | build_script:
205 | - echo dummy build script - dont remove me
206 |
207 | test_script:
208 | - echo %PLATFORM%
209 | - echo %Darch%
210 | - echo %DC%
211 | - echo %DMD%
212 | - echo %PATH%
213 | - '%DC% --help'
214 | - dub add-local .
215 | - dub test --arch=%Darch% --compiler=%DC%
216 |
--------------------------------------------------------------------------------
/src/scriptlike/path/extras.d:
--------------------------------------------------------------------------------
1 | /++
2 | $(H2 Scriptlike $(SCRIPTLIKE_VERSION))
3 |
4 | Extra Scriptlike-only functionality to complement $(MODULE_STD_PATH).
5 |
6 | Copyright: Copyright (C) 2014-2017 Nick Sabalausky
7 | License: zlib/libpng
8 | Authors: Nick Sabalausky
9 | +/
10 | module scriptlike.path.extras;
11 |
12 | import std.algorithm;
13 | import std.conv;
14 | import std.datetime;
15 | import std.file;
16 | import std.process;
17 | import std.range;
18 | import std.stdio;
19 | import std.string;
20 | import std.traits;
21 | import std.typecons;
22 | import std.typetuple;
23 |
24 | static import std.path;
25 | import std.path : dirSeparator, pathSeparator, isDirSeparator,
26 | CaseSensitive, buildPath, buildNormalizedPath;
27 |
28 | import scriptlike.path.wrappers;
29 |
30 | /// Represents a file extension.
31 | struct Ext
32 | {
33 | private string str;
34 |
35 | /// Main constructor.
36 | this(string extension) pure @safe nothrow
37 | {
38 | this.str = extension;
39 | }
40 |
41 | /// Convert to string.
42 | string toString() pure @safe nothrow
43 | {
44 | return str;
45 | }
46 |
47 | /// No longer needed. Use Ext.toString() instead.
48 | deprecated("Use Ext.toString() instead.")
49 | string toRawString() pure @safe nothrow
50 | {
51 | return str;
52 | }
53 |
54 | /// Compare using OS-specific case-sensitivity rules. If you want to force
55 | /// case-sensitive or case-insensitive, then call filenameCmp instead.
56 | int opCmp(ref const Ext other) const
57 | {
58 | return std.path.filenameCmp(this.str, other.str);
59 | }
60 |
61 | ///ditto
62 | int opCmp(Ext other) const
63 | {
64 | return std.path.filenameCmp(this.str, other.str);
65 | }
66 |
67 | ///ditto
68 | int opCmp(string other) const
69 | {
70 | return std.path.filenameCmp(this.str, other);
71 | }
72 |
73 | /// Compare using OS-specific case-sensitivity rules. If you want to force
74 | /// case-sensitive or case-insensitive, then call filenameCmp instead.
75 | int opEquals(ref const Ext other) const
76 | {
77 | return opCmp(other) == 0;
78 | }
79 |
80 | ///ditto
81 | int opEquals(Ext other) const
82 | {
83 | return opCmp(other) == 0;
84 | }
85 |
86 | ///ditto
87 | int opEquals(string other) const
88 | {
89 | return opCmp(other) == 0;
90 | }
91 |
92 | /// Convert to bool
93 | T opCast(T)() if(is(T==bool))
94 | {
95 | return !!str;
96 | }
97 | }
98 |
99 | /// Represents a filesystem path. The path is always kept normalized
100 | /// automatically (as performed by buildNormalizedPathFixed).
101 | struct Path
102 | {
103 | private string str = ".";
104 |
105 | /// Main constructor.
106 | this(string path) pure @safe nothrow
107 | {
108 | this.str = buildNormalizedPathFixed(path);
109 | }
110 |
111 | pure @trusted nothrow invariant()
112 | {
113 | assert(str == buildNormalizedPathFixed(str));
114 | }
115 |
116 | /// Convert to string, quoting or escaping spaces if necessary.
117 | string toString()
118 | {
119 | return .escapeShellArg(str);
120 | }
121 |
122 | /// Returns the underlying string. Does NOT do any escaping, even if path contains spaces.
123 | string raw() const pure @safe nothrow
124 | {
125 | return str;
126 | }
127 |
128 | ///ditto
129 | deprecated("Use Path.raw instead.")
130 | alias toRawString = raw;
131 |
132 | /// Concatenates two paths, with a directory separator in between.
133 | Path opBinary(string op)(Path rhs) if(op=="~")
134 | {
135 | Path newPath;
136 | newPath.str = buildNormalizedPathFixed(this.str, rhs.str);
137 | return newPath;
138 | }
139 |
140 | ///ditto
141 | Path opBinary(string op)(string rhs) if(op=="~")
142 | {
143 | Path newPath;
144 | newPath.str = buildNormalizedPathFixed(this.str, rhs);
145 | return newPath;
146 | }
147 |
148 | ///ditto
149 | Path opBinaryRight(string op)(string lhs) if(op=="~")
150 | {
151 | Path newPath;
152 | newPath.str = buildNormalizedPathFixed(lhs, this.str);
153 | return newPath;
154 | }
155 |
156 | /// Appends an extension to a path. Naturally, a directory separator
157 | /// is NOT inserted in between.
158 | Path opBinary(string op)(Ext rhs) if(op=="~")
159 | {
160 | Path newPath;
161 | newPath.str = std.path.setExtension(this.str, rhs.str);
162 | return newPath;
163 | }
164 |
165 | /// Appends a path to this one, with a directory separator in between.
166 | Path opOpAssign(string op)(Path rhs) if(op=="~")
167 | {
168 | str = buildNormalizedPathFixed(str, rhs.str);
169 | return this;
170 | }
171 |
172 | ///ditto
173 | Path opOpAssign(string op)(string rhs) if(op=="~")
174 | {
175 | str = buildNormalizedPathFixed(str, rhs);
176 | return this;
177 | }
178 |
179 | /// Appends an extension to this path. Naturally, a directory separator
180 | /// is NOT inserted in between.
181 | Path opOpAssign(string op)(Ext rhs) if(op=="~")
182 | {
183 | str = std.path.setExtension(str, rhs.str);
184 | return this;
185 | }
186 |
187 | /// Compare using OS-specific case-sensitivity rules. If you want to force
188 | /// case-sensitive or case-insensitive, then call filenameCmp instead.
189 | int opCmp(ref const Path other) const
190 | {
191 | return std.path.filenameCmp(this.str, other.str);
192 | }
193 |
194 | ///ditto
195 | int opCmp(Path other) const
196 | {
197 | return std.path.filenameCmp(this.str, other.str);
198 | }
199 |
200 | ///ditto
201 | int opCmp(string other) const
202 | {
203 | return std.path.filenameCmp(this.str, other);
204 | }
205 |
206 | /// Compare using OS-specific case-sensitivity rules. If you want to force
207 | /// case-sensitive or case-insensitive, then call filenameCmp instead.
208 | int opEquals(ref const Path other) const
209 | {
210 | return opCmp(other) == 0;
211 | }
212 |
213 | ///ditto
214 | int opEquals(Path other) const
215 | {
216 | return opCmp(other) == 0;
217 | }
218 |
219 | ///ditto
220 | int opEquals(string other) const
221 | {
222 | return opCmp(other) == 0;
223 | }
224 |
225 | /// Convert to bool
226 | T opCast(T)() if(is(T==bool))
227 | {
228 | return !!str;
229 | }
230 |
231 | /// Returns the parent path, according to $(FULL_STD_PATH dirName).
232 | @property Path up()
233 | {
234 | return this.dirName();
235 | }
236 |
237 | /// Is this path equal to empty string?
238 | @property bool empty()
239 | {
240 | return str == "";
241 | }
242 | }
243 |
244 | /// Convenience alias
245 | alias extOf = extension;
246 | alias stripExt = stripExtension; ///ditto
247 | alias setExt = setExtension; ///ditto
248 | alias defaultExt = defaultExtension; ///ditto
249 |
250 | /// Like buildNormalizedPath, but if the result is the current directory,
251 | /// this returns "." instead of "". However, if all the inputs are "", or there
252 | /// are no inputs, this still returns "" just like buildNormalizedPath.
253 | ///
254 | /// Also, unlike buildNormalizedPath, this converts back/forward slashes to
255 | /// native on BOTH Windows and Posix, not just on Windows.
256 | string buildNormalizedPathFixed(string[] paths...)
257 | @trusted pure nothrow
258 | {
259 | if(all!`a is null`(paths))
260 | return null;
261 |
262 | if(all!`a==""`(paths))
263 | return "";
264 |
265 | auto result = std.path.buildNormalizedPath(paths);
266 |
267 | version(Posix) result = result.replace(`\`, `/`);
268 | else version(Windows) { /+ do nothing +/ }
269 | else static assert(0);
270 |
271 | return result==""? "." : result;
272 | }
273 |
274 | /// Properly escape arguments containing spaces for the command shell, if necessary.
275 | ///
276 | /// Although Path doesn't strictly need this (since Path.toString automatically
277 | /// calls this anyway), an overload of escapeShellArg which accepts a Path is
278 | /// provided for the sake of generic code.
279 | const(string) escapeShellArg(in string str)
280 | {
281 | if(str.canFind(' '))
282 | {
283 | version(Windows)
284 | return escapeWindowsArgument(str);
285 | else version(Posix)
286 | return escapeShellFileName(str);
287 | else
288 | static assert(0, "This platform not supported.");
289 | }
290 | else
291 | return str;
292 | }
293 |
294 | ///ditto
295 | string escapeShellArg(Path path)
296 | {
297 | return path.toString();
298 | }
299 |
300 |
--------------------------------------------------------------------------------
/src/scriptlike/path/package.d:
--------------------------------------------------------------------------------
1 | /++
2 | $(H2 Scriptlike $(SCRIPTLIKE_VERSION))
3 |
4 | Extra Scriptlike-only functionality to complement and wrap $(MODULE_STD_PATH),
5 | providing extra functionality, such as no-fail "try*" alternatives, and support
6 | for Scriptlike's $(API_PATH_EXTR Path), command echoing and dry-run features.
7 |
8 | Modules:
9 | $(UL
10 | $(LI $(MODULE_PATH_EXTR) )
11 | $(LI $(MODULE_PATH_WRAP) )
12 | )
13 |
14 | Copyright: Copyright (C) 2014-2017 Nick Sabalausky
15 | License: zlib/libpng
16 | Authors: Nick Sabalausky
17 | +/
18 | module scriptlike.path;
19 |
20 | public import scriptlike.path.extras;
21 | public import scriptlike.path.wrappers;
22 |
23 | // The unittests in this module mainly check that all the templates compile
24 | // correctly and that the appropriate Phobos functions are correctly called.
25 | //
26 | // A completely thorough testing of the behavior of such functions is
27 | // occasionally left to Phobos itself as it is outside the scope of these tests.
28 | version(unittest_scriptlike_d)
29 | unittest
30 | {
31 | import std.algorithm;
32 | import std.conv;
33 | import std.datetime;
34 | import std.file;
35 | import std.path : dirSeparator;
36 | import std.process;
37 | import std.range;
38 | import std.stdio;
39 | import std.string;
40 | import std.traits;
41 | import std.typecons;
42 | import std.typetuple;
43 |
44 | import std.stdio : writeln;
45 | writeln("Running Scriptlike unittests: std.path wrappers");
46 |
47 | alias dirSep = dirSeparator;
48 |
49 | {
50 | auto e = Ext(".txt");
51 | assert(e != Ext(".dat"));
52 | assert(e == Ext(".txt"));
53 | version(Windows)
54 | assert(e == Ext(".TXT"));
55 | else version(OSX)
56 | assert(e == Ext(".TXT"));
57 | else version(Posix)
58 | assert(e != Ext(".TXT"));
59 | else
60 | static assert(0, "This platform not supported.");
61 |
62 | // Test the other comparison overloads
63 | assert(e != Ext(".dat"));
64 | assert(e == Ext(".txt"));
65 | assert(Ext(".dat") != e);
66 | assert(Ext(".txt") == e);
67 | assert(".dat" != e);
68 | assert(".txt" == e);
69 |
70 | assert(Ext("foo"));
71 | assert(Ext(""));
72 | assert(Ext(null).toString() is null);
73 | assert(!Ext(null));
74 | }
75 |
76 | auto p = Path();
77 | assert(p.raw == ".");
78 | assert(!p.empty);
79 |
80 | assert(Path("").empty);
81 |
82 | assert(Path("foo"));
83 | assert(Path(""));
84 | assert(Path(null).raw is null);
85 | assert(!Path(null));
86 |
87 | version(Windows)
88 | auto testStrings = ["/foo/bar", "/foo/bar/", `\foo\bar`, `\foo\bar\`];
89 | else version(Posix)
90 | auto testStrings = ["/foo/bar", "/foo/bar/"];
91 | else
92 | static assert(0, "This platform not supported.");
93 |
94 | foreach(str; testStrings)
95 | {
96 | writeln(" testing str: ", str);
97 |
98 | p = Path(str);
99 | assert(!p.empty);
100 | assert(p.raw == dirSep~"foo"~dirSep~"bar");
101 |
102 | p = Path(str);
103 | assert(p.raw == dirSep~"foo"~dirSep~"bar");
104 | assert(p.raw == p.raw);
105 | assert(p.toString() == p.raw.to!string());
106 |
107 | assert(p.up.toString() == dirSep~"foo");
108 | assert(p.up.up.toString() == dirSep);
109 |
110 | assert((p~"sub").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub");
111 | assert((p~"sub"~"2").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub"~dirSep~"2");
112 | assert((p~Path("sub")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"sub");
113 |
114 | version(Windows)
115 | assert((p~"sub dir").toString() == `"`~dirSep~"foo"~dirSep~"bar"~dirSep~"sub dir"~`"`);
116 | else version(Posix)
117 | assert((p~"sub dir").toString() == `'`~dirSep~"foo"~dirSep~"bar"~dirSep~`sub dir'`);
118 | else
119 | static assert(0, "This platform not supported.");
120 |
121 | assert(("dir"~p).toString() == dirSep~"foo"~dirSep~"bar");
122 | assert(("dir"~Path(str[1..$])).toString() == "dir"~dirSep~"foo"~dirSep~"bar");
123 |
124 | p ~= "blah";
125 | assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah");
126 |
127 | p ~= Path("more");
128 | assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah"~dirSep~"more");
129 |
130 | p ~= "..";
131 | assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"blah");
132 |
133 | p ~= Path("..");
134 | assert(p.toString() == dirSep~"foo"~dirSep~"bar");
135 |
136 | p ~= "sub dir";
137 | p ~= "..";
138 | assert(p.toString() == dirSep~"foo"~dirSep~"bar");
139 |
140 | p ~= "filename";
141 | assert((p~Ext(".txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
142 | assert((p~Ext("txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
143 | assert((p~Ext("")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename");
144 |
145 | p ~= Ext(".ext");
146 | assert(p.toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
147 | assert(p.baseName().toString() == "filename.ext");
148 | assert(p.dirName().toString() == dirSep~"foo"~dirSep~"bar");
149 | assert(p.rootName().toString() == dirSep);
150 | assert(p.driveName().toString() == "");
151 | assert(p.stripDrive().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
152 | version(Windows)
153 | {
154 | assert(( Path("C:"~p.raw) ).toString() == "C:"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
155 | assert(( Path("C:"~p.raw) ).stripDrive().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
156 | }
157 | assert(p.extension().toString() == ".ext");
158 | assert(p.stripExtension().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename");
159 | assert(p.setExtension(".txt").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
160 | assert(p.setExtension("txt").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
161 | assert(p.setExtension("").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename");
162 | assert(p.setExtension(Ext(".txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
163 | assert(p.setExtension(Ext("txt")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.txt");
164 | assert(p.setExtension(Ext("")).toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename");
165 |
166 | assert(p.defaultExtension(".dat").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
167 | assert(p.stripExtension().defaultExtension(".dat").toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.dat");
168 |
169 | assert(equal(p.pathSplitter(), [dirSep, "foo", "bar", "filename.ext"]));
170 |
171 | assert(p.isRooted());
172 | version(Windows)
173 | assert(!p.isAbsolute());
174 | else version(Posix)
175 | assert(p.isAbsolute());
176 | else
177 | static assert(0, "This platform not supported.");
178 |
179 | assert(!( Path("dir"~p.raw) ).isRooted());
180 | assert(!( Path("dir"~p.raw) ).isAbsolute());
181 |
182 | version(Windows)
183 | {
184 | assert(( Path("dir"~p.raw) ).absolutePath("C:/main").toString() == "C:"~dirSep~"main"~dirSep~"dir"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
185 | assert(( Path("C:"~p.raw) ).relativePath("C:/foo").toString() == "bar"~dirSep~"filename.ext");
186 | assert(( Path("C:"~p.raw) ).relativePath("C:/foo/bar").toString() == "filename.ext");
187 | }
188 | else version(Posix)
189 | {
190 | assert(( Path("dir"~p.raw) ).absolutePath("/main").toString() == dirSep~"main"~dirSep~"dir"~dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
191 | assert(p.relativePath("/foo").toString() == "bar"~dirSep~"filename.ext");
192 | assert(p.relativePath("/foo/bar").toString() == "filename.ext");
193 | }
194 | else
195 | static assert(0, "This platform not supported.");
196 |
197 | assert(p.filenameCmp(dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext") == 0);
198 | assert(p.filenameCmp(dirSep~"faa"~dirSep~"bat"~dirSep~"filename.ext") != 0);
199 | assert(p.globMatch("*foo*name.ext"));
200 | assert(!p.globMatch("*foo*Bname.ext"));
201 |
202 | assert(!p.isValidFilename());
203 | assert(p.baseName().isValidFilename());
204 | assert(p.isValidPath());
205 |
206 | assert(p.expandTilde().toString() == dirSep~"foo"~dirSep~"bar"~dirSep~"filename.ext");
207 |
208 | assert(p != Path("/dir/subdir/filename.ext"));
209 | assert(p == Path("/foo/bar/filename.ext"));
210 | version(Windows)
211 | assert(p == Path("/FOO/BAR/FILENAME.EXT"));
212 | else version(OSX)
213 | assert(p == Path("/FOO/BAR/FILENAME.EXT"));
214 | else version(Posix)
215 | assert(p != Path("/FOO/BAR/FILENAME.EXT"));
216 | else
217 | static assert(0, "This platform not supported.");
218 |
219 | // Test the other comparison overloads
220 | assert(p != Path("/dir/subdir/filename.ext"));
221 | assert(p == Path("/foo/bar/filename.ext"));
222 | assert(Path("/dir/subdir/filename.ext") != p);
223 | assert(Path("/foo/bar/filename.ext") == p);
224 | assert("/dir/subdir/filename.ext" != p);
225 | assert("/foo/bar/filename.ext" == p);
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/src/scriptlike/file/package.d:
--------------------------------------------------------------------------------
1 | /++
2 | $(H2 Scriptlike $(SCRIPTLIKE_VERSION))
3 |
4 | Extra Scriptlike-only functionality to complement and wrap $(MODULE_STD_FILE),
5 | providing extra functionality, such as no-fail "try*" alternatives, and support
6 | for Scriptlike's $(API_PATH_EXTR Path), command echoing and dry-run features.
7 |
8 | Modules:
9 | $(UL
10 | $(LI $(MODULE_FILE_EXTR) )
11 | $(LI $(MODULE_FILE_WRAP) )
12 | )
13 |
14 | Copyright: Copyright (C) 2014-2017 Nick Sabalausky
15 | License: zlib/libpng
16 | Authors: Nick Sabalausky
17 | +/
18 | module scriptlike.file;
19 |
20 | public import scriptlike.file.extras;
21 | public import scriptlike.file.wrappers;
22 |
23 | version(unittest_scriptlike_d)
24 | unittest
25 | {
26 | import std.algorithm : equal;
27 | import std.conv;
28 | import std.datetime : SysTime;
29 | static import std.file;
30 | static import std.path;
31 | import std.string;
32 | import std.traits;
33 | import std.typecons;
34 |
35 | import scriptlike.path;
36 | import scriptlike.core : tmpName;
37 |
38 | import std.stdio : writeln;
39 | import std.process : thisProcessID;
40 | alias copy = scriptlike.file.wrappers.copy;
41 |
42 | writeln("Running Scriptlike unittests: std.file wrappers");
43 |
44 | immutable tempname1 = tmpName("1");
45 | immutable tempname2 = tmpName("2");
46 | immutable tempname3 = tmpName("3", "somefile");
47 | auto tempPath = Path(tempname1);
48 | auto tempPath2 = Path(tempname2);
49 | auto tempPath3 = Path(tempname3);
50 |
51 | void testA(T)(T tempPath)
52 | {
53 | scope(exit)
54 | {
55 | if(std.file.exists(tempname1)) std.file.remove(tempname1);
56 | }
57 |
58 | tempPath.write("stuff");
59 |
60 | tempPath.append(" more");
61 | assert(tempPath.read(3) == "stu");
62 | assert(tempPath.read() == "stuff more");
63 | assert(tempPath.readText() == "stuff more");
64 | assert(tempPath.getSize() == 10);
65 |
66 | auto parsed = tempPath.slurp!(string, string)("%s %s");
67 | assert(equal(parsed, [tuple("stuff", "more")]));
68 |
69 | SysTime timeA, timeB, timeC;
70 | tempPath.getTimes(timeA, timeB);
71 | version(Windows)
72 | tempPath.getTimesWin(timeA, timeB, timeC);
73 | tempPath.setTimes(timeA, timeB);
74 | timeA = tempPath.timeLastModified();
75 | timeA = tempPath.timeLastModified(timeB);
76 |
77 | uint attr;
78 | attr = tempPath.getAttributes();
79 | attr = tempPath.getLinkAttributes();
80 |
81 | assert(tempPath.exists());
82 | assert(tempPath.isFile());
83 | assert(tempPath.existsAsFile());
84 | assert(!tempPath.isDir());
85 | assert(!tempPath.existsAsDir());
86 | assert(!tempPath.isSymlink());
87 | assert(!tempPath.existsAsSymlink());
88 | tempPath.remove();
89 | assert(!tempPath.exists());
90 | assert(!tempPath.existsAsFile());
91 | assert(!tempPath.existsAsDir());
92 | assert(!tempPath.existsAsSymlink());
93 | }
94 |
95 | import std.stdio : stdout;
96 | writeln(" testA with string"); stdout.flush();
97 | testA(tempPath.raw); // Test with string
98 |
99 | writeln(" testA with Path"); stdout.flush();
100 | testA(tempPath); // Test with Path
101 |
102 | writeln(" more..."); stdout.flush();
103 | {
104 | assert(!tempPath.exists());
105 | assert(!tempPath2.exists());
106 |
107 | scope(exit)
108 | {
109 | if(std.file.exists(tempname1)) std.file.remove(tempname1);
110 | if(std.file.exists(tempname2)) std.file.remove(tempname2);
111 | }
112 | tempPath.write("ABC");
113 |
114 | assert(tempPath.existsAsFile());
115 | assert(!tempPath2.exists());
116 |
117 | tempPath.rename(tempPath2);
118 |
119 | assert(!tempPath.exists());
120 | assert(tempPath2.existsAsFile());
121 |
122 | tempPath2.copy(tempPath);
123 |
124 | assert(tempPath.existsAsFile());
125 | assert(tempPath2.existsAsFile());
126 | }
127 |
128 | {
129 | scope(exit)
130 | {
131 | if(std.file.exists(tempname1)) std.file.rmdir(tempname1);
132 | if(std.file.exists(tempname3)) std.file.rmdir(tempname3);
133 | if(std.file.exists( std.path.dirName(tempname3) )) std.file.rmdir( std.path.dirName(tempname3) );
134 | }
135 |
136 | assert(!tempPath.exists());
137 | assert(!tempPath3.exists());
138 |
139 | tempPath.mkdir();
140 | assert(tempPath.exists());
141 | assert(!tempPath.isFile());
142 | assert(!tempPath.existsAsFile());
143 | assert(tempPath.isDir());
144 | assert(tempPath.existsAsDir());
145 | assert(!tempPath.isSymlink());
146 | assert(!tempPath.existsAsSymlink());
147 |
148 | tempPath3.mkdirRecurse();
149 | assert(tempPath3.exists());
150 | assert(!tempPath3.isFile());
151 | assert(!tempPath3.existsAsFile());
152 | assert(tempPath3.isDir());
153 | assert(tempPath3.existsAsDir());
154 | assert(!tempPath3.isSymlink());
155 | assert(!tempPath3.existsAsSymlink());
156 |
157 | auto saveDirName = std.file.getcwd();
158 | auto saveDir = Path(saveDirName);
159 | scope(exit) chdir(saveDirName);
160 |
161 | tempPath.chdir();
162 | assert(getcwd() == tempname1);
163 | saveDir.chdir();
164 | assert(getcwd() == saveDirName);
165 |
166 | auto entries1 = (tempPath3~"..").dirEntries(SpanMode.shallow);
167 | assert(!entries1.empty);
168 | auto entries2 = (tempPath3~"..").dirEntries("*", SpanMode.shallow);
169 | assert(!entries2.empty);
170 | auto entries3 = (tempPath3~"..").dirEntries("TUNA TUNA THIS DOES NOT EXIST TUNA WHEE", SpanMode.shallow);
171 | assert(entries3.empty);
172 |
173 | tempPath.rmdir();
174 | assert(!tempPath.exists());
175 | assert(!tempPath.existsAsFile());
176 | assert(!tempPath.existsAsDir());
177 | assert(!tempPath.existsAsSymlink());
178 |
179 | tempPath3.rmdirRecurse();
180 | assert(!tempPath.exists());
181 | assert(!tempPath.existsAsFile());
182 | assert(!tempPath.existsAsDir());
183 | assert(!tempPath.existsAsSymlink());
184 | }
185 |
186 | {
187 | version(Posix)
188 | {
189 | assert(!tempPath.exists());
190 | assert(!tempPath2.exists());
191 |
192 | scope(exit)
193 | {
194 | if(std.file.exists(tempname2)) std.file.remove(tempname2);
195 | if(std.file.exists(tempname1)) std.file.remove(tempname1);
196 | }
197 | tempPath.write("DEF");
198 |
199 | tempPath.symlink(tempPath2);
200 | assert(tempPath2.exists());
201 | assert(tempPath2.isFile());
202 | assert(tempPath2.existsAsFile());
203 | assert(!tempPath2.isDir());
204 | assert(!tempPath2.existsAsDir());
205 | assert(tempPath2.isSymlink());
206 | assert(tempPath2.existsAsSymlink());
207 |
208 | auto linkTarget = tempPath2.readLink();
209 | assert(linkTarget.raw == tempname1);
210 | }
211 | }
212 |
213 | {
214 | assert(!tempPath.exists());
215 |
216 | scope(exit)
217 | {
218 | if(std.file.exists(tempname1)) std.file.remove(tempname1);
219 | }
220 |
221 | import scriptlike.process;
222 | run(`echo TestScriptStuff > `~tempPath.to!string());
223 | assert(tempPath.exists());
224 | assert(tempPath.isFile());
225 | assert((cast(string)tempPath.read()).strip() == "TestScriptStuff");
226 | tempPath.remove();
227 | assert(!tempPath.exists());
228 |
229 | auto errlevel = tryRun(`echo TestScriptStuff > `~tempPath.to!string());
230 | assert(tempPath.exists());
231 | assert(tempPath.isFile());
232 | assert((cast(string)tempPath.read()).strip() == "TestScriptStuff");
233 | assert(errlevel == 0);
234 | tempPath.remove();
235 | assert(!tempPath.exists());
236 |
237 | import scriptlike.process;
238 | getcwd().run(`echo TestScriptStuff > `~tempPath.to!string());
239 | getcwd().tryRun(`echo TestScriptStuff > `~tempPath.to!string());
240 | }
241 |
242 | {
243 | assert(!tempPath3.exists());
244 | assert(!tempPath3.up.exists());
245 |
246 | scope(exit)
247 | {
248 | if(std.file.exists(tempname3)) std.file.remove(tempname3);
249 | if(std.file.exists( std.path.dirName(tempname3) )) std.file.rmdir( std.path.dirName(tempname3) );
250 | }
251 |
252 | tempPath3.up.mkdir();
253 | assert(tempPath3.up.exists());
254 | assert(tempPath3.up.isDir());
255 |
256 | import scriptlike.process;
257 | tempPath3.up.run(`echo MoreTestStuff > `~tempPath3.baseName().to!string());
258 | assert(tempPath3.exists());
259 | assert(tempPath3.isFile());
260 | assert((cast(string)tempPath3.read()).strip() == "MoreTestStuff");
261 | }
262 |
263 | {
264 | scope(exit)
265 | {
266 | if(std.file.exists(tempname1)) std.file.rmdir(tempname1);
267 | if(std.file.exists(tempname3)) std.file.rmdir(tempname3);
268 | if(std.file.exists( std.path.dirName(tempname3) )) std.file.rmdir( std.path.dirName(tempname3) );
269 | }
270 |
271 | assert(!tempPath.exists());
272 | assert(!tempPath3.exists());
273 |
274 | assert(!tempPath.tryRmdir());
275 | assert(!tempPath.tryRmdirRecurse());
276 | assert(!tempPath.tryRemove());
277 | assert(!tempPath.tryRename(tempPath3));
278 | version(Posix) assert(!tempPath.trySymlink(tempPath3));
279 | assert(!tempPath.tryCopy(tempPath3));
280 |
281 | assert(tempPath.tryMkdir());
282 | assert(tempPath.exists());
283 | assert(!tempPath.tryMkdir());
284 | assert(!tempPath.tryMkdirRecurse());
285 |
286 | assert(tempPath.tryRmdir());
287 | assert(!tempPath.exists());
288 |
289 | assert(tempPath.tryMkdirRecurse());
290 | assert(tempPath.exists());
291 | assert(!tempPath.tryMkdirRecurse());
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/src/scriptlike/interact.d:
--------------------------------------------------------------------------------
1 | /**
2 | * Handling of interaction with users via standard input.
3 | *
4 | * Provides functions for simple and common interactions with users in
5 | * the form of question and answer.
6 | *
7 | * Copyright: Copyright Jesse Phillips 2010
8 | * License: $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/LICENSE.txt, zlib/libpng)
9 | * Authors: Jesse Phillips
10 | *
11 | * Synopsis:
12 | *
13 | * --------
14 | * import scriptlike.interact;
15 | *
16 | * auto age = userInput!int("Please Enter your age");
17 | *
18 | * if(userInput!bool("Do you want to continue?"))
19 | * {
20 | * auto outputFolder = pathLocation("Where you do want to place the output?");
21 | * auto color = menu!string("What color would you like to use?", ["Blue", "Green"]);
22 | * }
23 | *
24 | * auto num = require!(int, "a > 0 && a <= 10")("Enter a number from 1 to 10");
25 | * --------
26 | */
27 | module scriptlike.interact;
28 |
29 | import std.conv;
30 | import std.file;
31 | import std.functional;
32 | import std.range;
33 | import std.stdio;
34 | import std.string;
35 | import std.traits;
36 |
37 | /**
38 | * The $(D userInput) function provides a means to accessing a single
39 | * value from the user. Each invocation outputs a provided
40 | * statement/question and takes an entire line of input. The result is then
41 | * converted to the requested type; default is a string.
42 | *
43 | * --------
44 | * auto name = userInput("What is your name");
45 | * //or
46 | * string name;
47 | * userInput("What is your name", name);
48 | * --------
49 | *
50 | * Returns: User response as type T.
51 | *
52 | * Where type is bool:
53 | *
54 | * true on "ok", "continue",
55 | * and if the response starts with 'y' or 'Y'.
56 | *
57 | * false on all other input, include no response (will not throw).
58 | *
59 | * Throws: $(D NoInputException) if the user does not enter anything.
60 | * $(D ConvError) when the string could not be converted to the desired type.
61 | */
62 | T userInput(T = string)(string question = "")
63 | {
64 | write(question ~ "\n> ");
65 | stdout.flush;
66 | auto ans = readln();
67 |
68 | static if(is(T == bool))
69 | {
70 | switch(ans.front)
71 | {
72 | case 'y', 'Y':
73 | return true;
74 | default:
75 | }
76 | switch(ans.strip())
77 | {
78 | case "continue":
79 | case "ok":
80 | return true;
81 | default:
82 | return false;
83 | }
84 | } else
85 | {
86 | if(ans == "\x0a")
87 | throw new NoInputException("Value required, "~
88 | "cannot continue operation.");
89 | static if(isSomeChar!T)
90 | {
91 | return to!(T)(ans[0]);
92 | } else
93 | return to!(T)(ans.strip());
94 | }
95 | }
96 |
97 | ///ditto
98 | void userInput(T = string)(string question, ref T result)
99 | {
100 | result = userInput!T(question);
101 | }
102 |
103 | version(unittest_scriptlike_d)
104 | unittest
105 | {
106 | mixin(selfCom(["10PM", "9PM"]));
107 | mixin(selfCom());
108 | auto s = userInput("What time is it?");
109 | assert(s == "10PM", "Expected 10PM got" ~ s);
110 | outfile.rewind;
111 | assert(outfile.readln().strip == "What time is it?");
112 |
113 | outfile.rewind;
114 | userInput("What time?", s);
115 | assert(s == "9PM", "Expected 9PM got" ~ s);
116 | outfile.rewind;
117 | assert(outfile.readln().strip == "What time?");
118 | }
119 |
120 | /**
121 | * Pauses and prompts the user to press Enter (or "Return" on OSX).
122 | *
123 | * This is similar to the Windows command line's PAUSE command.
124 | *
125 | * --------
126 | * pause();
127 | * pause("Thanks. Please press Enter again...");
128 | * --------
129 | */
130 | void pause(string prompt = defaultPausePrompt)
131 | {
132 | //TODO: This works, but needs a little work. Currently, it echoes all
133 | // input until Enter is pressed. Fixing that requires some low-level
134 | // os-specific work.
135 | //
136 | // For reference:
137 | // http://stackoverflow.com/questions/6856635/hide-password-input-on-terminal
138 | // http://linux.die.net/man/3/termios
139 |
140 | write(prompt);
141 | stdout.flush();
142 | getchar();
143 | }
144 |
145 | version(OSX)
146 | enum defaultPausePrompt = "Press Return to continue..."; ///
147 | else
148 | enum defaultPausePrompt = "Press Enter to continue..."; ///
149 |
150 |
151 | /**
152 | * Gets a valid path folder from the user. The string will not contain
153 | * quotes, if you are using in a system call and the path contain spaces
154 | * wrapping in quotes may be required.
155 | *
156 | * --------
157 | * auto confFile = pathLocation("Where is the configuration file?");
158 | * --------
159 | *
160 | * Throws: NoInputException if the user does not provide a path.
161 | */
162 | string pathLocation(string action)
163 | {
164 | import std.algorithm;
165 | import std.utf : byChar;
166 | string ans;
167 |
168 | do
169 | {
170 | if(ans !is null)
171 | writeln("Could not locate that file.");
172 | ans = userInput(action);
173 | // Quotations will generally cause problems when
174 | // using the path with std.file and Windows. This removes the quotes.
175 | ans = std.string.strip( ans.byChar.filter!(a => a != '"' && a != ';').array );
176 | //ans = ans.removechars("\";").strip();
177 | //ans = ans[0] == '"' ? ans[1..$] : ans; // removechars skips first char
178 | } while(!exists(ans));
179 |
180 | return ans;
181 | }
182 |
183 | /**
184 | * Creates a menu from a Range of strings.
185 | *
186 | * It will require that a number is selected within the number of options.
187 | *
188 | * If the the return type is a string, the string in the options parameter will
189 | * be returned.
190 | *
191 | * Throws: NoInputException if the user wants to quit.
192 | */
193 | T menu(T = ElementType!(Range), Range) (string question, Range options)
194 | if((is(T==ElementType!(Range)) || is(T==int)) &&
195 | isForwardRange!(Range))
196 | {
197 | string ans;
198 | int maxI;
199 | int i;
200 |
201 | while(true)
202 | {
203 | writeln(question);
204 | i = 0;
205 | foreach(str; options)
206 | {
207 | writefln("%8s. %s", i+1, str);
208 | i++;
209 | }
210 | maxI = i+1;
211 |
212 | writefln("%8s. %s", "No Input", "Quit");
213 | ans = userInput!(string)("").strip();
214 | int ians;
215 |
216 | try
217 | {
218 | ians = to!(int)(ans);
219 | } catch(ConvException ce)
220 | {
221 | bool found;
222 | i = 0;
223 | foreach(o; options)
224 | {
225 | if(ans.toLower() == to!string(o).toLower())
226 | {
227 | found = true;
228 | ians = i+1;
229 | break;
230 | }
231 | i++;
232 | }
233 | if(!found)
234 | throw ce;
235 |
236 | }
237 |
238 | if(ians > 0 && ians <= maxI)
239 | static if(is(T==ElementType!(Range)))
240 | static if(isRandomAccessRange!(Range))
241 | return options[ians-1];
242 | else
243 | {
244 | take!(ians-1)(options);
245 | return options.front;
246 | }
247 | else
248 | return ians;
249 | else
250 | writeln("You did not select a valid entry.");
251 | }
252 | }
253 |
254 | version(unittest_scriptlike_d)
255 | unittest
256 | {
257 | mixin(selfCom(["1","Green", "green","2"]));
258 | mixin(selfCom());
259 | auto color = menu!string("What color?", ["Blue", "Green"]);
260 | assert(color == "Blue", "Expected Blue got " ~ color);
261 |
262 | auto ic = menu!int("What color?", ["Blue", "Green"]);
263 | assert(ic == 2, "Expected 2 got " ~ ic.to!string);
264 |
265 | color = menu!string("What color?", ["Blue", "Green"]);
266 | assert(color == "Green", "Expected Green got " ~ color);
267 |
268 | color = menu!string("What color?", ["Blue", "Green"]);
269 | assert(color == "Green", "Expected Green got " ~ color);
270 | outfile.rewind;
271 | assert(outfile.readln().strip == "What color?");
272 | }
273 |
274 |
275 | /**
276 | * Requires that a value be provided and valid based on
277 | * the delegate passed in. It must also check against null input.
278 | *
279 | * --------
280 | * auto num = require!(int, "a > 0 && a <= 10")("Enter a number from 1 to 10");
281 | * --------
282 | *
283 | * Throws: NoInputException if the user does not provide any value.
284 | * ConvError if the user does not provide any value.
285 | */
286 | T require(T, alias cond)(in string question, in string failure = null)
287 | {
288 | alias unaryFun!(cond) call;
289 | T ans;
290 | while(1)
291 | {
292 | ans = userInput!T(question);
293 | if(call(ans))
294 | break;
295 | if(failure)
296 | writeln(failure);
297 | }
298 |
299 | return ans;
300 | }
301 |
302 | version(unittest_scriptlike_d)
303 | unittest
304 | {
305 | mixin(selfCom(["1","11","3"]));
306 | mixin(selfCom());
307 | auto num = require!(int, "a > 0 && a <= 10")("Enter a number from 1 to 10");
308 | assert(num == 1, "Expected 1 got" ~ num.to!string);
309 | num = require!(int, "a > 0 && a <= 10")("Enter a number from 1 to 10");
310 | assert(num == 3, "Expected 1 got" ~ num.to!string);
311 | outfile.rewind;
312 | assert(outfile.readln().strip == "Enter a number from 1 to 10");
313 | }
314 |
315 |
316 | /**
317 | * Used when input was not provided.
318 | */
319 | class NoInputException: Exception
320 | {
321 | this(string msg)
322 | {
323 | super(msg);
324 | }
325 | }
326 |
327 | version(unittest_scriptlike_d)
328 | private string selfCom()
329 | {
330 | string ans = q{
331 | auto outfile = File.tmpfile();
332 | auto origstdout = stdout;
333 | scope(exit) stdout = origstdout;
334 | stdout = outfile;};
335 |
336 | return ans;
337 | }
338 |
339 | version(unittest_scriptlike_d)
340 | private string selfCom(string[] input)
341 | {
342 | string ans = q{
343 | auto infile = File.tmpfile();
344 | auto origstdin = stdin;
345 | scope(exit) stdin = origstdin;
346 | stdin = infile;};
347 |
348 | foreach(i; input)
349 | ans ~= "infile.writeln(`"~i~"`);";
350 | ans ~= "infile.rewind;";
351 |
352 | return ans;
353 | }
354 |
--------------------------------------------------------------------------------
/ddoc/changelog.d:
--------------------------------------------------------------------------------
1 | /++
2 | Scriptlike_Changelog:
3 |
4 | The latest version of this changelog is always available at:$(BR)
5 | $(LINK http://semitwist.com/scriptlike/changelog.html)
6 |
7 | (Dates below are YYYY/MM/DD)
8 |
9 | $(H2 v0.10.3 - 2019/07/15)
10 |
11 | Maintenance release.
12 |
13 | $(UL
14 | $(CHANGE
15 | Fixed deprecations for DMD 2.075+ and 2.079+. Dropped support for LDC 0.15.1.
16 | )
17 | $(TESTS
18 | Added Windows CI testing via
19 | $(LINK2 https://ci.appveyor.com/project/Abscissa/scriptlike, AppVeyor).
20 | )
21 | )
22 |
23 | $(H2 v0.10.2 - 2017/03/03)
24 |
25 | $(UL
26 | $(ENHANCE
27 | Added $(API_CORE trace) functions as debugging aid. Outputs
28 | file/line info and optionally a variable name/value.
29 | )
30 | $(ENHANCE
31 | Added $(API_FILE_EXTR isUserExec), $(API_FILE_EXTR isGroupExec)
32 | and $(API_FILE_EXTR isWorldExec) to check a file's executable bits on Posix.
33 | )
34 | $(FIXED
35 | $(ISSUE 34): Unable to build docs of own project with DUB.
36 | )
37 | $(FIXED
38 | Make sure the example tests, when run in travis-ci, always use
39 | the current scriptlike commit, instead of using a scriptlike release
40 | from the published dub repos.
41 | )
42 | $(FIXED
43 | Docs weren't being correctly built for $(API_FILE_WRAP symlink),
44 | $(API_FILE_WRAP readLink), $(API_FILE_WRAP getTimesWin) and $(API_FILE_EXTR trySymlink).
45 | )
46 | $(CHANGE
47 | Removed outdated, messy and problematic "plain script" example.
48 | )
49 | )
50 |
51 | $(H2 v0.10.1 - 2017/02/25)
52 |
53 | $(UL
54 | $(FIXED
55 | Fix some minor doc and travis-ci issues with v0.10.0's release.
56 | )
57 | )
58 |
59 | $(H2 v0.10.0 - 2017/02/25)
60 |
61 | $(UL
62 | $(CHANGE
63 | $(ISSUE 33): Rename `Path.toRawString` to `Path.raw`.
64 | )
65 | $(CHANGE
66 | Deprecated `Ext.toRawString`. It didn't do anything
67 | different from `Ext.toString` and thus wasn't needed.
68 | )
69 | $(FIXED
70 | $(ISSUE 19): Compile error with DMDFE 2.065. Note, Scriptlike
71 | still $(I officially) requires at least DMDFE 2.066, mainly because
72 | of a bugfix for Windows, but DMDFE 2.065 appears to still be
73 | important for Debian's GDC.
74 | )
75 | $(FIXED
76 | Excess blank lines and malformed `
` in this changelog.
77 | )
78 | )
79 |
80 | $(H2 v0.9.7 - 2017/01/23)
81 |
82 | $(UL
83 | $(ENHANCE
84 | Docs/Examples: Now recommend DUB v1.0.0+'s single-file package support,
85 | and test the provided example.
86 | )
87 | $(FIXED
88 | $(ISSUE 31): Deprecation warnings on DMD 2.072 and up.
89 | )
90 | )
91 |
92 | $(H2 v0.9.6 - 2016/05/28)
93 |
94 | (Note: This was going to be v0.9.5, but the release got borked, so it's released as v0.9.6 instead.)
95 |
96 | $(UL
97 | $(FIXED
98 | $(ISSUE 26): Deprecation warnings on DMD 2.070 and 2.071.
99 | )
100 | $(FIXED
101 | $(ISSUE 27): Flush stdout when requesting input.
102 | [$(LINK2 https://github.com/JesseKPhillips, Jesse Phillips)]
103 | )
104 | $(FIXED
105 | $(LINK2 https://github.com/Abscissa/scriptlike/blob/master/USAGE.md#in-a-plain-script, Plain script)
106 | example fails on DUB 0.9.25 (due to a change in dub's package cache directory structure).
107 | )
108 | $(FIXED
109 | Testing any pull request on
110 | $(LINK2 https://travis-ci.org/Abscissa/scriptlike/, Travis-CI)
111 | fails.
112 | )
113 | $(FIXED
114 | Unittests fail to build on DMD 2.071.
115 | )
116 | )
117 |
118 | $(H2 v0.9.4 - 2015/09/22)
119 |
120 | $(UL
121 | $(FIXED
122 | Previous release broke the `unittest` script when `dub test` support was added.
123 | )
124 | $(FIXED
125 | In echo mode, several functions would echo the wrong "try*" or
126 | non-"try*" version. Ex: $(API_PROCESS run) echoed $(API_PROCESS tryRun),
127 | and $(API_FILE_EXTR tryRename) echoed $(API_FILE_WRAP rename).
128 | )
129 | $(FIXED
130 | $(API_PATH_EXTR Path) and $(API_PATH_EXTR buildNormalizedPathFixed) now
131 | convert back/forward slashes to native on BOTH Windows and Posix, not
132 | just on Windows.
133 | )
134 | $(FIXED
135 | Some links within changelog and API reference were pointing to the
136 | reference docs for Scriptlike's latest version, instead of staying
137 | within the same documentation version. This made
138 | $(LINK2 http://semitwist.com/scriptlike-docs/, archived docs for previous versions)
139 | difficult to navigate.
140 | )
141 | $(ENHANCE
142 | $(ISSUE 17),$(ISSUE 20): Added usage examples to readme.
143 | )
144 | $(ENHANCE
145 | Add $(API_CORE interp) for interpolated strings:$(BR)
146 | `string s = mixin( interp!"Value is ${variableOrExpression}" )`
147 | )
148 | $(ENHANCE
149 | Add $(API_FILE_EXTR removePath)/$(API_FILE_EXTR tryRemovePath) for
150 | deleting a path regardless of whether it's a file or directory. (Calls
151 | $(API_FILE_WRAP remove) for files and $(API_FILE_WRAP rmdirRecurse) for
152 | directories.)
153 | )
154 | $(ENHANCE
155 | Add a Path-accepting overload of $(API_PATH_EXTR escapeShellArg) for
156 | the sake of generic code.
157 | )
158 | $(ENHANCE
159 | When $(API_PROCESS runCollect) throws, the $(API_PROCESS ErrorLevelException)
160 | now includes and displays the command's output (otherwise there'd be no
161 | way to inspect the command's output for diagnostic purposes).
162 | )
163 | $(ENHANCE
164 | Greatly extended and improved set of tests.
165 | )
166 | )
167 |
168 | $(H2 v0.9.3 - 2015/08/19)
169 |
170 | $(UL
171 | $(FIXED
172 | $(ISSUE 16): Access to standard Phobos function hampered.
173 | )
174 | $(ENHANCE
175 | Support running unittests through DUB: `dub test`
176 | )
177 | $(ENHANCE
178 | Uses $(LINK2 https://travis-ci.org, travis-ci.org) for continuous integration testing.
179 | )
180 | )
181 |
182 | $(H2 v0.9.2 - 2015/07/10)
183 |
184 | $(UL
185 | $(FIXED
186 | Properly flush all command echoing output
187 | (ie, in $(API_CORE yap) and $(API_CORE yapFunc)).
188 | )
189 | $(ENHANCE
190 | Add a "no-build" configuration for projects that need to import/depend
191 | on Scriptlike through DUB, but use their own buildsystem.
192 | )
193 | )
194 |
195 | $(H2 v0.9.1 - 2015/06/28)
196 |
197 | $(UL
198 | $(FIXED Fails to compile unless the `makedocs` script has been run.)
199 | )
200 |
201 | $(H2 v0.9.0 - 2015/06/27)
202 |
203 | $(UL
204 | $(CHANGE Split $(MODULE_FILE) and $(MODULE_PATH) into the following:$(BR)
205 | $(UL
206 | $(LI $(MODULE_CORE) )
207 | $(LI $(MODULE_FILE_EXTR) )
208 | $(LI $(MODULE_FILE_WRAP) )
209 | $(LI $(MODULE_PATH_EXTR) )
210 | $(LI $(MODULE_PATH_WRAP) )
211 | )
212 | Utilizes `package.d` to retain ability to import $(MODULE_FILE) and $(MODULE_PATH).
213 | )
214 | $(CHANGE Convert changelog from markdown to $(DDOX) so links are more readable. )
215 | $(ENHANCE Add (opt-in) command echoing to most functions in $(MODULE_FILE). )
216 | $(ENHANCE
217 | Add $(API_CORE yap) and $(API_CORE yapFunc) as improved versions
218 | of to-be-deprecated $(API_CORE echoCommand).
219 | )
220 | $(FIXED Make $(API_PATH_EXTR escapeShellArg) const-correct. )
221 | $(FIXED
222 | Make $(API_PATH_EXTR Path.toRawString) and $(API_PATH_EXTR Ext.toRawString)
223 | both be `pure @safe nothrow`.
224 | )
225 | )
226 |
227 | $(H2 v0.8.1 - 2015/06/22)
228 |
229 | $(UL
230 | $(ENHANCE
231 | New overload for $(API_INTERACT userInput) to allow type inference:$(BR)
232 | `void userInput(T=string)(string question, ref T result);`
233 | (suggestion from
234 | $(LINK2 http://forum.dlang.org/post/povoxkcogcmbvhwlxqbc@forum.dlang.org, Per Nordlöw)).
235 | )
236 | )
237 |
238 | $(H2 v0.8.0 - 2015/06/13)
239 |
240 | $(UL
241 | $(CHANGE
242 | Minimum officially supported $(DMD) increased from v2.064.2 to v2.066.0.
243 | Versions below v2.066.0 may still work, but there will now be certain
244 | problems when dealing with paths that contain spaces, particularly
245 | on Windows.
246 | )
247 | $(CHANGE
248 | Removed unnecessary non-$(API_PATH_EXTR Path) wrappers around $(MODULE_STD_FILE)/$(MODULE_STD_PATH).
249 | Things not wrapped (like $(STD_PATH dirSeparator) and $(STD_FILE SpanMode))
250 | are now selective public imports instead of aliases. These changes should
251 | reduce issues with symbol conflicts.
252 | )
253 | $(CHANGE
254 | $(LINK2 http://semitwist.com/scriptlike/, API reference) now built
255 | using $(DDOX) and uses much improved styling (actually uses a stylesheet now).
256 | )
257 | $(CHANGE
258 | Eliminate remnants of the "planned but never enabled" wstring/dstring
259 | versions of $(API_PATH_EXTR Path)/$(API_PATH_EXTR Ext)/$(API_PROCESS Args). There
260 | turned out not to be much need for them, and even $(MODULE_STD_FILE)
261 | doesn't support wstring/dstring either.
262 | )
263 | $(CHANGE Put output binaries in "bin" subdirectory, instead of Scriptlike's root. )
264 | $(ENHANCE
265 | Add module scriptlike.only to import all of scriptlike, but omit the
266 | helper Phobos imports in scriptlike.std.
267 | )
268 | $(ENHANCE
269 | $(API_FAIL fail) now accepts an arbitrary list of args of any type,
270 | just like $(STD_STDIO writeln),
271 | )
272 | $(ENHANCE
273 | Added $(API_FAIL failEnforce), like Phobos's $(STD_EXCEPTION enforce),
274 | but for $(API_FAIL fail).
275 | )
276 | $(ENHANCE
277 | Added $(API_PROCESS runCollect) and $(API_PROCESS tryRunCollect), to
278 | capture a command's output instead of displaying it.
279 | )
280 | $(ENHANCE Added $(API_INTERACT pause) to pause and prompt the user to press Enter. )
281 | $(ENHANCE $(API_CORE echoCommand) is no longer private. )
282 | $(ENHANCE
283 | Added $(API_PATH_EXTR Path)-based wrappers for $(MODULE_STD_FILE)'s
284 | $(STD_FILE getcwd), $(STD_FILE thisExePath) and $(STD_FILE tempDir).
285 | )
286 | $(FIXED No longer uses Phobos's deprecated $(STD_PROCESS system) function.)
287 | )
288 |
289 | $(H2 v0.7.0 - 2015/04/02)
290 |
291 | $(UL
292 | $(ENHANCE
293 | $(ISSUE 14): Added scriptlike.interact module for easy user-input prompts.
294 | [$(LINK2 https://github.com/JesseKPhillips, Jesse Phillips)]
295 | )
296 | $(FIXED Unittest compile failure on $(DMD) v2.067.0. )
297 | )
298 |
299 | $(H2 v0.6.0 - 2014/02/16)
300 |
301 | $(UL
302 | $(CHANGE
303 | $(API_PATH_EXTR Path) and $(API_PATH_EXTR Ext) are now aliases for the UTF-8
304 | instantiations, and the template structs are now named `PathT` and `ExtT`.
305 | )
306 | $(CHANGE
307 | Removed `path()` and `ext()` helper functions to free up useful names
308 | from the namespace, since they are no longer needed. Use `Path()` and
309 | `Ext()` instead.
310 | )
311 | $(CHANGE
312 | Internally split into separate modules, but uses `package.d` to
313 | preserve `import scriptlike;`.
314 | )
315 | $(CHANGE Rename `escapeShellPath` -> $(API_PATH_EXTR escapeShellArg). )
316 | $(CHANGE
317 | Rename $(API_PROCESS runShell) -> $(API_PROCESS tryRun). Temporarily keep
318 | $(API_PROCESS runShell) as an alias.
319 | )
320 | $(CHANGE
321 | Rename $(API_CORE scriptlikeTraceCommands) -> $(API_CORE scriptlikeEcho).
322 | Temporarily keep $(API_CORE scriptlikeTraceCommands) as an alias.
323 | )
324 | $(ENHANCE Added scripts to run unittests and build API docs. )
325 | $(ENHANCE
326 | Added $(API_PATH_EXTR Path.opCast) and $(API_PATH_EXTR Ext.opCast) for
327 | converting to bool.
328 | )
329 | $(ENHANCE
330 | $(API_FAIL fail) no longer requires any boilerplate in `main()`.
331 | ($(LINK2 http://forum.dlang.org/thread/ldc6qt$(DOLLAR)22tv$(DOLLAR)1@digitalmars.com, Newsgroup link))
332 | )
333 | $(ENHANCE
334 | Added $(API_PROCESS run) to run a shell command like $(API_PROCESS tryRun),
335 | but automatically throw if the process returns a non-zero error level.
336 | )
337 | $(ENHANCE $(ISSUE 2): Optional callback sink for command echoing: $(API_CORE scriptlikeCustomEcho). )
338 | $(ENHANCE $(ISSUE 8): Dry run support via bool $(API_CORE scriptlikeDryRun). )
339 | $(ENHANCE
340 | $(ISSUE 13): Added `ArgsT` (and $(API_PROCESS Args) helper alias)
341 | to safely build command strings from parts.
342 | )
343 | $(ENHANCE Added this changelog. )
344 | $(FIXED
345 | $(API_PATH_EXTR Path)(null) and $(API_PATH_EXTR Ext)(null) were automatically
346 | changed to empty string.
347 | )
348 | $(FIXED $(ISSUE 10): Docs should include all OS-specific functions. )
349 | )
350 |
351 | $(H2 v0.5.0 - 2014/02/11)
352 |
353 | $(UL
354 | $(LI Initial release. )
355 | )
356 |
357 | Copyright:
358 | Copyright (C) 2014-2017 Nick Sabalausky.
359 | Portions Copyright (C) 2010 Jesse Phillips.
360 |
361 | License: zlib/libpng
362 | Authors: Nick Sabalausky, Jesse Phillips
363 | +/
364 | module changelog;
365 |
--------------------------------------------------------------------------------
/tests/testExample.d:
--------------------------------------------------------------------------------
1 | /++
2 | This program runs and tests one or all of the "features" examples
3 | in this directory.
4 | +/
5 | import scriptlike;
6 |
7 | void function()[string] lookupTest; // Lookup test by name
8 | string testName; // Name of test being run
9 |
10 | void main(string[] args)
11 | {
12 | // Init test lookup
13 | lookupTest = [
14 | "All": &testAll,
15 |
16 | "features/AutomaticPhobosImport": &testAutomaticPhobosImport,
17 | "features/CommandEchoing": &testCommandEchoing,
18 | "features/DisambiguatingWrite": &testDisambiguatingWrite,
19 | "features/DryRunAssistance": &testDryRunAssistance,
20 | "features/Fail": &testFail,
21 | "features/Filepaths": &testFilepaths,
22 | "features/ScriptStyleShellCommands": &testScriptStyleShellCommands,
23 | "features/StringInterpolation": &testStringInterpolation,
24 | "features/TryAsFilesystemOperations": &testTryAsFilesystemOperations,
25 | "features/UserInputPrompts": &testUserInputPrompts,
26 |
27 | "DubProject": &testDubProject,
28 | "SingleFile": &testSingleFile,
29 | ];
30 |
31 | // Check args
32 | getopt(args, "v", &scriptlikeEcho);
33 |
34 | failEnforce(
35 | args.length == 2,
36 | "Invalid args.\n",
37 | "\n",
38 | "Usage: testExample [-v] NAME\n",
39 | "\n",
40 | "Options:\n",
41 | "-v Verbose\n",
42 | "\n",
43 | "Examples:\n",
44 | " testExample All\n",
45 | " testExample features/UserInputPrompts\n",
46 | "\n",
47 | "Available Test Names:\n",
48 | " ", lookupTest.keys.sort().join("\n "),
49 | );
50 |
51 | testName = args[1];
52 | failEnforce(
53 | (testName in lookupTest) != null,
54 | "No such test '", testName, "'.\n",
55 | "Available Test Names:\n",
56 | " ", lookupTest.keys.sort().join("\n "),
57 | );
58 |
59 | // Setup for test
60 | chdir(thisExePath.dirName);
61 | tryMkdirRecurse("bin/features"); // gdmd doesn't automatically create the output directory.
62 |
63 | // Run test
64 | writeln("Testing ", testName); stdout.flush();
65 | lookupTest[testName]();
66 | }
67 |
68 | alias RunResult = Tuple!(int, "status", string, "output");
69 |
70 | /++
71 | Compiles and runs a test, returning the test's output.
72 |
73 | Always displays, but does not return, the compiler output.
74 |
75 | Throws upon failure.
76 | +/
77 | string compileAndRun(string testName, string runCmdSuffix=null)
78 | {
79 | return _compileAndRunImpl(true, testName, runCmdSuffix).output;
80 | }
81 |
82 | /++
83 | Compiles and runs a test, returning the status code and the test's output.
84 |
85 | Always displays, but does not return, the compiler output.
86 | +/
87 | RunResult tryCompileAndRun(string testName, string runCmdSuffix=null)
88 | {
89 | return _compileAndRunImpl(false, testName, runCmdSuffix);
90 | }
91 |
92 | /++
93 | Separating the compile & build steps is important here because on
94 | AppVeyor/Windows the linker outputs a non-fatal message:
95 | "[...] not found or not built by the last incremental link; performing full link)"
96 |
97 | Any such non-fatal compilation messages MUST NOT be included in this
98 | function's return value or they will cause the tests to fail.
99 | +/
100 | RunResult _compileAndRunImpl(bool throwOnError, string testName, string runCmdSuffix)
101 | {
102 | version(Windows) auto exeSuffix = ".exe";
103 | else auto exeSuffix = "";
104 |
105 | auto compileCmd = compilerCommand(testName);
106 | auto runBinary = fixSlashes("bin/"~testName~exeSuffix);
107 | auto runCmd = runBinary~runCmdSuffix;
108 |
109 | writeln("compileCmd: ", compileCmd); stdout.flush();
110 | writeln("runCmd: ", runCmd); stdout.flush();
111 |
112 | if(throwOnError)
113 | {
114 | run(compileCmd);
115 | auto output = runCollect(runCmd);
116 | return RunResult(0, output);
117 | }
118 | else
119 | {
120 | auto status = tryRun(compileCmd);
121 | if(status != 0)
122 | return RunResult(status, null);
123 |
124 | return tryRunCollect(runCmd);
125 | }
126 | }
127 |
128 | string compilerCommand(string testName)
129 | {
130 | string archFlag = "";
131 | auto envArch = environment.get("Darch", "");
132 | if(envArch == "x86_64") archFlag = "-m64";
133 | if(envArch == "x86") archFlag = "-m32";
134 |
135 | auto libSourceFiles = cast(string)
136 | dirEntries("../src", "*.d", SpanMode.breadth).
137 | map!(a => cast(const(ubyte)[]) escapeShellArg(a)).
138 | joiner(cast(const(ubyte)[]) " ").
139 | array;
140 |
141 | version(Windows) auto execName = testName~".exe";
142 | else auto execName = testName;
143 |
144 | auto envDmd = environment.get("DMD", "dmd");
145 | return envDmd~" "~archFlag~" -debug -g -I../src "~libSourceFiles~" -ofbin/"~execName~" ../examples/"~testName~".d";
146 | }
147 |
148 | string normalizeNewlines(string str)
149 | {
150 | version(Windows) return str.replace("\r\n", "\n");
151 | else return str;
152 | }
153 |
154 | string fixSlashes(string path)
155 | {
156 | version(Windows) return path.replace(`/`, `\`);
157 | else version(Posix) return path.replace(`\`, `/`);
158 | else static assert(0);
159 | }
160 |
161 | string quote(string str)
162 | {
163 | version(Windows) return `"` ~ str ~ `"`;
164 | else version(Posix) return `'` ~ str ~ `'`;
165 | else static assert(0);
166 | }
167 |
168 | void testAll()
169 | {
170 | bool failed = false; // Have any tests failed?
171 |
172 | foreach(name; lookupTest.keys.sort())
173 | if(lookupTest[name] != &testAll)
174 | {
175 | // Instead of running the test function directly, run it as a separate
176 | // process. This way, we can safely continue running all the tests
177 | // even if one throws an AssertError or other Error.
178 | auto verbose = scriptlikeEcho? "-v " : "";
179 | auto status = tryRun("." ~ dirSeparator ~ "testExample " ~ verbose ~ name);
180 | if(status != 0)
181 | failed = true;
182 | }
183 | writeln("Done running tests for examples."); stdout.flush();
184 |
185 | failEnforce(!failed, "Not all tests succeeded.");
186 | }
187 |
188 | void testAutomaticPhobosImport()
189 | {
190 | auto output = compileAndRun(testName).normalizeNewlines;
191 | assert(output == "Works!\n");
192 | }
193 |
194 | void testCommandEchoing()
195 | {
196 | immutable expected =
197 | "run: echo Hello > file.txt
198 | mkdirRecurse: "~("some/new/dir".fixSlashes)~"
199 | copy: file.txt -> "~("some/new/dir/target name.txt".fixSlashes.quote)~"
200 | Gonna run foo() now...
201 | foo: i = 42
202 | ";
203 |
204 | auto output = compileAndRun(testName).normalizeNewlines;
205 | assert(output == expected);
206 | }
207 |
208 | void testDisambiguatingWrite()
209 | {
210 | immutable expected = "Hello worldHello world";
211 |
212 | auto output = compileAndRun(testName).normalizeNewlines;
213 | assert(output == expected);
214 | }
215 |
216 | void testDryRunAssistance()
217 | {
218 | immutable expected =
219 | "copy: original.d -> app.d
220 | run: dmd app.d -ofbin/app
221 | exists: another-file
222 | ";
223 |
224 | auto output = compileAndRun(testName).normalizeNewlines;
225 | assert(output == expected);
226 | }
227 |
228 | void testFail()
229 | {
230 | auto result = tryCompileAndRun(testName);
231 | assert(result.status > 0);
232 | assert(result.output.normalizeNewlines.strip == "Fail: ERROR: Need two args, not 0!");
233 |
234 | result = tryCompileAndRun(testName, " abc 123");
235 | assert(result.status > 0);
236 | assert(result.output.normalizeNewlines.strip == "Fail: ERROR: First arg must be 'foobar', not 'abc'!");
237 |
238 | auto output = compileAndRun(testName, " foobar 123");
239 | assert(output == "");
240 | }
241 |
242 | void testFilepaths()
243 | {
244 | immutable expected =
245 | ("foo/bar/different subdir/Filename with spaces.txt".fixSlashes.quote) ~ "\n" ~
246 | ("foo/bar/different subdir/Filename with spaces.txt".fixSlashes) ~ "\n";
247 |
248 | auto output = compileAndRun(testName).normalizeNewlines;
249 | assert(output == expected);
250 | }
251 |
252 | void testScriptStyleShellCommands()
253 | {
254 | // This test relies on "dmd" being available on the PATH
255 | auto dmdResult = tryRunCollect("dmd --help");
256 | if(dmdResult.status != 0)
257 | {
258 | writeln(`Skipping `, testName, `: Couldn't find 'dmd' on the PATH.`); stdout.flush();
259 | return;
260 | }
261 |
262 | immutable inFile = "testinput.txt";
263 | scope(exit)
264 | tryRemove(inFile);
265 |
266 | writeFile(inFile, "\n");
267 |
268 | version(OSX) enum key = "Return";
269 | else enum key = "Enter";
270 |
271 | immutable expectedExcerpt =
272 | "Press "~key~" to continue...Error: unrecognized switch '--bad-flag'\n";
273 |
274 | auto output = compileAndRun(testName, " < " ~ inFile).normalizeNewlines;
275 | assert(output.canFind(expectedExcerpt));
276 | }
277 |
278 | void testStringInterpolation()
279 | {
280 | immutable expected =
281 | "The number 21 doubled is 42!
282 | Empty braces output nothing.
283 | Multiple params: John Doe.
284 | ";
285 |
286 | auto output = compileAndRun(testName).normalizeNewlines;
287 | assert(output == expected);
288 | }
289 |
290 | void testTryAsFilesystemOperations()
291 | {
292 | auto output = compileAndRun(testName).normalizeNewlines;
293 | assert(output == "");
294 | }
295 |
296 | void testUserInputPrompts()
297 | {
298 | immutable inFile = "testinput.txt";
299 | scope(exit)
300 | tryRemove(inFile);
301 |
302 | writeFile(inFile,
303 | "Nana
304 | 20
305 | y
306 | testExample.d
307 | 2
308 | 7
309 | \n\n"
310 | );
311 |
312 | version(OSX) enum key = "Return";
313 | else enum key = "Enter";
314 |
315 | immutable expectedExcerpt =
316 | "Please enter your name
317 | > And your age
318 | > Do you want to continue?
319 | > Where you do want to place the output?
320 | > What color would you like to use?
321 | 1. Blue
322 | 2. Green
323 | No Input. Quit
324 |
325 | > Enter a number from 1 to 10
326 | > Press "~key~" to continue...Hit Enter again, dood!!";
327 |
328 | auto output = compileAndRun(testName, " < " ~ inFile).normalizeNewlines;
329 | assert(output.canFind(expectedExcerpt));
330 | }
331 |
332 | void testUseInScripts(string subdir, Path workingDir, string command, bool checkReportedDir=true)
333 | {
334 | auto projDir = Path("../examples/"~subdir);
335 |
336 | // Test with cmdline arg
337 | {
338 | string expected;
339 | if(checkReportedDir)
340 | {
341 | expected = text(
342 | "This script is in directory: ", (thisExePath.dirName ~ projDir), "
343 | Hello, Frank!
344 | ");
345 | }
346 | else
347 | {
348 | expected = text(
349 | "Hello, Frank!
350 | ");
351 | }
352 | auto output = workingDir.runCollect( command~" Frank" ).normalizeNewlines;
353 | if(output != expected)
354 | {
355 | writeln("expected:========================");
356 | writeln(expected);
357 | writeln("output:========================");
358 | writeln(output);
359 | writeln("========================");
360 | stdout.flush();
361 | }
362 | assert(output.endsWith(expected));
363 | }
364 |
365 | // Test interactive
366 | {
367 | immutable inFile = "testinput.txt";
368 | scope(exit)
369 | tryRemove(workingDir ~ inFile);
370 |
371 | writeFile(workingDir ~ inFile, "George\n");
372 |
373 | string expected;
374 | if(checkReportedDir)
375 | {
376 | expected = text(
377 | "This script is in directory: ", (thisExePath.dirName ~ projDir), "
378 | What's your name?
379 | > Hello, George!
380 | ");
381 | }
382 | else
383 | {
384 | expected = text(
385 | "What's your name?
386 | > Hello, George!
387 | ");
388 | }
389 |
390 | auto output = workingDir.runCollect( command~" < "~inFile ).normalizeNewlines;
391 | if(output != expected)
392 | {
393 | writeln("expected:========================");
394 | writeln(expected);
395 | writeln("output:========================");
396 | writeln(output);
397 | writeln("========================");
398 | stdout.flush();
399 | }
400 | assert(output.endsWith(expected));
401 | }
402 | }
403 |
404 | string getDubEnvArgs()
405 | {
406 | string args;
407 |
408 | if(environment.get("Darch") !is null)
409 | args ~= " --arch=" ~ environment["Darch"];
410 |
411 | if(environment.get("DC") !is null)
412 | args ~= " --compiler=" ~ environment["DC"];
413 |
414 | return args;
415 | }
416 |
417 | void testDubProject()
418 | {
419 | // Force rebuild
420 | tryRemove("../examples/dub-project/myscript");
421 | tryRemove("../examples/dub-project/myscript.exe");
422 |
423 | // Do test
424 | testUseInScripts("dub-project", Path("../examples/dub-project"), "dub --vquiet "~getDubEnvArgs~" -- ");
425 | }
426 |
427 | void testSingleFile()
428 | {
429 | // Do tests
430 | writeln(" Testing from its own directory..."); stdout.flush();
431 | testUseInScripts("single-file", Path("../examples/single-file"), "dub --vquiet --single "~getDubEnvArgs~" myscript.d -- ", false);
432 |
433 | writeln(" Testing from different directory..."); stdout.flush();
434 | testUseInScripts(
435 | "single-file",
436 | Path("../tests/bin"),
437 | "dub --vquiet --single "~getDubEnvArgs~" "~Path("../../examples/single-file/myscript.d").raw~" -- ",
438 | false
439 | );
440 | }
441 |
--------------------------------------------------------------------------------
/docs/public/prettify/prettify.js:
--------------------------------------------------------------------------------
1 | var o=!0,r=null,A=!1;window.PR_SHOULD_USE_CONTINUATION=o; (function(){function O(a){function m(a){var f=a.charCodeAt(0);if(92!==f)return f;var b=a.charAt(1);return(f=s[b])?f:"0"<=b&&"7">=b?parseInt(a.substring(1),8):"u"===b||"x"===b?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(32>a)return(16>a?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if("\\"===a||"-"===a||"["===a||"]"===a)a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g")), a=[],b=[],p="^"===f[0],c=p?1:0,i=f.length;cd||122d||90d||122i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function z(a){for(var f=a.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g")),b=f.length,d=[],c=0,i=0;c/,r])):m.push(["com",/^#[^\r\n]*/,r,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\r\n]*/,r]),e.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/,r]));a.regexLiterals&&e.push(["lang-regex",RegExp("^(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*(/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/)")]); (h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),r]);m.push(["pln",/^\s+/,r," \r\n\t\u00a0"]);e.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,r],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,r],["pln",/^[a-z_$][a-z_$@0-9]*/i,r],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,r,"0123456789"],["pln",/^\\[\s\S]?/,r],["pun",/^.[^\s\w\.$@\'\"\`\/\#\\]*/,r]);return x(m,e)}function F(a, m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(q){var b=a.nodeValue,d=b.match(u);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(t.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(A):a,f=a.parentNode; if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&1===e.nodeType;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,u=/\r\n?|\n/,t=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=t.defaultView.getComputedStyle(a,r).getPropertyValue("white-space"));var q=l&&"pre"===l.substring(0,3);for(l=t.createElement("LI");a.firstChild;)l.appendChild(a.firstChild); for(var d=[l],g=0;g=p&&(h+=2);e>=c&&(a+=2)}}catch(x){"console"in window&&console.log(x&&x.stack?x.stack:x)}}var w=["break,continue,do,else,for,if,return,while"],y=[[w,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], "catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],H=[y,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],I=[y,"alias,align,asm,bool,cast,export,foreach,foreach_reverse,import,interface,module,null,assert,template,typeid,byte,ubyte,ushort,uint,ulong"], J=[y,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],K=[J,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],y=[y,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],L= [w,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],M=[w,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],w=[w,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],N=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/, Q=/\S/,R=v({keywords:[H,I,K,y,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+L,M,w],hashComments:o,cStyleComments:o,multiLineStrings:o,regexLiterals:o}),C={};k(R,["default-code"]);k(x([],[["pln",/^[^]+/],["dec",/^]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-", /^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^