I've been mostly chatter lately, so I figure it's time to poot forth some code!
I've been increasingly trying to incorporate Functional Programming into my everyday coding, and with .NET Framework that means anonymous delegates.
My current project produced a command-line tool as part of the infrastructure. As usual, command-line tools require command-line arguments with the associated parsing. How many times does that get re-written? Pretty much every time.
I've coded this task many times before. I wanted something simple, but reusable. My disadvantage from previous implementations was lack of closures to bridge the scopes between my tool's option parsing, and the argument-traversal algorithm.
Here is what I ended up with:
static void processArgs(
string[] args,
Converter<String, int> argcount,
Action<String[]> argproc
)
{
int ix = 0;
while (ix < args.Length) {
String argx = args[ix];
ix++;
if (argx.StartsWith("-")) {
int ac = argcount(argx);
List<String> collect = new List<String>();
collect.Add(argx);
while (ac > 0 && ix < args.Length) {
collect.Add(args[ix]);
ix++;
ac--;
}
argproc(collect.ToArray());
}
}
} So argcount takes a string (starting with -) and returns how many additional "arguments" should follow it, for stuff like -dir directory.
The argproc takes the collected items and processes them.
Let's take a look at the call-site to get a better picture:
bool wait = false;
String outputf = null;
String template = null;
List comppath = new List();
processArgs(args,
(parg) => {
if (String.Equals(parg, "-dir", StringComparison.InvariantCultureIgnoreCase) ||
String.Equals(parg, "-out", StringComparison.InvariantCultureIgnoreCase) ||
String.Equals(parg, "-template", StringComparison.InvariantCultureIgnoreCase)) {
return 1;
}
return 0;
},
(pargs) => {
if (String.Equals(pargs[0], "-dir", StringComparison.InvariantCultureIgnoreCase)) {
if (pargs.Length < 2)
throw new NotSupportedException(pargs[0] + ": missing argument");
comppath.Add(pargs[1]);
}
else if (String.Equals(pargs[0], "-out", StringComparison.InvariantCultureIgnoreCase)) {
if (pargs.Length < 2)
throw new NotSupportedException(pargs[0] + ": missing argument");
outputf = pargs[1];
}
else if (String.Equals(pargs[0], "-template", StringComparison.InvariantCultureIgnoreCase)) {
if (pargs.Length < 2)
throw new NotSupportedException(pargs[0] + ": missing argument");
template = pargs[1];
}
else if (String.Equals(pargs[0], "-defdir", StringComparison.InvariantCultureIgnoreCase)) {
comppath.Add(Settings.Default.ComponentsPath);
}
else if (String.Equals(pargs[0], "-wait", StringComparison.InvariantCultureIgnoreCase)) {
wait = true;
}
else {
throw new NotSupportedException("Unrecognized option: " + pargs[0]);
}
}
);
Without mixing of contexts or storing the results of all the processing, it becomes easy to tweak the command-line option handling from the point in the source code that makes the most sense.
Now if you're not down with if/else chains, you could refactor (hint: use Dictionarys), but the good news is, that doesn't affect the core algorithm, only your call-site.
qed.
No comments:
Post a Comment