Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

(Ebook - Pdf) Kick Ass Delphi Programming

.pdf
Скачиваний:
284
Добавлен:
17.08.2013
Размер:
5.02 Mб
Скачать

ProcessString

Copy the passed string to the Option variable. No error checking is performed, and a blank string is considered a valid parameter.

}

function ProcessString

(

Param : String;

var Option : ShortString ) : Boolean;

begin

Option := Param;

Result := True; end;

{

ProcessFilename

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.

}

function ProcessFilename

(

Param : String;

var Filename : ShortString ) : Boolean;

begin

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.

}

function CheckParam

(

Param : String;

Options : pOptionsArray; nOptions : Integer

) : Boolean;

var

Rec : pOptionRec;

Option : String; begin

Result := False;

if (Param[1] in ['-', '/']) then begin if (Length (Param) < 2) then begin

WriteLn ('Invalid option'); end

else begin

Rec := GetOptionRec (Options, nOptions, Param[2]);

if (Rec <> Nil) then begin

Option := Copy (Param, 3, Length (Param) - 2); case Rec^.Option of

otBool :

Result := ProcessBool (Option, Rec.OnOff); otInt :

Result := ProcessInt (Option, Rec^.Value); otString :

Result := ProcessString (Option, Rec^.Param); otFilename :

Result := ProcessFilename (Option, Rec^.Filename); else

WriteLn ('Invalid option specification: ', Param[2]);

end; end

else begin

WriteLn ('Invalid option character: ', Param[2]); end;

end; end

else begin

WriteLn ('Error: options must start with - or /'); end;

end;

{

ProcessCommandLine

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.

}

function ProcessCommandLine

(

Options : pOptionsArray; nOptions : Integer

) : Boolean;

var

ParamNo : Integer;

begin

Result := True;

for ParamNo := 1 to ParamCount do begin if (Not CheckParam (ParamStr (ParamNo),

Options, nOptions)) then begin

Result := False; Exit;

end;

end;

end;

end.

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.

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

Go!

Keyword

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

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

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.

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.

To access the contents, click the chapter and section titles.

Kick Ass Delphi Programming

Go!

Keyword

(Publisher: The Coriolis Group)

Author(s): Don Taylor, Jim Mischel, John Penman, Terence Goggin

ISBN: 1576100448

Publication Date: 09/01/96

Search this book:

Go!

-----------

A Note on Program Structure

Before we start writing more involved programs, let’s move the processing code from the project (.DPR) file to a separate unit. I’ve found that it’s best to keep my own handwritten code out of the project file and in separate units, for several reasons.

For me, the most important point is that Delphi modifies the project file from time to time. I think it’s only when you change the project name or add a new unit to the project, but I’m not sure. I don’t know what Delphi is capable of changing, I haven’t seen it fully documented anywhere, and I’d sure be upset if it changed something that I thought was constant. On the flip side, I might inadvertantly change something that Delphi put there for a reason. This in itself is reason enough for me. Delphi rarely touches non-form units (to my knowledge, only when you select File|Save as to change the unit name), so I feel safer with my code in separate units.

Another reason is that the project file is difficult to debug. For some reason, I had trouble setting breakpoints and single-stepping through code that was in the .DPR file.

Finally, the project file is just that—a project file. From the structure of the example programs and the way that Delphi creates projects, I get the impression that the .DPR file was never meant to contain large amounts of executable code. The project file gathers the project’s units together for the project manager, and at run-time automatically, creates specific forms, and then runs the application. Seems to me that we should use the product in the way that it was intended.

So let’s split out the processing code and make FILTER.DPR essentially a one-liner. Listing 1.5 is the new FILTER.DPR, and Listing 1.6 contains FILTMAIN.PAS—the module that now contains all of the processing code.

Listing 1.5 The new Filter project file

{$APPTYPE CONSOLE}

program filter;

uses

cmdline in 'cmdline.pas', filtmain in 'filtmain.pas';

begin DoFilter;

end.

Listing 1.6 FILTMAIN: the Filter program's processing module

{

FILTMAIN.PAS -- main processing module for Filter program.

}

unit filtmain;

interface

{ DoFilter does all the processing } procedure DoFilter;

implementation

uses CmdLine;

procedure DoFilter; 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;

end.

Now the project file contains just what it’s supposed to contain—project build information and a “go” command. All of the programmer-written code is in FILTMAIN.PAS.

Reading and Writing Files

Once you’ve got command line parsing out of the way, the next big hurdle in a filter program is file I/O. Of course, if you’re doing a simple character-by-character (or line-by-line) translation of a text file, you can use Read and Write (or ReadLn and WriteLn) in conjunction with Eof and Eoln to process your file. For example, the DoFilter procedure shown in Listing 1.7 copies characters from input to output, translating the lower-case characters to upper-case along the way.

Listing 1.7 Translating characters from upper-case to lower-case

procedure DoFilter;

const

nOptions = 2;

Options : Array [1..nOptions] of OptionRec = ( (OptionChar : 'i'; Option : otFilename; Filename : ''), (OptionChar : 'o'; Option : otFilename; Filename : '') );

var

cRslt : Boolean; iRec : pOptionRec; oRec : pOptionRec; InputFile : Text; OutputFile : Text; c : char;

begin

cRslt := CmdLine.ProcessCommandLine (@Options, nOptions); if (not cRslt) then

Halt;

{ make sure input and output files were specified } iRec := CmdLine.GetOptionRec (@Options, nOptions, 'i'); if (iRec^.Filename = '') then begin

WriteLn ('Error: input file expected');

Halt;

end;

oRec := CmdLine.GetOptionRec (@Options, nOptions, 'o'); if (oRec^.Filename = '') then begin

WriteLn ('Error: output file expected'); Halt;

end;

{open input file -- no error checking } Assign (InputFile, iRec^.Filename); Reset (InputFile);

{create output file -- no error checking } Assign (OutputFile, oRec^.Filename); Rewrite (OutputFile);

{Read and translate each character }

while (not Eof (InputFile)) do begin Read (InputFile, c);

c := UpCase (c); Write (OutputFile, c);

end;

Close (InputFile); Close (OutputFile);

end;

There are two problems with this version of FILTER. First, it’s slow—kinda like a snake crawling out of a refrigerator. If you’ve got a megabyte-sized text file somewhere and a few minutes to spare, try it. The other problem is that the program only works on text files. That’s fine for a one-shot filter application, but we’re writing a filter template that’s going to be used by many different types of programs, some of which will have to work with non-text files, and they’ll all benefit from a speed improvement. What we need is a more general and much faster way to read characters (or bytes) from the file. We have to do our own buffering, which adds some complexity, but the results are well worth the effort.

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.