Skip to content

Commit 8170b74

Browse files
committed
Add Utils.FileIO unit & tests to DUnitX tests
Utils.FileIO unit was moved from original CodeSnip source, renamed from UIOUtils. Modified Utils.FileIO as copied as follows: * Made necessary changes to compile w/ Delphi12 * Added extra assertions to guard against nil encodings * Fixed bug in TStream override of TFileIO.CheckBOM * Added TFileIO.IsEqualBytes method overloads based on routines of same name from main CodeSnip source's UUtils unit. * Renamed EIOUtils exception to EFileIO * Changed Delphi XE style standard var declarations to more modern inline var declarations. * Reformatted some XMLDoc comments. Added new cupola/tests/data/Test.Utils.FileIO directory containing several test text files in various encodings for use in Utils.FileIO tests.
1 parent 0b18d28 commit 8170b74

11 files changed

+983
-88
lines changed

Src/UIOUtils.pas renamed to cupola/src/CSLE.Utils.FileIO.pas

Lines changed: 99 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,43 +3,68 @@
33
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
44
* obtain one at https://mozilla.org/MPL/2.0/
55
*
6-
* Copyright (C) 2009-2021, Peter Johnson (gravatar.com/delphidabbler).
6+
* Copyright (C) 2009-2024, Peter Johnson (gravatar.com/delphidabbler).
77
*
8-
* Provides a container for assisting with common file operations.
8+
* Record with methods that groups common file operations.
9+
*
10+
* Copied from main CodeSnip Delphi XE source code UIOUtils units with
11+
* IsEqualBytes overloaded methods were copied from routines in the UUtils unit.
12+
* Modified to compile with Delphi 12 and later.
913
}
1014

1115

12-
unit UIOUtils;
16+
unit CSLE.Utils.FileIO;
1317

1418

1519
interface
1620

1721

1822
uses
19-
// Delphi
20-
SysUtils, Classes, Types;
23+
System.SysUtils,
24+
System.Classes,
25+
System.Types,
26+
CSLE.Exceptions;
2127

2228
type
23-
/// <summary>
24-
/// Container for methods that assist with common file operations.
29+
/// <summary>Container for methods that assist with common file operations.
2530
/// </summary>
26-
/// <remarks>
27-
/// TFileIO is used instead of IOUtils.TFile because the assumptions TFile
28-
/// makes about the use of byte order marks with encoded text files are not
29-
/// compatible with the needs of this program.
30-
/// </remarks>
31+
/// <remarks>TFileIO is used instead of IOUtils.TFile because the assumptions
32+
/// TFile makes about the use of byte order marks with encoded text files are
33+
/// not compatible with the needs of this program.</remarks>
3134
TFileIO = record
3235
strict private
33-
/// <summary>
34-
/// Appends whole contents of a byte array to a stream.
35-
/// </summary>
36+
37+
/// <summary>Test a given number of bytes from the start of two byte arrays
38+
/// for equality.</summary>
39+
/// <param name="BA1">[in] First byte array to be compared.</param>
40+
/// <param name="BA2">[in] Second byte array to be compared.</param>
41+
/// <param name="Count">[in] Number of bytes to be compared. Must
42+
/// be greater than zero.</param>
43+
/// <returns>True if the required number of bytes in the arrays are equal
44+
/// and both arrays have at least Count bytes. Otherwise False is returned.
45+
/// </returns>
46+
/// <remarks>If either BA1 or BA1 have less than Count bytes then False is
47+
/// returned, regardless of whether the arrays are equal.</remarks>
48+
class function IsEqualBytes(const BA1, BA2: array of Byte;
49+
const Count: Integer): Boolean; overload; static;
50+
51+
/// <summary>Checks if two byte arrays are equal.</summary>
52+
/// <param name="BA1">[in] First byte array to be compared.</param>
53+
/// <param name="BA2">[in] Second byte array to be compared.</param>
54+
/// <returns>True if the two arrays are equal, False if not.</returns>
55+
/// <remarks>If both arrays are empty they are considered equal.</remarks>
56+
class function IsEqualBytes(const BA1, BA2: array of Byte): Boolean;
57+
overload; static;
58+
59+
/// <summary>Appends whole contents of a byte array to a stream.</summary>
3660
class procedure BytesToStream(const Bytes: TBytes; const Stream: TStream);
3761
static;
38-
/// <summary>
39-
/// Copies content of a whole stream into a byte array.
40-
/// </summary>
62+
63+
/// <summary>Copies content of a whole stream into a byte array.</summary>
4164
class function StreamToBytes(const Stream: TStream): TBytes; static;
65+
4266
public
67+
4368
/// <summary>Checks if given byte array begins with the BOM of the given
4469
/// encoding.</summary>
4570
/// <remarks>
@@ -50,6 +75,7 @@ TFileIO = record
5075
/// </remarks>
5176
class function CheckBOM(const Bytes: TBytes; const Encoding: TEncoding):
5277
Boolean; overload; static;
78+
5379
/// <summary>Checks if given stream begins with the BOM of the given
5480
/// encoding.</summary>
5581
/// <remarks>
@@ -62,6 +88,7 @@ TFileIO = record
6288
/// </remarks>
6389
class function CheckBOM(const Stream: TStream; const Encoding: TEncoding):
6490
Boolean; overload; static;
91+
6592
/// <summary>Checks if given file begins with the BOM of the given
6693
/// encoding.</summary>
6794
/// <remarks>
@@ -72,18 +99,15 @@ TFileIO = record
7299
/// </remarks>
73100
class function CheckBOM(const FileName: TFileName;
74101
const Encoding: TEncoding): Boolean; overload; static;
75-
/// <summary>
76-
/// Writes all the bytes from a byte array to a file.
77-
/// </summary>
102+
103+
/// <summary>Writes all the bytes from a byte array to a file.</summary>
78104
/// <param name="FileName">string [in] Name of file.</param>
79105
/// <param name="Bytes">TBytes [in] Array of bytes to be written to file.
80106
/// </param>
81107
class procedure WriteAllBytes(const FileName: string; const Bytes: TBytes);
82108
static;
83109

84-
/// <summary>
85-
/// Writes text to a file.
86-
/// </summary>
110+
/// <summary>Writes text to a file.</summary>
87111
/// <param name="FileName">string [in] Name of file.</param>
88112
/// <param name="Content">string [in] Text to be written to file.</param>
89113
/// <param name="Encoding">TEncoding [in] Encoding to be used for text in
@@ -94,9 +118,8 @@ TFileIO = record
94118
class procedure WriteAllText(const FileName, Content: string;
95119
const Encoding: TEncoding; const UseBOM: Boolean = False); static;
96120

97-
/// <summary>
98-
/// Writes lines of text to a text file with lines separated by CRLF.
99-
/// </summary>
121+
/// <summary>Writes lines of text to a text file with lines separated by
122+
/// CRLF.</summary>
100123
/// <param name="FileName">string [in] Name of file.</param>
101124
/// <param name="Lines">array of string [in] Array of lines of text to be
102125
/// written.</param>
@@ -109,16 +132,12 @@ TFileIO = record
109132
const Lines: array of string; const Encoding: TEncoding;
110133
const UseBOM: Boolean = False); static;
111134

112-
/// <summary>
113-
/// Reads all bytes from a file into a byte array.
114-
/// </summary>
135+
/// <summary>Reads all bytes from a file into a byte array.</summary>
115136
/// <param name="FileName">string [in] Name of file.</param>
116137
/// <returns>TBytes array containing the file's contents.</returns>
117138
class function ReadAllBytes(const FileName: string): TBytes; static;
118139

119-
/// <summary>
120-
/// Reads all the text from a text file.
121-
/// </summary>
140+
/// <summary>Reads all the text from a text file.</summary>
122141
/// <param name="FileName">string [in] Name of file.</param>
123142
/// <param name="Encoding">TEncoding [in] Text encoding used by file.
124143
/// </param>
@@ -130,9 +149,7 @@ TFileIO = record
130149
class function ReadAllText(const FileName: string;
131150
const Encoding: TEncoding; const HasBOM: Boolean = False): string; static;
132151

133-
/// <summary>
134-
/// Reads all the lines of text from a text file.
135-
/// </summary>
152+
/// <summary>Reads all the lines of text from a text file.</summary>
136153
/// <param name="FileName">string [in] Name of file.</param>
137154
/// <param name="Encoding">TEncoding [in] Text encoding used by file.
138155
/// </param>
@@ -145,9 +162,7 @@ TFileIO = record
145162
const Encoding: TEncoding; const HasBOM: Boolean = False):
146163
TStringDynArray; static;
147164

148-
/// <summary>
149-
/// Copies content of one file to another.
150-
/// </summary>
165+
/// <summary>Copies content of one file to another.</summary>
151166
/// <param name="SrcFileName">string [in] Name of file to be copied.
152167
/// </param>
153168
/// <param name="DestFileName">string [in] Name of file to receive
@@ -158,23 +173,17 @@ TFileIO = record
158173
end;
159174

160175
type
161-
/// <summary>Class of exception raised by UIOUtils code.</summary>
162-
EIOUtils = class(Exception);
176+
/// <summary>Class of exception raised by TFileIO methods.</summary>
177+
EFileIO = class(Exception);
163178

164179

165180
implementation
166181

167182

168-
uses
169-
// Project
170-
UUtils;
171-
172-
173183
resourcestring
174184
// Error messages
175185
sBadBOM = 'Preamble of file %s does not match expected encoding';
176186

177-
178187
{ TFileIO }
179188

180189
class procedure TFileIO.BytesToStream(const Bytes: TBytes;
@@ -186,29 +195,26 @@ class procedure TFileIO.BytesToStream(const Bytes: TBytes;
186195

187196
class function TFileIO.CheckBOM(const Bytes: TBytes; const Encoding: TEncoding):
188197
Boolean;
189-
var
190-
Preamble: TBytes;
191198
begin
192199
Assert(Assigned(Encoding), 'TFileIO.CheckBOM: Encoding is nil');
193-
Preamble := Encoding.GetPreamble;
200+
var Preamble := Encoding.GetPreamble;
194201
if Length(Preamble) = 0 then
195202
Exit(False);
196203
Result := IsEqualBytes(Bytes, Preamble, Length(Preamble));
197204
end;
198205

199206
class function TFileIO.CheckBOM(const Stream: TStream;
200207
const Encoding: TEncoding): Boolean;
201-
var
202-
Bytes: TBytes;
203-
Preamble: TBytes;
204-
OldPos: Int64;
205208
begin
206209
Assert(Assigned(Stream), 'TFileIO.CheckBOM: Stream is nil');
207210
Assert(Assigned(Encoding), 'TFileIO.CheckBOM: Encoding is nil');
208-
Preamble := Encoding.GetPreamble;
211+
var Preamble := Encoding.GetPreamble;
212+
if Length(Preamble) = 0 then
213+
Exit(False);
209214
if Stream.Size < Length(Preamble) then
210215
Exit(False);
211-
OldPos := Stream.Position;
216+
var OldPos: Int64 := Stream.Position;
217+
var Bytes: TBytes;
212218
SetLength(Bytes, Length(Preamble));
213219
Stream.Position := 0;
214220
Stream.ReadBuffer(Pointer(Bytes)^, Length(Preamble));
@@ -218,11 +224,9 @@ class function TFileIO.CheckBOM(const Stream: TStream;
218224

219225
class function TFileIO.CheckBOM(const FileName: TFileName;
220226
const Encoding: TEncoding): Boolean;
221-
var
222-
Stream: TStream;
223227
begin
224228
Assert(Assigned(Encoding), 'TFileIO.CheckBOM: Encoding is nil');
225-
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
229+
var Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
226230
try
227231
Result := CheckBOM(Stream, Encoding);
228232
finally
@@ -235,11 +239,31 @@ class procedure TFileIO.CopyFile(const SrcFileName, DestFileName: string);
235239
TFileIO.WriteAllBytes(DestFileName, TFileIO.ReadAllBytes(SrcFileName));
236240
end;
237241

242+
class function TFileIO.IsEqualBytes(const BA1, BA2: array of Byte): Boolean;
243+
begin
244+
if Length(BA1) <> Length(BA2) then
245+
Exit(False);
246+
for var I := 0 to Pred(Length(BA1)) do
247+
if BA1[I] <> BA2[I] then
248+
Exit(False);
249+
Result := True;
250+
end;
251+
252+
class function TFileIO.IsEqualBytes(const BA1, BA2: array of Byte;
253+
const Count: Integer): Boolean;
254+
begin
255+
Assert(Count > 0, 'TFileIO.IsEqualBytes: Count must be greater than zero');
256+
if (Length(BA1) < Int64(Count)) or (Length(BA2) < Int64(Count)) then
257+
Exit(False);
258+
for var I := 0 to Pred(Count) do
259+
if BA1[I] <> BA2[I] then
260+
Exit(False);
261+
Result := True;
262+
end;
263+
238264
class function TFileIO.ReadAllBytes(const FileName: string): TBytes;
239-
var
240-
FS: TFileStream;
241265
begin
242-
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
266+
var FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyNone);
243267
try
244268
Result := StreamToBytes(FS);
245269
finally
@@ -249,16 +273,13 @@ class function TFileIO.ReadAllBytes(const FileName: string): TBytes;
249273

250274
class function TFileIO.ReadAllLines(const FileName: string;
251275
const Encoding: TEncoding; const HasBOM: Boolean): TStringDynArray;
252-
var
253-
Lines: TStrings;
254-
I: Integer;
255276
begin
256277
Assert(Assigned(Encoding), 'TFileIO.ReadAllLines: Encoding is nil');
257-
Lines := TStringList.Create;
278+
var Lines := TStringList.Create;
258279
try
259280
Lines.Text := ReadAllText(FileName, Encoding, HasBOM);
260281
SetLength(Result, Lines.Count);
261-
for I := 0 to Pred(Lines.Count) do
282+
for var I := 0 to Pred(Lines.Count) do
262283
Result[I] := Lines[I];
263284
finally
264285
Lines.Free;
@@ -267,20 +288,16 @@ class function TFileIO.ReadAllLines(const FileName: string;
267288

268289
class function TFileIO.ReadAllText(const FileName: string;
269290
const Encoding: TEncoding; const HasBOM: Boolean): string;
270-
var
271-
Content: TBytes;
272-
SizeOfBOM: Integer;
273291
begin
274-
Assert(Assigned(Encoding), 'TFileIO.ReadAllBytes: Encoding is nil');
275-
Content := ReadAllBytes(FileName);
292+
Assert(Assigned(Encoding), 'TFileIO.ReadAllText: Encoding is nil');
293+
var Content := ReadAllBytes(FileName);
294+
var SizeOfBOM: Integer := 0;
276295
if HasBOM then
277296
begin
278297
SizeOfBOM := Length(Encoding.GetPreamble);
279298
if (SizeOfBOM > 0) and not CheckBOM(Content, Encoding) then
280-
raise EIOUtils.CreateFmt(sBadBOM, [FileName]);
281-
end
282-
else
283-
SizeOfBOM := 0;
299+
raise EFileIO.CreateFmt(sBadBOM, [FileName]);
300+
end;
284301
Result := Encoding.GetString(Content, SizeOfBOM, Length(Content) - SizeOfBOM);
285302
end;
286303

@@ -294,10 +311,8 @@ class function TFileIO.StreamToBytes(const Stream: TStream): TBytes;
294311

295312
class procedure TFileIO.WriteAllBytes(const FileName: string;
296313
const Bytes: TBytes);
297-
var
298-
FS: TFileStream;
299314
begin
300-
FS := TFileStream.Create(FileName, fmCreate);
315+
var FS := TFileStream.Create(FileName, fmCreate);
301316
try
302317
BytesToStream(Bytes, FS);
303318
finally
@@ -308,13 +323,11 @@ class procedure TFileIO.WriteAllBytes(const FileName: string;
308323
class procedure TFileIO.WriteAllLines(const FileName: string;
309324
const Lines: array of string; const Encoding: TEncoding;
310325
const UseBOM: Boolean);
311-
var
312-
Line: string;
313-
SB: TStringBuilder;
314326
begin
315-
SB := TStringBuilder.Create;
327+
Assert(Assigned(Encoding), 'TFileIO.WriteAllLines: Encoding is nil');
328+
var SB := TStringBuilder.Create;
316329
try
317-
for Line in Lines do
330+
for var Line in Lines do
318331
SB.AppendLine(Line);
319332
WriteAllText(FileName, SB.ToString, Encoding, UseBOM);
320333
finally
@@ -324,10 +337,9 @@ class procedure TFileIO.WriteAllLines(const FileName: string;
324337

325338
class procedure TFileIO.WriteAllText(const FileName, Content: string;
326339
const Encoding: TEncoding; const UseBOM: Boolean);
327-
var
328-
FS: TFileStream;
329340
begin
330-
FS := TFileStream.Create(FileName, fmCreate);
341+
Assert(Assigned(Encoding), 'TFileIO.WriteAllText: Encoding is nil');
342+
var FS := TFileStream.Create(FileName, fmCreate);
331343
try
332344
if UseBOM then
333345
BytesToStream(Encoding.GetPreamble, FS);

cupola/tests/CodeSnip.Cupola.Tests.dpr

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ uses
4343
Test.Snippets.TestInfo in 'Test.Snippets.TestInfo.pas',
4444
CSLE.Consts in '..\src\CSLE.Consts.pas',
4545
CSLE.IniData in '..\src\CSLE.IniData.pas',
46-
Test.IniData in 'Test.IniData.pas';
46+
Test.IniData in 'Test.IniData.pas',
47+
CSLE.Utils.FileIO in '..\src\CSLE.Utils.FileIO.pas',
48+
Test.Utils.FileIO in 'Test.Utils.FileIO.pas';
4749

4850
{$IFNDEF TESTINSIGHT}
4951
var

cupola/tests/CodeSnip.Cupola.Tests.dproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@
102102
<DCCReference Include="..\src\CSLE.Consts.pas"/>
103103
<DCCReference Include="..\src\CSLE.IniData.pas"/>
104104
<DCCReference Include="Test.IniData.pas"/>
105+
<DCCReference Include="..\src\CSLE.Utils.FileIO.pas"/>
106+
<DCCReference Include="Test.Utils.FileIO.pas"/>
105107
<BuildConfiguration Include="Base">
106108
<Key>Base</Key>
107109
</BuildConfiguration>

0 commit comments

Comments
 (0)