Menu

[r3166]: / trunk / Src / UCodeImportMgr.pas  Maximize  Restore  History

Download this file

360 lines (317 with data), 11.9 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
{
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/
*
* Copyright (C) 2008-2012, Peter Johnson (www.delphidabbler.com).
*
* $Rev$
* $Date$
*
* Implements a static class that handles import of a codesnip export file into
* the user-defined database.
}
unit UCodeImportMgr;
interface
uses
// Delphi
Generics.Collections, Generics.Defaults,
// Project
UCodeImportExport, UExceptions, UIStringList;
type
/// <summary>
/// Stores information about if and how a snippet is to be imported.
/// </summary>
TImportInfo = record
strict private
// Property values
fOrigName: string;
fImportAsName: string;
fSkip: Boolean;
public
/// <summary>Initialises properties to given values.</summary>
constructor Create(const AOrigName, AImportAsName: string;
const ASkip: Boolean = False);
/// <summary>Name of snippet per import file.</summary>
property OrigName: string read fOrigName;
/// <summary>Name of snippet to be used when updating database.</summary>
/// <remarks>Can be changed by user.</remarks>
property ImportAsName: string read fImportAsName write fImportAsName;
/// <summary>Flag indicating if snippet is to be skipped (ignored) when
/// updating database.</summary>
property Skip: Boolean read fSkip write fSkip;
end;
type
/// <summary>
/// Comparer for TImportInfo objects.
/// </summary>
TImportInfoComparer = class(TComparer<TImportInfo>, IComparer<TImportInfo>)
public
/// <summary>Compares Left and Right TImportInfo records. Returns -ve if
/// Left less than Right, 0 if equal or +ve if Left greater than
/// Right.</summary>
function Compare(const Left, Right: TImportInfo): Integer; override;
end;
type
/// <summary>
/// List of TImportInfo records. Designed to store descriptions of how to
/// import each snippet in an import file.
/// </summary>
TImportInfoList = class(TList<TImportInfo>)
public
/// <summary>Constructs list with appropriate comparer.</summary>
constructor Create;
/// <summary>Finds a record based on its OrigName field value.</summary>
/// <param name="Name">string [in] Name to be found.</param>
/// <param name="ImportInfo">TImportInfo [out] Found record. Undefined if
/// Name not found.</param>
/// <returns>Boolean: True if Name found, False if not.</returns>
function FindByName(const Name: string; out ImportInfo: TImportInfo):
Boolean;
/// <summary>Returns index of record in list whose OrigName field matches
/// given name or -1 if name not found.</summary>
function IndexOfName(const Name: string): Integer;
end;
type
/// <summary>
/// Manages import of a codesnip export file into the user-defined database.
/// </summary>
/// <remarks>
/// Designed for ease of interaction with a suitable UI.
/// </remarks>
TCodeImportMgr = class sealed(TObject)
strict private
var
/// <summary>List of snippet information read from import file.</summary>
fSnippetInfoList: TSnippetInfoList;
/// <summary>Value of ImportInfo property.</summary>
fImportInfoList: TImportInfoList;
/// <summary>Value of UserInfo property.</summary>
fUserInfo: TUserInfo;
/// <summary>Initialises import information list with details of snippets
/// read from import file.</summary>
procedure InitImportInfoList;
/// <summary>Returns list of names that can't be used to rename an imported
/// snippet.</summary>
/// <param name="ExcludedName">string [in] Name of snippet to be excluded
/// from import list.</param>
/// <returns>IStringList: List of disallowed snippet names.</returns>
/// <remarks>List is made up of all names of snippets in user database plus
/// names of all imported snippets except for ExcludedName. ExcludedName
/// should be the name of a snippet being renamed.</remarks>
function DisallowedNames(const ExcludedName: string): IStringList;
/// <summary>Returns a name for snippet SnippetName that does not already
/// exist in user database or imported snippet list.</summary>
/// <remarks>
/// <para>If SnippetName is not in user database then it is returned
/// unchanged.</para>
/// <para>If SnippetName is in user database then numbers are appended
/// sequentially until a unique name is found.</para>
/// </remarks>
function GetUniqueSnippetName(const SnippetName: string): string;
public
/// <summary>Constructor. Sets up object.</summary>
constructor Create;
/// <summary>Destructor. Tears down object.</summary>
destructor Destroy; override;
/// <summary>Imports snippet info from file without updating database.
/// </summary>
/// <remarks>ImportInfo list property is initialised ready for
/// customisation.</remarks>
procedure Import(const FileName: string);
/// <summary>Updates database based on imported snippets and customisation
/// described by ImportInfo property.</summary>
/// <remarks>Any existing snippets with same name as imported snippets are
/// overwritten.</remarks>
procedure UpdateDatabase;
/// <summary>Information about user who created the import file.</summary>
/// <remarks>May be null if no user info included in import file.</remarks>
property UserInfo: TUserInfo read fUserInfo;
/// <summary>List of information describing if and how to import snippets
/// in import file. Permits customisation of import.</summary>
property ImportInfo: TImportInfoList read fImportInfoList;
end;
type
/// <summary>
/// Class of exception raised when import manager encounters an anticipated
/// error.
/// </summary>
ECodeImportMgr = class(ECodeSnip);
implementation
uses
// Delphi
SysUtils, Classes,
// Project
ActiveText.UMain, DB.UMain, DB.USnippet, UIOUtils, USnippetIDs, UStrUtils;
{ TCodeImportMgr }
constructor TCodeImportMgr.Create;
begin
inherited Create;
SetLength(fSnippetInfoList, 0);
fImportInfoList := TImportInfoList.Create;
end;
destructor TCodeImportMgr.Destroy;
begin
fImportInfoList.Free;
SetLength(fSnippetInfoList, 0);
inherited;
end;
function TCodeImportMgr.DisallowedNames(const ExcludedName: string):
IStringList;
var
Snippet: TSnippet; // each snippet in user database
SnippetInfo: TSnippetInfo; // info about each imported snippet
begin
Result := TIStringList.Create;
Result.CaseSensitive := False;
for Snippet in Database.Snippets do
if Snippet.UserDefined then
Result.Add(Snippet.Name);
for SnippetInfo in fSnippetInfoList do
if not StrSameText(SnippetInfo.Name, ExcludedName) then
Result.Add(SnippetInfo.Name);
end;
function TCodeImportMgr.GetUniqueSnippetName(
const SnippetName: string): string;
var
UsedNames: IStringList; // list of snippet names in use
Postfix: Cardinal; // number to be appended to name to make unique
begin
UsedNames := DisallowedNames(SnippetName);
if not UsedNames.Contains(SnippetName) then
Exit(SnippetName);
Postfix := 1;
repeat
Inc(PostFix);
Result := SnippetName + IntToStr(PostFix);
until not UsedNames.Contains(Result);
end;
procedure TCodeImportMgr.Import(const FileName: string);
var
Data: TBytes; // content of import file as bytes
begin
fUserInfo := TUserInfo.CreateNul;
fImportInfoList.Clear;
try
Data := TFileIO.ReadAllBytes(FileName);
TCodeImporter.ImportData(fUserInfo, fSnippetInfoList, Data);
except
on E: EStreamError do
raise ECodeImportMgr.Create(E);
on E: ECodeImporter do
raise ECodeImportMgr.Create(E);
end;
InitImportInfoList;
end;
procedure TCodeImportMgr.InitImportInfoList;
var
SnippetInfo: TSnippetInfo; // info about each snippet in import file
begin
fImportInfoList.Clear;
for SnippetInfo in fSnippetInfoList do
begin
fImportInfoList.Add(
TImportInfo.Create(
SnippetInfo.Name, GetUniqueSnippetName(SnippetInfo.Name)
)
);
end;
end;
procedure TCodeImportMgr.UpdateDatabase;
// ---------------------------------------------------------------------------
// Adjusts a snippet's dependency list so that main database is searched for a
// required snippet if it is not in the user database.
procedure AdjustDependsList(const Depends: ISnippetIDList);
var
Idx: Integer; // loops through dependencies
SnippetID: TSnippetID; // each snippet ID in dependency list
begin
// NOTE: The data file format does not record which database a required
// snippet belongs to, so we first look in the user database and if it's
// not there, we assume the main database
for Idx := 0 to Pred(Depends.Count) do
begin
SnippetID := Depends[Idx];
SnippetID.UserDefined :=
Database.Snippets.Find(SnippetID.Name, True) <> nil;
Depends[Idx] := SnippetID;
end;
end;
/// Builds an active text representation of the contributing user's details.
function UserDetailsActiveText: IActiveText;
resourcestring
// user information prefix text
sContributorPrefix = 'Contributed by:';
begin
Result := TActiveTextFactory.CreateActiveText;
Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsOpen));
Result.AddElem(
TActiveTextFactory.CreateTextElem(
sContributorPrefix + ' ' + UserInfo.Details.ToString
)
);
Result.AddElem(TActiveTextFactory.CreateActionElem(ekPara, fsClose));
end;
// ---------------------------------------------------------------------------
var
Editor: IDatabaseEdit; // object used to update user database
Snippet: TSnippet; // reference any existing snippet to overwrite
SnippetInfo: TSnippetInfo; // info about each snippet from import file
ImportInfo: TImportInfo; // info about how / whether to import a snippet
resourcestring
// Error message
sBadNameError = 'Can''t find snippet "%s" in import data';
begin
Editor := Database as IDatabaseEdit;
for SnippetInfo in fSnippetInfoList do
begin
if not fImportInfoList.FindByName(SnippetInfo.Name, ImportInfo) then
raise EBug.CreateFmt(sBadNameError, [SnippetInfo.Name]);
if ImportInfo.Skip then
Continue;
AdjustDependsList(SnippetInfo.Data.Refs.Depends);
if UserInfo.Details.ToString <> '' then
SnippetInfo.Data.Props.Extra.Append(UserDetailsActiveText);
Snippet := Database.Snippets.Find(ImportInfo.ImportAsName, True);
if Assigned(Snippet) then
// snippet already exists: overwrite it
Editor.UpdateSnippet(Snippet, SnippetInfo.Data)
else
// snippet is new: add to database
Editor.AddSnippet(ImportInfo.ImportAsName, SnippetInfo.Data);
end;
end;
{ TImportInfo }
constructor TImportInfo.Create(const AOrigName, AImportAsName: string;
const ASkip: Boolean);
begin
fOrigName := AOrigName;
fImportAsName := AImportAsName;
fSkip := ASkip;
end;
{ TImportInfoComparer }
function TImportInfoComparer.Compare(const Left, Right: TImportInfo): Integer;
begin
Result := TSnippetID.CompareNames(Left.OrigName, Right.OrigName);
end;
{ TImportInfoList }
constructor TImportInfoList.Create;
begin
inherited Create(TImportInfoComparer.Create);
end;
function TImportInfoList.FindByName(const Name: string;
out ImportInfo: TImportInfo): Boolean;
var
Idx: Integer; // index of named snippet in list
begin
Idx := IndexOf(TImportInfo.Create(Name, ''));
if Idx = -1 then
Exit(False);
ImportInfo := Items[Idx];
Result := True;
end;
function TImportInfoList.IndexOfName(const Name: string): Integer;
begin
Result := IndexOf(TImportInfo.Create(Name, ''));
end;
end.
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.