www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - Scriptlike: New lib to aid in writing script-like programs

reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
I've released a little one-module utility, Scriptlike, to help simplify 
writing shell script-like programs in D:

   https://github.com/Abscissa/scriptlike

Here's post outlining my motivations for this library (sorry, the CMS 
I'm using ATM generates very long URLs):

http://semitwist.com/articles/article/view/scriptlike-shell-scripting-in-d-annoyances-and-a-library-solution

Current Features:

- A thin wrapper over std.path and std.file that provides a dedicated 
Path type specifically designed for managing file paths in a simple, 
reliable, cross-platform way. No more dealing with slashes, 
paths-with-spaces, calling buildPath, normalizing, or getting paths 
mixed up with ordinary strings.

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

- Most typical Phobos modules automatically imported. Who needs rows and 
rows of standard lib imports for a mere script?

- Less-pedantic filesystem operations for when you don't care whether it 
exists or not: existsAsFile, existsAsDir, existsAsSymlink, tryRename, 
trySymlink, tryCopy, tryMkdir, tryMkdirRecurse, tryRmdir, 
tryRmdirRecurse, tryRemove: All check whether the source path exists and 
return WITHOUT throwing if there's nothing to do.

- One simple call, runShell, to run a shell command script-style (ie, 
synchronously with forwarded stdout/in/err) from any working directory. 

BTW, thanks all involved who fixed that.)

- One simple function, fail(string msg), to help you exit with an error 
message in an exception-safe way. (Does require some minor boilerplate 
added to your main().)
Feb 11 2014
next sibling parent "thedeemon" <dlang thedeemon.com> writes:
On Tuesday, 11 February 2014 at 11:38:06 UTC, Nick Sabalausky 
wrote:
 I've released a little one-module utility, Scriptlike, to help 
 simplify writing shell script-like programs in D:

   https://github.com/Abscissa/scriptlike
Sounds very nice and handy. I tend to write my scripts in D these days, often migrating them from Ruby, and this lib might be helpful. Thanks for putting it up!
Feb 11 2014
prev sibling next sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Tuesday, 11 February 2014 at 11:38:06 UTC, Nick Sabalausky 
wrote:
 I've released a little one-module utility, Scriptlike, to help 
 simplify writing shell script-like programs in D:

   https://github.com/Abscissa/scriptlike
It looks like you've covered a lot of the short comings for doing some cmdln scripting. It also sounds like it would fit right into what I was doing (I hand only covered the user input portion) https://github.com/JesseKPhillips/JPDLibs/tree/cmdln (I really probably should move it into its own project, maybe I should instead submit mine to yours? Though I use Boost License). [OT] I also want to thank those behind std.process, std.path, and std.algorithms these things have been really awesome and I use heavily (great improvements).
 Current Features:

 - A thin wrapper over std.path and std.file that provides a 
 dedicated Path type specifically designed for managing file 
 paths in a simple, reliable, cross-platform way. No more 
 dealing with slashes, paths-with-spaces, calling buildPath, 
 normalizing, or getting paths mixed up with ordinary strings.
Personally I've found the new std.path makes this much easier, I'm not user how you can address paths-with-spaces as this depends on who you call, internally I don't need to worry. But maybe this simplifies it more and is still worth it.
 - Optionally enable automatic command echoing (including shell 
 commands, changing/creating directories and 
 deleting/copying/moving/linking/renaming both directories and 
 files) by setting one simple flag: bool scriptlikeTraceCommands
Command echoing, and dry run are both valuable.
 - Most typical Phobos modules automatically imported. Who needs 
 rows and rows of standard lib imports for a mere script?

 - Less-pedantic filesystem operations for when you don't care 
 whether it exists or not: existsAsFile, existsAsDir, 
 existsAsSymlink, tryRename, trySymlink, tryCopy, tryMkdir, 
 tryMkdirRecurse, tryRmdir, tryRmdirRecurse, tryRemove: All 
 check whether the source path exists and return WITHOUT 
 throwing if there's nothing to do.
This is my biggest gripe with the current available functions!
 - One simple call, runShell, to run a shell command 
 script-style (ie, synchronously with forwarded stdout/in/err) 
 from any working directory. (Also automatically works around 

 involved who fixed that.)
Aside from the bug, I don't understand what this provides over "execute."
 - One simple function, fail(string msg), to help you exit with 
 an error message in an exception-safe way. (Does require some 
 minor boilerplate added to your main().)
I've just been using exceptions, Fail should take a return code too.
Feb 11 2014
parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 2/11/2014 1:52 PM, Jesse Phillips wrote:
 On Tuesday, 11 February 2014 at 11:38:06 UTC, Nick Sabalausky wrote:
 I've released a little one-module utility, Scriptlike, to help
 simplify writing shell script-like programs in D:

   https://github.com/Abscissa/scriptlike
It looks like you've covered a lot of the short comings for doing some cmdln scripting. It also sounds like it would fit right into what I was doing (I hand only covered the user input portion) https://github.com/JesseKPhillips/JPDLibs/tree/cmdln (I really probably should move it into its own project, maybe I should instead submit mine to yours? Though I use Boost License).
Oh yea, I hadn't even thought of user input :P. I guess I got in the habit of avoiding it in CLI apps. I remember seeing your lib for that a while back and rather liked it. I think that would fit very well into Scriptlike, as long as you don't mind it all being in the same module as the rest of scriptlike, and preferably using same formatting style (not that that's strictly important, but consistency is nice of course). As for the license, if you don't mind switching to zlib then great, just go ahead and submit a pull request if you'd like to. But I'm not married to zlib license or anything. My reasons for using zlib license are relatively minor, and I'm not opposed to switching to Boost, especially since it's already the Phobos license after all. What are your thoughts?
 [OT] I also want to thank those behind std.process, std.path, and
 std.algorithms these things have been really awesome and I use heavily
 (great improvements).
///ditto
 - A thin wrapper over std.path and std.file that provides a dedicated
 Path type specifically designed for managing file paths in a simple,
 reliable, cross-platform way. No more dealing with slashes,
 paths-with-spaces, calling buildPath, normalizing, or getting paths
 mixed up with ordinary strings.
Personally I've found the new std.path makes this much easier,
Yea, it *definitely* does. But at the same time, I've found I still end up mucking with slashes and such anyway for a couple reasons: 1. In simpler situations, calling buildPath sometimes just seems to make what should be trivial become more verbose than I'd like. So I don't always like to use it even when I know I should. But with Scriptlike's Path type, it's just a simple partA~partB and slashes are handled behind-the-scenes (internally via buildNormalizedPath). Can't get much less verbose than that :) Maybe not the most efficient strategy, but this is intended for shell-like scripts, so it's unlikely to be anything near a problematic bottleneck. 2. std.path (and buildPath in particular) likes to use / on Posix and \ on Windows. While this makes a lot of sense in certain ways, I find that when I do deal with paths, having all my code internally operate on forward-slashes-only often leads to much, much simpler code. The downside is that not only do I have to sanitize all my inputs to forward-slash-only, I also have to do the same for many paths I get back from std.path. And then I have to convert back to backslashes on windows if I want to display it to the user in a nice OS-correct way, or pass it to certain WinAPI functions. So with Scriptlike, I'm hoping to avoid the need to ever deal with slashes at all, because all the clutter and meticulous care of always doing it the proper std.path-way is (hopefully) hidden behind-the-scenes via the Path type.
 I'm not
 user how you can address paths-with-spaces as this depends on who you
 call, internally I don't need to worry. But maybe this simplifies it
 more and is still worth it.
Path.toString() automatically quotes paths correctly when they contain spaces. So you can just pass it off to spawnShell or whatever and it's automatically all ready-to-go. Don't even need to call the std.path.escape*() funcs (which I've had trouble with on windows anyway, least help). Of course, if for any reason you need to keep the path un-quoted, there's Path.toRawString() for that.
 Command echoing, and dry run are both valuable.
Oh, dry run, of course, I didn't think of that! Some scripts may still have to take extra care to handle dry runs correctly, for example if one step relies on the result of an earlier step *actually* being executed. But Scriptlike could probably help with at least some of the work. I've added a ticket for that: https://github.com/Abscissa/scriptlike/issues/8 Do you think enabling dry run should automatically enable command echoing?
 - Less-pedantic filesystem operations for when you don't care whether
 it exists or not: existsAsFile, existsAsDir, existsAsSymlink,
 tryRename, trySymlink, tryCopy, tryMkdir, tryMkdirRecurse, tryRmdir,
 tryRmdirRecurse, tryRemove: All check whether the source path exists
 and return WITHOUT throwing if there's nothing to do.
This is my biggest gripe with the current available functions!
Yea, I'm constantly making wrappers for those things in my scripts. Finally decided to just toss them into a common lib. I *do* think std.file's pedantic behavior with those does make a lot of sense in the general case. I'm not sure that I'd even want std.file to relax those checks by default. But for simple shell-script-like stuff, it does tend to be more bother than benefit.
 - One simple call, runShell, to run a shell command script-style (ie,
 synchronously with forwarded stdout/in/err) from any working

 for v2.066 - BTW, thanks all involved who fixed that.)
Aside from the bug, I don't understand what this provides over "execute."
First of all, "spawn(Process|Shell)().wait()" is a better comparison for runShell than execute(Process|Shell). The execute functions, at least by default, hide the child's stdout/stderr. Granted, execute does capture the child's stdout, so you *could* output it yourself afterwords, but then the user doesn't see the output in real-time (and forget about anything interactive), so that's not a good solution. Regarding runShell vs "spawn(Process|Shell)().wait()", I admit the motivations are somewhat on the weak side, but I think it's still enough to be worth having: - There's the (now fixed in master for 2.066) bug, like we said. - Out of all the ways to launch a process with std.process, "spawnShell().wait()" is the *one* way that actually matches a shell script's behavior (aside from the bug), and is therefore (at least IMO and in my experience) the one you'd usually want in a script-like program. But looking at the available choices and options in std.process, it's not immediately obvious how to do "Run a command just like a shell script would". So Scriptlike says "runShell, that's how you do it". - runShell optionally takes a Path to use as the initial working directory to launch the process from (and then uses scope(exit) to automatically chdir back when the process finishes). Nothing in std.process does that right now, although there is a request in bugzilla for it: http://d.puremagic.com/issues/show_bug.cgi?id=11363 Plus there's the automatic command echoing (not that I couldn't do that in some more direct std.process wrappers, like I do for certain std.file functions).
 - One simple function, fail(string msg), to help you exit with an
 error message in an exception-safe way. (Does require some minor
 boilerplate added to your main().)
I've just been using exceptions,
Yea, that's exactly what fail() is, really. It just throws a "class Fail : Exception", and then the user's main() is expected to catch it, do a "stderr.writeln(e.msg)" and return 1. I wanted to provide a nice way to reduce all that main() boilerplate the user needs, but I'm not sure if that's possible right now. Here's the relevant D.learn thread (ooh, which appears to have some new posts now...): http://forum.dlang.org/thread/ldc6qt$22tv$1 digitalmars.com
 Fail should take a return code too.
Good idea. Mind filing an enhancement request here?: https://github.com/Abscissa/scriptlike/issues
Feb 11 2014
next sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Wednesday, 12 February 2014 at 00:07:42 UTC, Nick Sabalausky 
wrote:
 As for the license, if you don't mind switching to zlib then 
 great, just go ahead and submit a pull request if you'd like 
 to. But I'm not married to zlib license or anything. My reasons 
 for using zlib license are relatively minor, and I'm not 
 opposed to switching to Boost, especially since it's already 
 the Phobos license after all. What are your thoughts?
I've been using Boost to be compatible Phobos. I haven't released anything which I feel needs a more restrictive license (zlib I think is permissive).
 So with Scriptlike, I'm hoping to avoid the need to ever deal 
 with slashes at all, because all the clutter and meticulous 
 care of always doing it the proper std.path-way is (hopefully) 
 hidden behind-the-scenes via the Path type.
Any hard coded paths I'll use / and buildPath for everything else. My experience so far has been positive on Windows (but maybe I still have some legacy conversions from the old days).
 Path.toString() automatically quotes paths correctly when they 
 contain spaces. So you can just pass it off to spawnShell or 
 whatever and it's automatically all ready-to-go. Don't even 
 need to call the std.path.escape*() funcs (which I've had 

 apparently fixed in master now, so that should at least help).

 Of course, if for any reason you need to keep the path 
 un-quoted, there's Path.toRawString() for that.
Ah, I use execute(char[][]) and pipeProcess(char[][]) these bypass the shell (it seems) and require that the arguments aren't quoted. I prefer these because: auto cmd = ["dmd"]; cmd ~= "file.d";//... Is just a nice way to build a command.
 Do you think enabling dry run should automatically enable 
 command echoing?
Yes, I can't think of a reason I wouldn't want it to.
 - Less-pedantic filesystem operations for when you don't
care whether
 it exists or not: existsAsFile, existsAsDir, existsAsSymlink,
 tryRename, trySymlink, tryCopy, tryMkdir, tryMkdirRecurse,
tryRmdir,
 tryRmdirRecurse, tryRemove: All check whether the source
path exists
 and return WITHOUT throwing if there's nothing to do.
This is my biggest gripe with the current available functions!
Yea, I'm constantly making wrappers for those things in my scripts. Finally decided to just toss them into a common lib. I *do* think std.file's pedantic behavior with those does make a lot of sense in the general case. I'm not sure that I'd even want std.file to relax those checks by default. But for simple shell-script-like stuff, it does tend to be more bother than benefit.
Yeah, going back to being strict is harder.
 - One simple call, runShell, to run a shell command
script-style (ie,
 synchronously with forwarded stdout/in/err) from any working

without waiting
 for v2.066 - BTW, thanks all involved who fixed that.)
Aside from the bug, I don't understand what this provides
over "execute." First of all, "spawn(Process|Shell)().wait()" is a better comparison for runShell than execute(Process|Shell). The execute functions, at least by default, hide the child's stdout/stderr. Granted, execute does capture the child's stdout, so you *could* output it yourself afterwords, but then the user doesn't see the output in real-time (and forget about anything interactive), so that's not a good solution.
Hmm, I've been needing to retain the output and do things with it quite a bit. Haven't played with it, but you may be able to get interaction by using pipeProcess, but then there is just more work to make it act like spawn.
 - runShell optionally takes a Path to use as the initial 
 working directory to launch the process from (and then uses 
 scope(exit) to automatically chdir back when the process 
 finishes). Nothing in std.process does that right now, although 
 there is a request in bugzilla for it: 
 http://d.puremagic.com/issues/show_bug.cgi?id=11363
That is likely quite useful.
 Plus there's the automatic command echoing (not that I couldn't 
 do that in some more direct std.process wrappers, like I do for 
 certain std.file functions).
I wonder if a generic wrapper could be created to handle this.
Feb 11 2014
parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 2/11/2014 8:01 PM, Jesse Phillips wrote:
 On Wednesday, 12 February 2014 at 00:07:42 UTC, Nick Sabalausky wrote:
 As for the license, if you don't mind switching to zlib then great,
 just go ahead and submit a pull request if you'd like to. But I'm not
 married to zlib license or anything. My reasons for using zlib license
 are relatively minor, and I'm not opposed to switching to Boost,
 especially since it's already the Phobos license after all. What are
 your thoughts?
I've been using Boost to be compatible Phobos. I haven't released anything which I feel needs a more restrictive license (zlib I think is permissive).
zlib's about as permissive (and easy to read) as it gets without going all the way to the "Do WTF You Want" license (which I actually use for REALLY trivial things) It's basically like a re-worded MIT license: http://opensource.org/licenses/Zlib
 So with Scriptlike, I'm hoping to avoid the need to ever deal with
 slashes at all, because all the clutter and meticulous care of always
 doing it the proper std.path-way is (hopefully) hidden
 behind-the-scenes via the Path type.
Any hard coded paths I'll use / and buildPath for everything else. My experience so far has been positive on Windows (but maybe I still have some legacy conversions from the old days).
Yea, I like to use hardcoded / too, it's visually pleasant, minimally-verbose and Windows usually does accept it just fine. Problem is I can't always *rely* on paths always having / without being careful to sanitize my inputs.
 Ah, I use execute(char[][]) and pipeProcess(char[][]) these bypass the
 shell (it seems) and require that the arguments aren't quoted. I prefer
 these because:
Oh, that's right, it's "execute", not "executeProcess". I overlooked that naming detail. And it's possible I misunderstood pipeProcess when I looked at it before, I'll have to look again. The "*Shell" ones are nice because I know I can do anything I can do on the cmdline, like piping and redirecting and such, in just the same way as I would the command line. Luckily it works pretty much the same on both Windows and Bash. Although come to think of it, BSD's default shell (which I actually quite like in certain ways - specifically, searching through the command history) handles redirecting differently.
      auto cmd = ["dmd"];
      cmd ~= "file.d";//...

 Is just a nice way to build a command.
Hmm, yea, that's a good point. In any case, I didn't necessarily intend to leave Scriptlike's process features limited to just the current "runShell", so this is all good stuff to think about.
 Do you think enabling dry run should automatically enable command
 echoing?
Yes, I can't think of a reason I wouldn't want it to.
The only downside I can think of is if there *were* some reason to do dry-run without echoing, there'd be no way to do it. Then again, I already intended to allow a custom OutputRange sink for the echoing, so if someone really wanted to squelch the echoing, they could just pass in a do-nothing OutputRange. Ok, I'll do it that way then.
 Hmm, I've been needing to retain the output and do things with it quite
 a bit. Haven't played with it, but you may be able to get interaction by
 using pipeProcess, but then there is just more work to make it act like
 spawn.
Yea, that parsing the output can definitely be useful in certain cases. I haven't really done it much yet because the old std.process couldn't really handle it well, and since then, I either haven't needed to or maybe I've just gotten used to avoiding it. Actually, I've been really wanting to make a D equivalent of...I forget the name offhand, but it's fairly well-established tcl lib specifically designed for automating interactive text-based sessions. "expect", I think? I may do that soon, it'd fit well in Scriptlike.
 - runShell optionally takes a Path to use as the initial working
 directory to launch the process from (and then uses scope(exit) to
 automatically chdir back when the process finishes). Nothing in
 std.process does that right now, although there is a request in
 bugzilla for it: http://d.puremagic.com/issues/show_bug.cgi?id=11363
That is likely quite useful.
Yea, I've needed to do that SOOO many times. I've gotten really tired of cluttering my scripts with: { auto saveDir = getcwd(); chdir(somePath); scope(exit) chdir(saveDir); ... } Thank goodness it's D though, otherwise that'd be much worse. Scope guards are one of my favorite features of D. Brilliant solution.
Feb 11 2014
prev sibling parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Wednesday, 12 February 2014 at 00:07:42 UTC, Nick Sabalausky 
wrote:
 I think that would fit very well into Scriptlike, as long as 
 you don't mind it all being in the same module as the rest of 
 scriptlike, and preferably using same formatting style (not 
 that that's strictly important, but consistency is nice of 
 course).
Forgot that I wanted to say, I think considering the goals of the files it may be reasonable to have them separate. Solving the extra import can be done with something like package.d, but maybe you also want to reduce file count for some reason?
Feb 11 2014
parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 2/11/2014 8:10 PM, Jesse Phillips wrote:
 On Wednesday, 12 February 2014 at 00:07:42 UTC, Nick Sabalausky wrote:
 I think that would fit very well into Scriptlike, as long as you don't
 mind it all being in the same module as the rest of scriptlike, and
 preferably using same formatting style (not that that's strictly
 important, but consistency is nice of course).
Forgot that I wanted to say, I think considering the goals of the files it may be reasonable to have them separate. Solving the extra import can be done with something like package.d, but maybe you also want to reduce file count for some reason?
Honestly, I had a hard time deciding whether to do single-file or package.d. Package.d is great, but the (minor) downsides are: 1. If you download the source files manually (maybe someone who isn't onboard with git or dub yet?), there's more to do: Either download more than one file, or download one file and unzip. Versus just "download this file and use it". 2. You still have to either pass all the files to DMD, or add an extra -Ipath to your RDMD call. Normally I'd consider both of those trivialities and wouldn't worry about them, but scripts are usually just trivial single-files anyway, so just adding any dependency at all to a script is a potential deterrent. Even if only a subconscious one. So I wanted to keep it as trivial as possible. Ordinarily, separate source files and maybe a package.d would definitely be the way to go. But the idea of expecting people to add a dependency to typically zero-dependency scripts seems like it's already a battle against inertia right from the start. So a "whole package" instead of "one file" just seems like needlessly hurting things even more. However, maybe I could get the best of both worlds... What if it was maintained as separate source files, with a package.d, but then I had a pre-commit hook to run a script that combined it all into one file (just stripping out the "module xxxx;" statements)? Pre-commit hooks, unfortunately, can't be included in the repository, so it wouldn't run for contributors (unless they went to the bother of setting it up), but I wouldn't mind doing it myself after merging pull requests. Then again, maybe *that* complication could scare people off (esp. contributors), and maybe I'm worrying too much about the subconscious (or conscious) resistance to including external dependencies into trivial scripts?
Feb 11 2014
parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
Personally the biggest problem I have are libraries which depend 
on other libraries. A few of my scripts ended up growing a 
library I chopped off, but I haven't come up with a way to 
segregate it from needing XML/ini/cmdln libraries too.

On Wednesday, 12 February 2014 at 02:15:38 UTC, Nick Sabalausky 
wrote:
 1. If you download the source files manually (maybe someone who 
 isn't onboard with git or dub yet?), there's more to do: Either 
 download more than one file, or download one file and unzip. 
 Versus just "download this file and use it".
Personally pulling in one file or many is the same to me. If it comes in a zip, that seems reasonable for those not gitting (git provides zipping a folder, not subfolders).
 2. You still have to either pass all the files to DMD, or add 
 an extra -Ipath to your RDMD call.
That is true with the single file, so I don't see why this would be relevant. What is nice about keeping them separate is that people will be able to choose what type of script they will write. And I doubt there will be any common helper functions between these to modules.
Feb 11 2014
parent reply Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 2/11/2014 10:42 PM, Jesse Phillips wrote:
 On Wednesday, 12 February 2014 at 02:15:38 UTC, Nick Sabalausky wrote:
 2. You still have to either pass all the files to DMD, or add an extra
 -Ipath to your RDMD call.
That is true with the single file, so I don't see why this would be relevant.
With DMD it's just more additional files vs fewer additional files. But that probably is an irrelevent concern after all since RDMD can just as easily be used instead. You're right of course about RDMD, not sure what I was thinking there.
 What is nice about keeping them separate is that people will be able to
 choose what type of script they will write. And I doubt there will be
 any common helper functions between these to modules.
With scripts, I'm not sure there's much benefit to importing part of a lib vs the whole thing. But I may well just be paranoid about multiple files being a problem. Unless there's objections (don't seem to be so far) I may go ahead and split it up with a package.d. If it turns out to be an issue, I could just deal with it then.
Feb 11 2014
parent reply "Jesse Phillips" <Jesse.K.Phillips+D gmail.com> writes:
On Wednesday, 12 February 2014 at 04:59:11 UTC, Nick Sabalausky 
wrote:
 But I may well just be paranoid about multiple files being a 
 problem. Unless there's objections (don't seem to be so far) I 
 may go ahead and split it up with a package.d. If it turns out 
 to be an issue, I could just deal with it then.
Ok, I'll work on a pull request following your formatting as a separate file. You can take the merging/not merging from there.
Feb 11 2014
parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 2/12/2014 12:13 AM, Jesse Phillips wrote:
 following your formatting as a separate file.
Done, it's all separate files with a package.d now.
Feb 11 2014
prev sibling parent reply "Vladimir Panteleev" <vladimir thecybershadow.net> writes:
On Tuesday, 11 February 2014 at 11:38:06 UTC, Nick Sabalausky 
wrote:
 I've released a little one-module utility, Scriptlike, to help 
 simplify writing shell script-like programs in D:

   https://github.com/Abscissa/scriptlike
I've been thinking of creating something with a similar purpose, however also remove the need for any import lines or declaring a main() function, so you can start writing statements from an empty file. It would use a different file extension. Alternatively, you could do what vibe.d does and move the main() function to the library. That allows the library to parse command-line arguments, or catch the Fail exception (see below).
 - A thin wrapper over std.path and std.file that provides a 
 dedicated Path type specifically designed for managing file 
 paths in a simple, reliable, cross-platform way. No more 
 dealing with slashes, paths-with-spaces, calling buildPath, 
 normalizing, or getting paths mixed up with ordinary strings.
I think this is subjective. Path objects add too much syntax overhead, especially in shell script substitutes, IMO.
 - Less-pedantic filesystem operations for when you don't care 
 whether it exists or not: existsAsFile, existsAsDir, 
 existsAsSymlink, tryRename, trySymlink, tryCopy, tryMkdir, 
 tryMkdirRecurse, tryRmdir, tryRmdirRecurse, tryRemove: All 
 check whether the source path exists and return WITHOUT 
 throwing if there's nothing to do.
I think creating a new family of functions is not the best way to do this. How about a function/template that wraps a function instead? In Phobos, we have collectException(fun(...)), but something shorter like attempt!fun(...) would be better for scripting. The biggest advantage to this approach is that such wrappers are composable. For example, in my library I have safeUpdate, which wraps a function that creates a file by redirecting its output to a temporary file, then replacing the target atomically with one rename() call, and obtainUsing, which skips the operation entirely if the target file already exists (useful for expensive operations such as downloading a file). The latter implies the former, so that e.g. a program terminated in the middle of the download does not consider that file to have been downloaded successfully in the previous invocation.
 - One simple call, runShell, to run a shell command 
 script-style (ie, synchronously with forwarded stdout/in/err) 
 from any working directory. (Also automatically works around 

 involved who fixed that.)
Sorry about breaking it in the first place ;) But I would avoid running things "shell-like" though, as it's a mess of whitespace/escaping. Passing arguments as an array rather than a string is much better for many reasons.
 - One simple function, fail(string msg), to help you exit with 
 an error message in an exception-safe way. (Does require some 
 minor boilerplate added to your main().)
I think this idea combines even better when the main() function is created for you.
Feb 11 2014
parent Nick Sabalausky <SeeWebsiteToContactMe semitwist.com> writes:
On 2/11/2014 5:01 PM, Vladimir Panteleev wrote:
 On Tuesday, 11 February 2014 at 11:38:06 UTC, Nick Sabalausky wrote:
 I've released a little one-module utility, Scriptlike, to help
 simplify writing shell script-like programs in D:

   https://github.com/Abscissa/scriptlike
I've been thinking of creating something with a similar purpose, however also remove the need for any import lines
Not sure if you noticed, but Scriptlike publicly imports much of Phobos. If you think something else should be included that isn't already included, just submit a ticket or a pull request.
 or declaring a main()
 function, so you can start writing statements from an empty file. It
 would use a different file extension.
Yea, I've given that some thought in the past, too, esp in the context of D-based makefile alternatives. Newer features like scoped imports do make it more appealing than ever. But the thing I always get hung up in is the fact that, inside functions, you can't forward-reference declarations like you can outside of functions. So I always felt that would be more of a pain than just typing "import something; int main(string[] args) {}", which I've gotten
 Alternatively, you could do what vibe.d does and move the main()
 function to the library. That allows the library to parse command-line
 arguments, or catch the Fail exception (see below).
Yea, I thought of that, but I've always been [irrationally?] uncomfortable with hidden-main-provided-by-libs. (Of course, druntime itself does the hidden-real-main thing too ;) ) Plus then the user has to remember the new name to use instead of "main". So I shied away from doing that here. Still though, it is something to consider.
 - A thin wrapper over std.path and std.file that provides a dedicated
 Path type specifically designed for managing file paths in a simple,
 reliable, cross-platform way. No more dealing with slashes,
 paths-with-spaces, calling buildPath, normalizing, or getting paths
 mixed up with ordinary strings.
I think this is subjective. Path objects add too much syntax overhead, especially in shell script substitutes, IMO.
Perhaps. I've tried to keep it as simple as possible though: auto p = path("/foo/bar");
 - Less-pedantic filesystem operations for when you don't care whether
 it exists or not: existsAsFile, existsAsDir, existsAsSymlink,
 tryRename, trySymlink, tryCopy, tryMkdir, tryMkdirRecurse, tryRmdir,
 tryRmdirRecurse, tryRemove: All check whether the source path exists
 and return WITHOUT throwing if there's nothing to do.
I think creating a new family of functions is not the best way to do this. How about a function/template that wraps a function instead? In Phobos, we have collectException(fun(...)), but something shorter like attempt!fun(...) would be better for scripting.
I'm not entirely opposed to that. I didn't pursue it for two reasons, though: 1. I was lazy and just didn't feel like writing a function forwarding template. 2. They don't all have the semantics. For example, tryMkdir does nothing and returns false if the path DOES exist, but tryRmdir must behave that way if the path DOESN'T exist. Flipping either of those around wouldn't make sense. So this way, I can have each func do the right thing without the user thinking about it, or having to come up with a system fancy enough to handle all that in one template.
 The biggest advantage to this approach is that such wrappers are
 composable.
Composability is good, but since the goal of Scriptlike is to simplify common script needs as much as possible, one of my intents is to wrap common compositions into single one-part identifiers. "No needless wrappers" is good for Phobos, but works against my goals for Scriptlike. That said, *implementing* the existsAs*() and try*() functions by using compositional elements might be a good idea.
 For example, in my library I have safeUpdate, which wraps a
 function that creates a file by redirecting its output to a temporary
 file, then replacing the target atomically with one rename() call, and
 obtainUsing, which skips the operation entirely if the target file
 already exists (useful for expensive operations such as downloading a
 file). The latter implies the former, so that e.g. a program terminated
 in the middle of the download does not consider that file to have been
 downloaded successfully in the previous invocation.
Nice. I like that. I may steal some of that ;)
 - One simple call, runShell, to run a shell command script-style (ie,
 synchronously with forwarded stdout/in/err) from any working

 for v2.066 - BTW, thanks all involved who fixed that.)
Sorry about breaking it in the first place ;)
:) Command escaping rules are admittedly a pain, *especially* on Windows where it tends to resemble a certain rabbit-hole Alice once discovered...
 But I would avoid running things "shell-like" though, as it's a mess of
 whitespace/escaping. Passing arguments as an array rather than a string
 is much better for many reasons.
Yea. I was thinking of providing "arguments as an array" too, but I haven't had a chance to give it much thought yet. But keep in mind, Path.toString() automatically quotes paths with spaces (use Path.toRawString if you don't want that), plus it ensures "current directory" remains as "." and never "", so those alone should reduce a fair amount of potential trouble.
 - One simple function, fail(string msg), to help you exit with an
 error message in an exception-safe way. (Does require some minor
 boilerplate added to your main().)
I think this idea combines even better when the main() function is created for you.
Agreed. As it is right now, "fail" is more of a semi-standardized convention than an actual implementation, since the real work is done in main by the user.
Feb 11 2014