Now that we know how to create a console application, let’s put that knowledge to use. The rest of this chapter is concerned with writing filter programs as console apps. After an overview of how filter programs work, we’re going to discuss processing the command line and efficient file
operations. We’ll be cutting a wide swath through Delphi’s standard run-time library, and won’t have time to discuss every function in detail. Remember that online help is your friend—use it early and often.
Your Basic Filter Program
As I mentioned at the beginning of this chapter, filter programs typically accept a command line that specifies options and input/output filenames, crunch the input as specified by the options, and produce the output file.
Given that general description, there’s lots of room for improvisation. A line counter program, for example, could accept multiple input file names
(including wildcards), and could have options that tell it to report not only the number of text lines in a file, but also the number of words and characters, and possibly a frequency distribution of words and characters. In a more involved program, the output can be a simple transformation of a single input file, a single file that combines multiple input files, or many different files created from a single input file.
Despite the differences in complexity, filters share a large amount of common functionality. They all process the command line, read input files, and write output files. Only the intermediate processing step changes significantly from one program to another. Because of this commonality, it’s possible to build a group of functions that provide the common functionality and allows you to quickly build a custom filter program by simply defining how the command line is to be parsed and writing the code for the “processing” step. The input, output, and command line parsing portions are all there. Kind of a dehydrated filter program—just add processing.
Previous Table of Contents Next
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.
Brief Full Advanced Search
Search Tips
To access the contents, click the chapter and section titles.
Kick Ass Delphi Programming
(Publisher: The Coriolis Group)
Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin ISBN: 1576100448
Publication Date: 09/01/96
Search this book:
Previous Table of Contents Next
Processing the Command Line
Command line processing sounds so simple. Given a text string that represents the command line, we want to parse out the file names and options, and set the
program’s variables accordingly. I’m continually amazed at how difficult such a simple-sounding thing can be. Fortunately, Object Pascal has two standard functions, ParamCount and ParamStr, that make things a bit easier.
ParamCount simply returns a count of the parameters on the command line. So if your command line is “MyFilter file1.txt file2.txt”, ParamCount will return 2.
The program name itself isn’t counted as a parameter by this function.
ParamStr accepts an integer and returns a string that contains the command line argument that corresponds to that integer. For example, given the above command line, this statement
WriteLn (ParamStr (1));
will output ‘file1.txt’ (without the quotes).
If you pass 0 to ParamStr, the returned string will contain the full path and file name of the program that’s currently being executed.
The Params program shown in Listing 1.2 illustrates the use of ParamCount and ParamStr. To create this program, select File|New from Delphi’s main menu, select the Console Application item from the Projects page of the New Items dialog box, and then tell Delphi where to put your new application. Be sure to save the project as Params.dpr before you modify it.
Listing 1.2 The Params program {$APPTYPE CONSOLE}
{
Go!
Keyword
---Go!
Params -- a simple exploration of the ParamCount and ParamStr functions.
}
program params;
uses Windows;
Var
i : Integer;
begin
WriteLn ('Program: ', ParamStr (0));
WriteLn ('ParamCount = ', ParamCount);
WriteLn ('Parameters');
WriteLn ('---');
for i := 1 to ParamCount do begin
WriteLn (ParamStr (i));
end;
Write ('Press Enter...');
ReadLn;
end.
If you want to test the program from within Delphi, you need to select
Run|Parameters from Delphi’s main menu and enter the command line that you want to have passed to the program. For the above example, you’d enter the string
“file1.txt file2.txt” (without the quotes) in the Run parameters dialog box.
Simple, no? Unfortunately, not so simple. Back in the days of DOS and Windows 3.1, things really were simple. But then along came long file names with
embedded spaces. Now we have a problem. You see, ParamCount and
ParamStr assume that command line arguments are separated by spaces. This works fine as long as your files don’t have embedded spaces, but try this command line:
params c:\program files\borland\delphi 2.0\readme.txt ParamCount returns 3, and the individual parameters it reports are
c:\program
files\borland\delphi 2.0\readme.txt
which is clearly not what we intended! (Okay, so maybe long file names aren’t all peaches and cream—beer and skittles if you’re British. Warm beer.)
I won’t go into all the possible solutions to this problem. If you want a full discussion of this problem and the possible solutions (none of which are
satisfactory, by the way—thank you Microsoft), get a copy of Lou Grinzo’s Zen of Windows 95 Programming, also published by Coriolis Group Books. The book is ostensibly about C and C++ programming for Windows 95, but there’s a wealth
of good information in there for all programmers, especially in the way of writing bug-free programs. This book is among the top three programming books I’ve ever read, along with Writing Solid Code and Debugging the Development Process, both written by Steve Maguire and published by Microsoft Press.
The only workable (although not satisfactory) solution to the embedded spaces problem is to require that file names containing embedded spaces be surrounded by quotation marks. So our sample command line becomes:
params "c:\program files\borland\delphi 2.0\readme.txt"
I guess you could require that your users always pass the short version of the file name, but I suspect they’d be more upset having to enter this
params c:\progra<1\borland\delphi<1.0\readme.txt than they would be about the quotation marks.
Command Line Options
Most (definitely not all) command line programs get their parameters via the command line. Sometimes you’ll see programs that receive their parameters from environment variables or configuration files, and some are hybrids that can accept parameters from the command line or from a configuration file whose name is specified on the command line. Since we don’t want to get too bogged down in parameter processing, we’re going to ignore the configuration files and
environment variables, and concentrate solely on command line parameters.
You’ve probably used a command line tool (like DIR) that accepts options prefaced by a slash (/). For example, if you want a listing of files in the current directory and all of its subdirectories, you’d enter the command: DIR /S. Many programs also accept command line parameters prefaced by the dash (or minus sign, -). Both are common, and many programs will accept either.
File names, on the other hand, are specified in many different ways, depending on the tool. COPY, for example, lets you specify the name of the input and output files without prefacing them with option characters. So COPY FILE1 FILE2 copies FILE1 to FILE2. Borland’s MAKE program, on the other hand, requires that you preface the input file name with the -f parameter. So, to process
BUILD.MAK, you’d enter this command: MAKE -fbuild.mak.
The way MAKE processes command lines is easier, because everything is an option. Every option is separated from the others by at lease one space, and file names are handled just like other options—there aren’t any special cases. This is the model we’re going to use for our filter programs.
In general, there are four kinds of command line options: switches, numbers, strings, and file names. Switches simply turn an option on or off. A text filter, for example, might have a switch option to convert all lower-case characters to
upper-case. Numbers can be integers or floating point, and can be specified in any number of ways: decimal and hexadecimal being the most common. Strings and file names are similar, although file names are often validated to ensure that they’re properly formed.
Previous Table of Contents Next
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.
Brief Full Advanced Search
Search Tips
To access the contents, click the chapter and section titles.
Kick Ass Delphi Programming (Publisher: The Coriolis Group)
Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin ISBN: 1576100448
Publication Date: 09/01/96
Search this book:
Previous Table of Contents Next
A Reusable Command Line Parser
If there’s anything I dislike about programming, it’s slogging through dozens (or hundreds) of lines of code to do something for the tenth (or hundredth) time. Command line parsing is like that: Every filter program has to process the command line, and command line parsing is boring code after you’ve written it once or twice. So I keep trying to come up with a generalized command line parser that will, with a minimum of effort on my part, parse a command line and fill in my program’s options structure. That way, I’ll have more time to spend on the filter (the real problem, after all) rather than on the command line parser.
A generalized command line parser is not an easy piece of code to write, and even a minimal one can be a bit involved. The one we’ll develop here is minimal, but functional for many applications.
The basic idea is to define the valid option characters, the type of each option, and the default value for each option. The structure that contains this information is passed to the command line parser, which chews up the command line and fills in the values for the individual options that it finds. If it finds an error (an invalid option or a number where it expected a switch), it spits out an error message, stops processing, and returns an error status to the calling function. Simple, right? Ahhh...but not so easy.
An individual options record takes the format of the OptionsRec structure shown in Listing 1.3. This listing contains the full source of the CmdLine unit. You should create a new file in your editor, and enter and save this code as CMDLINE.PAS.
Listing 1.3 The CmdLine unit
{
CMDLINE.PAS -- Command line parameters parsing }
unit cmdline;
interface type
OptionType = (otBool, otInt, otString, otFilename);
pOptionRec = ^OptionRec;
OptionRec = record OptionChar : char;
otFilename : (Filename : ShortString);
end;
pOptionsArray = ^OptionsArray;
OptionsArray = Array [1..1] of OptionRec;
{
GetOptionRec -- return a pointer to the options record in the passed Options array that corresponds to the specified option character. Returns Nil if the option character is not in the passed Options array.
}
ProcessCommandLine -- process the command line according to the parameters list passed in the Options array. Returns True if successful, or False if an error occurred in processing.
}
GetOptionRec -- return a pointer to the options record in the passed Options array that corresponds to the specified option character. Returns Nil if the option character is not in the passed Options array.
}
if (Options^[i].OptionChar = OptionChar) then begin Result := @Options^[i].OptionChar;
ProcessBool
Extract the on/off state for a parameter. If the passed Param is a blank string, it is assumed to be On (+). Otherwise the routine expects the string to start with + or -, and sets the OnOff variable accordingly.
}
Extract an integer from the passed command line parameter.
}
WriteLn ('Error: integer expected');
Exit;
WriteLn ('Error: integer expected');
Result := False;
end;
end;
{
ProcessString
Copy the passed string to the Option variable. No error checking is performed, and a blank string is considered a valid parameter.
}
Extract a file name from the passed command line parameter.
Currently, this function just calls ProcessString to copy the string parameter to the Filename. It could, in the future, check to see if the string represents a valid file name, or it could be used to expand a short filename to a full path/file.
}
Result := ProcessString (Param, Filename);
end;
{
CheckParam
Check the passed Param, representing one command-line argument, against the list of options. If the option character is valid, then process the option based on its type (Boolean, Integer, String, or Filename).
Returns True if option processed and stored correctly, False otherwise.
}
Rec := GetOptionRec (Options, nOptions, Param[2]);
if (Rec <> Nil) then begin
Given a list of option characters and parameter types, check each command line argument against the list and set the values in the options structure accordingly.
Returns True if all parameters processed and stored successfully.
}
for ParamNo := 1 to ParamCount do begin if (Not CheckParam (ParamStr (ParamNo),
Options, nOptions)) then begin
Previous Table of Contents Next
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.
Brief Full Advanced Search
Search Tips
To access the contents, click the chapter and section titles.
Kick Ass Delphi Programming
(Publisher: The Coriolis Group)
Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin ISBN: 1576100448
Publication Date: 09/01/96
Search this book:
Previous Table of Contents Next
OptionType is an enumerated type that describes the kinds of options that
ProcessCommandLine knows about. The OptionRec record has three fields: the option character, the option type, and a variant portion that has a field that will hold the value for the particular option type. (If you’re not familiar with variant records, take a peek at the help topic that discusses record types, or pick up a Pascal primer at your local bookstore.)
The OptionRec record, as coded, is a pretty inefficient way to solve this problem because each record, regardless of the type of option, occupies the maximum possible size. A ShortString type takes 256 bytes, which means that most of the records are much larger than they need to be. There are several ways around this, probably the most straightforward being to use pointers to strings, rather than strings themselves, for string and filename types. I choose not to implement that here because of the extra coding involved.
The other problem with this implementation also has to do with the ShortString type.
The longest possible string that can be stored in a ShortString is 255 characters, which is shorter than the maximum path length Windows will accept (260 bytes). I had hoped to use Delphi’s AnsiString (i.e. “long string”) type for this reason, but long string types can’t be stored in the variant portion of a record. Again, the most obvious solution would be to use string pointers.
Even with those problems, CmdLine is quite useful. The extra memory required shouldn’t be a problem because most programs have only a handful of options, and there’s no silly 64K limit on the size of static data anymore (it’s a wide 32-bit world out there!). The filename length limitation is a bit of a bother, but I don’t know too many people (like, none) who’re going to be typing 256-character path names into a command line tool.
The CmdLine unit makes two functions available to calling programs: GetOptionRec and ProcessCommandLine. GetOptionRec will return a pointer to the record that corresponds to the specified option character. If no record exists for that option, then GetOptionRec returns Nil. ProcessCommandLine is the real workhorse. You pass it
Go!
Keyword
---Go!
an array of OptionRec structures, and it parses the command line, filling in the value fields for the individual options. If ProcessCommandLine processes all of the
command line arguments without encountering an error, it returns True. If it encounters an error at any point, it immediately stops processing the command line, displays an error message, and returns False to the calling program.
Testing the CmdLine Unit
In order to test the command line parsing functions, we need a test program. Start a new application using the Console Application template. Save the new project as
FILTER.DPR, and copy CMDLINE.PAS (Listing 1.3) to the directory that contains the new project. Then, select File|Add to Project to add the CmdLine unit to your new project.
The Filter project will be the testbed for the CmdLine unit and the file I/O unit that we’ll be working on next. When we’re done with those units, we’re going to save the entire thing in the repository so that we have a template for other filter programs.
To test CmdLine, we need an array of options structures and some code that will call ProcessCommandLine. The test program, which you should enter into FILTER.DPR, is shown in Listing 1.4.
Listing 1.4 Testing the CmdLine unit with FILTER.DPR {$APPTYPE CONSOLE}
program filter;
uses Windows, CmdLine;
const
nOptions = 4;
Options : Array [1..nOptions] of OptionRec = (
(OptionChar : 'i'; Option : otFilename; Filename : ''), (OptionChar : 'o'; Option : otFilename; Filename : ''), (OptionChar : 'n'; Option : otInt; Value : 36),
(OptionChar : 'd'; Option : otBool; OnOff : False) );
var
cRslt : Boolean;
Rec : pOptionRec;
begin
cRslt := CmdLine.ProcessCommandLine (@Options, nOptions);
WriteLn ('ProcessCommandLine returned ', cRslt);
Rec := CmdLine.GetOptionRec (@Options, nOptions, 'i');
WriteLn ('i = ', Rec^.Filename);
Rec := CmdLine.GetOptionRec (@Options, nOptions, 'o');
WriteLn ('o = ', Rec^.Filename);
Rec := CmdLine.GetOptionRec (@Options, nOptions, 'n');
WriteLn ('n = ', Rec^.Value);
Rec := CmdLine.GetOptionRec (@Options, nOptions, 'd');
WriteLn ('d = ', Rec^.OnOff);
Write ('Press Enter...');
ReadLn;
end.
The options table is initialized in the const section of the program, and then
ProcessCommandLine is called to read the command line arguments and store the options’ values in the options table. The program then displays the return result of ProcessCommandLine, and also displays the value of each option.
Try this program with a bunch of different command lines. Be sure to try some invalid command lines along with the valid ones, just to make sure that the error handling is working properly. Here are some suggested test cases:
-iInFile.txt -oOutFile.txt -n995 -d {valid}
-n8.94 {Error: integer expected}
-x {Invalid option character: x}
The generalized command line parser provided in CmdLine makes picking out program options very easy. Just fill in a table, pass it to ProcessCommandLine, and it’s done for you. All you have to do is ensure that any required options are specified, and set your program’s internal variables according to the options that the user specified.
Believe me, it’s much easier than writing a custom command line parser for every different program.
Previous Table of Contents Next
Products | Contact Us | About Us | Privacy | Ad Info | Home
Use of this site is subject to certain Terms & Conditions, Copyright © 1996-2000 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permission of EarthWeb is prohibited. Read EarthWeb's privacy statement.
Brief Full Advanced Search
Search Tips
To access the contents, click the chapter and section titles.
Kick Ass Delphi Programming
(Publisher: The Coriolis Group)
Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin ISBN: 1576100448
Publication Date: 09/01/96
Search this book:
Search this book: