p4ruby 2022.1.2359956-x64-mingw-ucrt

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,700 @@
1
+ /*******************************************************************************
2
+
3
+ Copyright (c) 2007-2011, Perforce Software, Inc. All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright
12
+ notice, this list of conditions and the following disclaimer in the
13
+ documentation and/or other materials provided with the distribution.
14
+
15
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18
+ ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE SOFTWARE, INC. BE LIABLE FOR ANY
19
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
+
26
+ *******************************************************************************/
27
+
28
+ /*******************************************************************************
29
+ * Name : specmgr.cc
30
+ *
31
+ * Description : Ruby bindings for the Perforce API. Class for handling
32
+ * Perforce specs. This class provides other classes with
33
+ * generic support for parsing and formatting Perforce
34
+ * specs.
35
+ *
36
+ ******************************************************************************/
37
+ #include <ctype.h>
38
+ #include <ruby.h>
39
+ #include "p4utils.h"
40
+ #include "undefdups.h"
41
+ #include <p4/clientapi.h>
42
+ #include <p4/strops.h>
43
+ #include <p4/spec.h>
44
+ #include <p4/strtable.h>
45
+ #include "p4rubyconf.h"
46
+ #include "gc_hack.h"
47
+ #include "p4rubydebug.h"
48
+ #include "p4specdata.h"
49
+ #include "specmgr.h"
50
+
51
+ struct defaultspec {
52
+ const char *type;
53
+ const char *spec;
54
+ } speclist[] = {
55
+ {
56
+ "branch",
57
+ "Branch;code:301;rq;ro;fmt:L;len:32;;"
58
+ "Update;code:302;type:date;ro;fmt:L;len:20;;"
59
+ "Access;code:303;type:date;ro;fmt:L;len:20;;"
60
+ "Owner;code:304;fmt:R;len:32;;"
61
+ "Description;code:306;type:text;len:128;;"
62
+ "Options;code:309;type:line;len:32;val:"
63
+ "unlocked/locked;;"
64
+ "View;code:311;type:wlist;words:2;len:64;;"
65
+ },
66
+ {
67
+ "change",
68
+ "Change;code:201;rq;ro;fmt:L;seq:1;len:10;;"
69
+ "Date;code:202;type:date;ro;fmt:R;seq:3;len:20;;"
70
+ "Client;code:203;ro;fmt:L;seq:2;len:32;;"
71
+ "User;code:204;ro;fmt:L;seq:4;len:32;;"
72
+ "Status;code:205;ro;fmt:R;seq:5;len:10;;"
73
+ "Type;code:211;seq:6;type:select;fmt:L;len:10;"
74
+ "val:public/restricted;;"
75
+ "ImportedBy;code:212;type:line;ro;fmt:L;len:32;;"
76
+ "Identity;code:213;type:line;;"
77
+ "Description;code:206;type:text;rq;seq:7;;"
78
+ "JobStatus;code:207;fmt:I;type:select;seq:9;;"
79
+ "Jobs;code:208;type:wlist;seq:8;len:32;;"
80
+ "Stream;code:214;type:line;len:64;;"
81
+ "Files;code:210;type:llist;len:64;;"
82
+ },
83
+ {
84
+ "client",
85
+ "Client;code:301;rq;ro;seq:1;len:32;;"
86
+ "Update;code:302;type:date;ro;seq:2;fmt:L;len:20;;"
87
+ "Access;code:303;type:date;ro;seq:4;fmt:L;len:20;;"
88
+ "Owner;code:304;seq:3;fmt:R;len:32;;"
89
+ "Host;code:305;seq:5;fmt:R;len:32;;"
90
+ "Description;code:306;type:text;len:128;;"
91
+ "Root;code:307;rq;type:line;len:64;;"
92
+ "AltRoots;code:308;type:llist;len:64;;"
93
+ "Options;code:309;type:line;len:64;val:"
94
+ "noallwrite/allwrite,noclobber/clobber,nocompress/compress,"
95
+ "unlocked/locked,nomodtime/modtime,normdir/rmdir;;"
96
+ "SubmitOptions;code:313;type:select;fmt:L;len:25;val:"
97
+ "submitunchanged/submitunchanged+reopen/revertunchanged/"
98
+ "revertunchanged+reopen/leaveunchanged/leaveunchanged+reopen;;"
99
+ "LineEnd;code:310;type:select;fmt:L;len:12;val:"
100
+ "local/unix/mac/win/share;;"
101
+ "Stream;code:314;type:line;len:64;;"
102
+ "StreamAtChange;code:316;type:line;len:64;;"
103
+ "ServerID;code:315;type:line;ro;len:64;;"
104
+ "Type;code:318;type:select;len:10;val:"
105
+ "writeable/readonly/graph/partitioned;;"
106
+ "Backup;code:319;type:select;len:10;val:enable/disable;;"
107
+ "View;code:311;type:wlist;words:2;len:64;;"
108
+ "ChangeView;code:317;type:llist;len:64;;"
109
+ },
110
+ {
111
+ "depot",
112
+ "Depot;code:251;rq;ro;len:32;;"
113
+ "Owner;code:252;len:32;;"
114
+ "Date;code:253;type:date;ro;len:20;;"
115
+ "Description;code:254;type:text;len:128;;"
116
+ "Type;code:255;rq;len:10;;"
117
+ "Address;code:256;len:64;;"
118
+ "Suffix;code:258;len:64;;"
119
+ "StreamDepth;code:260;len:64;;"
120
+ "Map;code:257;rq;len:64;;"
121
+ "SpecMap;code:259;type:wlist;len:64;;"
122
+ },
123
+ {
124
+ "group",
125
+ "Group;code:401;rq;ro;len:32;;"
126
+ "Description;code:NNN;type:text;fmt:L:len:128;;"
127
+ "MaxResults;code:402;type:word;len:12;;"
128
+ "MaxScanRows;code:403;type:word;len:12;;"
129
+ "MaxLockTime;code:407;type:word;len:12;;"
130
+ "MaxOpenFiles;code:413;type:word;len:12;;"
131
+ "Timeout;code:406;type:word;len:12;;"
132
+ "PasswordTimeout;code:409;type:word;len:12;;"
133
+ "LdapConfig;code:410;type:line;len:128;;"
134
+ "LdapSearchQuery;code:411;type:line;len:128;;"
135
+ "LdapUserAttribute;code:412;type:line;len:128;;"
136
+ "LdapUserDNAttribute;code:414;type:line;len:128;;"
137
+ "Subgroups;code:404;type:wlist;len:32;opt:default;;"
138
+ "Owners;code:408;type:wlist;len:32;opt:default;;"
139
+ "Users;code:405;type:wlist;len:32;opt:default;;"
140
+ },
141
+ {
142
+ "job",
143
+ "Job;code:101;rq;len:32;;"
144
+ "Status;code:102;type:select;rq;len:10;"
145
+ "pre:open;val:open/suspended/closed;;"
146
+ "User;code:103;rq;len:32;pre:$user;;"
147
+ "Date;code:104;type:date;ro;len:20;pre:$now;;"
148
+ "Description;code:105;type:text;rq;pre:$blank;;"
149
+ },
150
+ {
151
+ "label",
152
+ "Label;code:301;rq;ro;fmt:L;len:32;;"
153
+ "Update;code:302;type:date;ro;fmt:L;len:20;;"
154
+ "Access;code:303;type:date;ro;fmt:L;len:20;;"
155
+ "Owner;code:304;fmt:R;len:32;;"
156
+ "Description;code:306;type:text;len:128;;"
157
+ "Options;code:309;type:line;len:64;val:"
158
+ "unlocked/locked,noautoreload/autoreload;;"
159
+ "Revision;code:312;type:word;words:1;len:64;;"
160
+ "ServerID;code:315;type:line;ro;len:64;;"
161
+ "View;code:311;type:wlist;len:64;;"
162
+ },
163
+ {
164
+ "ldap",
165
+ "Name;code:801;rq;len:32;;"
166
+ "Host;code:802;rq;type:word;words:1;len:128;;"
167
+ "Port;code:803;rq;type:word;words:1;len:5;;"
168
+ "Encryption;code:804;rq;len:10;val:"
169
+ "none/ssl/tls;;"
170
+ "BindMethod;code:805;rq;len:10;val:"
171
+ "simple/search/sasl;;"
172
+ "Options;code:816;type:line;len:64;val:"
173
+ "nodowncase/downcase,nogetattrs/getattrs,"
174
+ "norealminusername/realminusername;;"
175
+ "SimplePattern;code:806;type:line;len:128;;"
176
+ "SearchBaseDN;code:807;type:line;len:128;;"
177
+ "SearchFilter;code:808;type:line;len:128;;"
178
+ "SearchScope;code:809;len:10;val:"
179
+ "baseonly/children/subtree;;"
180
+ "SearchBindDN;code:810;type:line;len:128;;"
181
+ "SearchPasswd;code:811;type:line;len:128;;"
182
+ "SaslRealm;code:812;type:word;words:1;len:128;;"
183
+ "GroupBaseDN;code:813;type:line;len:128;;"
184
+ "GroupSearchFilter;code:814;type:line;len:128;;"
185
+ "GroupSearchScope;code:815;len:10;val:"
186
+ "baseonly/children/subtree;;"
187
+ "AttributeUid;code:817;type:word;len:128;;"
188
+ "AttributeName;code:818;type:line;len:128;;"
189
+ "AttributeEmail;code:819;type:word;len:128;;"
190
+ },
191
+ {
192
+ "license",
193
+ "License;code:451;len:32;;"
194
+ "License-Expires;code:452;len:10;;"
195
+ "Support-Expires;code:453;len:10;;"
196
+ "Customer;code:454;type:line;len:128;;"
197
+ "Application;code:455;len:32;;"
198
+ "IPaddress;code:456;len:24;;"
199
+ "IPservice;code:461;type:wlist;len:24;;"
200
+ "Platform;code:457;len:32;;"
201
+ "Clients;code:458;len:8;;"
202
+ "Users;code:459;len:8;;"
203
+ "Files;code:460;len:8;;"
204
+ "Repos;code:462;len:8;;"
205
+ "ExtraCapabilities;code:463;type:llist;len:512;;"
206
+ },
207
+ {
208
+ "protect",
209
+ "SubPath;code:502;ro;len:64;;"
210
+ "Update;code:503;type:date;ro;fmt:L;len:20;;"
211
+ "Protections;code:501;fmt:C;type:wlist;words:5;opt:default;z;len:64;;"
212
+ },
213
+ {
214
+ "remote",
215
+ "RemoteID;code:851;rq;ro;fmt:L;len:32;;"
216
+ "Address;code:852;rq;type:line;len:32;;"
217
+ "Owner;code:853;fmt:R;len:32;;"
218
+ "RemoteUser;code:861;fmt:R;len:32;;"
219
+ "Options;code:854;type:line;len:32;val:"
220
+ "unlocked/locked,nocompress/compress,copyrcs/nocopyrcs;;"
221
+ "Update;code:855;type:date;ro;fmt:L;len:20;;"
222
+ "Access;code:856;type:date;ro;fmt:L;len:20;;"
223
+ "Description;code:857;type:text;len:128;;"
224
+ "LastFetch;code:858;fmt:L;len:10;;"
225
+ "LastPush;code:859;fmt:L;len:10;;"
226
+ "DepotMap;code:860;type:wlist;words:2;len:64;;"
227
+ "ArchiveLimits;code:862;type:wlist;words:2;len:64;;"
228
+ },
229
+ {
230
+ "repo",
231
+ "Repo;code:1001;rq;ro;fmt:L;len:128;;"
232
+ "Owner;code:1002;fmt:R;len:32;;"
233
+ "Created;code:1003;type:date;ro;fmt:L;len:20;;"
234
+ "Pushed;code:1004;type:date;ro;fmt:R;len:20;;"
235
+ "ForkedFrom;code:1005;ro;fmt:L;len:128;;"
236
+ "Description;code:1006;type:text;len:128;;"
237
+ "DefaultBranch;code:1007;fmt:L;len:32;;"
238
+ "MirroredFrom;code:1008;fmt:R;len:32;;"
239
+ "Options;code:1009;type:select;len:10;val:lfs/nolfs;;"
240
+ "GconnMirrorServerId;code:1010;fmt:L;len:32;;"
241
+ "GconnMirrorSecretToken;code:NNN;len:36;;"
242
+ "GconnMirrorStatus;code:NNN;len:8;;"
243
+ "GconnMirrorExcludedBranches;code:NNN;len:256;;"
244
+ "GconnMirrorHideFetchUrl;code:NNN;len:5;;"
245
+ },
246
+ {
247
+ "server",
248
+ "ServerID;code:751;rq;ro;len:32;;"
249
+ "Type;code:752;rq;len:32;;"
250
+ "Name;code:753;type:line;len:32;;"
251
+ "Address;code:754;type:line;len:32;;"
252
+ "ExternalAddress;code:755;type:line;len:32;;"
253
+ "Services;code:756;rq;len:128;;"
254
+ "Options;code:764;type:line;len:32;val:"
255
+ "nomandatory/mandatory;;"
256
+ "ReplicatingFrom;code:765;type:line;len:32;;"
257
+ "Description;code:757;type:text;len:128;;"
258
+ "User;code:761;type:line;len:64;;"
259
+ "AllowedAddresses;code:763;type:wlist;len:64;;"
260
+ "UpdateCachedRepos;code:766;type:wlist;len:64;;"
261
+ "ClientDataFilter;code:758;type:wlist;len:64;;"
262
+ "RevisionDataFilter;code:759;type:wlist;len:64;;"
263
+ "ArchiveDataFilter;code:760;type:wlist;len:64;;"
264
+ "DistributedConfig;code:762;type:text;len:128;;"
265
+ },
266
+ {
267
+ "spec",
268
+ "Fields;code:351;type:wlist;words:5;rq;;"
269
+ "Words;code:352;type:wlist;words:2;;"
270
+ "Formats;code:353;type:wlist;words:3;;"
271
+ "Values;code:354;type:wlist;words:2;;"
272
+ "Presets;code:355;type:wlist;words:2;;"
273
+ "Openable;code:362;type:wlist;words:2;;"
274
+ "Maxwords;code:361;type:wlist;words:2;;"
275
+ "Comments;code:356;type:text;;"
276
+ },
277
+ {
278
+ "stream",
279
+ "Stream;code:701;rq;ro;len:64;;"
280
+ "Update;code:705;type:date;ro;fmt:L;len:20;;"
281
+ "Access;code:706;type:date;ro;fmt:L;len:20;;"
282
+ "Owner;code:704;len:32;open:isolate;;"
283
+ "Name;code:703;rq;type:line;len:32;open:isolate;;"
284
+ "Parent;code:702;rq;len:64;open:isolate;;"
285
+ "Type;code:708;rq;len:32;open:isolate;;"
286
+ "Description;code:709;type:text;len:128;open:isolate;;"
287
+ "Options;code:707;type:line;len:64;val:"
288
+ "allsubmit/ownersubmit,unlocked/locked,"
289
+ "toparent/notoparent,fromparent/nofromparent,"
290
+ "mergedown/mergeany;open:isolate;;"
291
+ "ParentView;code:NNN;rq;open:isolate;"
292
+ "pre:inherit;val:noinherit/inherit;;"
293
+ "Components;code:NNN;type:wlist;words:3;maxwords:4;len:64;open:propagate;fmt:C;;"
294
+ "Paths;code:710;rq;type:wlist;words:2;maxwords:3;len:64;open:propagate;fmt:C;;"
295
+ "Remapped;code:711;type:wlist;words:2;len:64;open:propagate;fmt:C;;"
296
+ "Ignored;code:712;type:wlist;words:1;len:64;open:propagate;fmt:C;;"
297
+ "View;code:713;type:wlist;words:2;len:64;;"
298
+ "ChangeView;code:714;type:llist;ro;len:64;;"
299
+ },
300
+ {
301
+ "triggers",
302
+ "Triggers;code:551;type:wlist;words:4;len:64;opt:default;z;;"
303
+ },
304
+ {
305
+ "typemap",
306
+ "TypeMap;code:601;type:wlist;words:2;len:64;opt:default;z;;"
307
+ },
308
+ {
309
+ "user",
310
+ "User;code:651;rq;ro;seq:1;len:32;;"
311
+ "Type;code:659;ro;fmt:R;len:10;;"
312
+ "Email;code:652;fmt:R;rq;seq:3;len:32;;"
313
+ "Update;code:653;fmt:L;type:date;ro;seq:2;len:20;;"
314
+ "Access;code:654;fmt:L;type:date;ro;len:20;;"
315
+ "FullName;code:655;fmt:R;type:line;rq;len:32;;"
316
+ "JobView;code:656;type:line;len:64;;"
317
+ "Password;code:657;len:32;;"
318
+ "AuthMethod;code:662;fmt:L;len:10;val:"
319
+ "perforce/perforce+2fa/ldap/ldap+2fa;;"
320
+ "Reviews;code:658;type:wlist;len:64;;"
321
+ },
322
+ { 0, 0 }
323
+ };
324
+
325
+ SpecMgr::SpecMgr()
326
+ {
327
+ debug = 0;
328
+ specs = 0;
329
+ convertArray = 1;
330
+ Reset();
331
+ }
332
+
333
+ SpecMgr::~SpecMgr()
334
+ {
335
+ delete specs;
336
+ }
337
+
338
+ void
339
+ SpecMgr::AddSpecDef( const char *type, StrPtr &specDef )
340
+ {
341
+ if( specs->GetVar( type ) )
342
+ specs->RemoveVar( type );
343
+ specs->SetVar( type, specDef );
344
+ }
345
+
346
+ void
347
+ SpecMgr::AddSpecDef( const char *type, const char *specDef )
348
+ {
349
+ if( specs->GetVar( type ) )
350
+ specs->RemoveVar( type );
351
+ specs->SetVar( type, specDef );
352
+ }
353
+
354
+
355
+ void
356
+ SpecMgr::Reset()
357
+ {
358
+ delete specs;
359
+ specs = new StrBufDict;
360
+
361
+ for( struct defaultspec *sp = &speclist[ 0 ]; sp->type; sp++ )
362
+ AddSpecDef( sp->type, sp->spec );
363
+
364
+ }
365
+
366
+ int
367
+ SpecMgr::HaveSpecDef( const char *type )
368
+ {
369
+ return specs->GetVar( type ) != 0;
370
+ }
371
+
372
+ //
373
+ // Convert a Perforce StrDict into a Ruby hash. Convert multi-level
374
+ // data (Files0, Files1 etc. ) into (nested) array members of the hash.
375
+ //
376
+
377
+ VALUE
378
+ SpecMgr::StrDictToHash( StrDict *dict, VALUE hash )
379
+ {
380
+ StrRef var, val;
381
+ int i;
382
+
383
+ if( hash == Qnil )
384
+ hash = rb_hash_new();
385
+
386
+ for ( i = 0; dict->GetVar( i, var, val ); i++ )
387
+ {
388
+ if ( var == "specdef" || var == "func" || var == "specFormatted" )
389
+ continue;
390
+
391
+ InsertItem( hash, &var, &val );
392
+ }
393
+ return hash;
394
+ }
395
+
396
+ //
397
+ // Convert a Perforce StrDict into a P4::Spec object
398
+ //
399
+
400
+ VALUE
401
+ SpecMgr::StrDictToSpec( StrDict *dict, StrPtr *specDef )
402
+ {
403
+
404
+ // This converts it to a string, and then to a hash, so we go from one
405
+ // type of dictionary to another, via an intermediate form (a StrBuf).
406
+
407
+ Error e;
408
+ SpecDataTable dictData( dict );
409
+ #if P4APIVER_ID >= 513538
410
+ Spec s( specDef->Text(), "", &e );
411
+ #else
412
+ Spec s( specDef->Text(), "" );
413
+ #endif
414
+ StrBuf form;
415
+
416
+ if( e.Test() ) return Qfalse;
417
+
418
+ // Format the StrDict into a StrBuf object
419
+ s.Format( &dictData, &form );
420
+
421
+ // Now parse the StrBuf into a new P4::Spec object
422
+ VALUE spec = NewSpec( specDef );
423
+ SpecDataRuby hashData( spec );
424
+
425
+ s.ParseNoValid( form.Text(), &hashData, &e );
426
+ if( e.Test() ) return Qfalse;
427
+
428
+ // Now see if there are any extraTag fields as we'll need to
429
+ // add those fields into our output. Just iterate over them
430
+ // extracting the fields and inserting them as we go.
431
+ int i = 0;
432
+ StrRef et("extraTag" );
433
+ for( i = 0; ; i++ )
434
+ {
435
+ StrBuf tag;
436
+ StrPtr *var;
437
+ StrPtr *val;
438
+
439
+ tag << et << i;
440
+ if( !(var = dict->GetVar( tag ) ) )
441
+ break;
442
+
443
+ val = dict->GetVar( *var );
444
+ if( !val ) continue;
445
+
446
+ InsertItem( spec, var, val );
447
+ }
448
+
449
+ return spec;
450
+ }
451
+
452
+ VALUE
453
+ SpecMgr::StringToSpec( const char *type, const char *form, Error *e )
454
+ {
455
+
456
+ StrPtr * specDef = specs->GetVar( type );
457
+ VALUE hash = NewSpec( specDef );
458
+ SpecDataRuby specData( hash );
459
+ #if P4APIVER_ID >= 513538
460
+ Spec s( specDef->Text(), "", e );
461
+ #else
462
+ Spec s( specDef->Text(), "" );
463
+ #endif
464
+
465
+ if( !e->Test() )
466
+ s.ParseNoValid( form, &specData, e );
467
+
468
+ if ( e->Test() )
469
+ return Qfalse;
470
+
471
+ return hash;
472
+ }
473
+
474
+
475
+ //
476
+ // Format routine. updates a StrBuf object with the form content.
477
+ // The StrBuf can then be converted to a Ruby string where required.
478
+ //
479
+ void
480
+ SpecMgr::SpecToString( const char *type, VALUE hash, StrBuf &b, Error *e )
481
+ {
482
+
483
+ StrBuf buf;
484
+ StrPtr * specDef = specs->GetVar( type );
485
+ if ( !specDef )
486
+ {
487
+ e->Set( E_FAILED, "No specdef available. Cannot convert hash to a "
488
+ "Perforce form" );
489
+ return;
490
+ }
491
+
492
+ SpecDataRuby specData( hash );
493
+ #if P4APIVER_ID >= 513538
494
+ Spec s( specDef->Text(), "", e );
495
+ #else
496
+ Spec s( specDef->Text(), "" );
497
+ #endif
498
+
499
+ if( e->Test() ) return;
500
+
501
+ s.Format( &specData, &b );
502
+ }
503
+
504
+ //
505
+ // This method returns a hash describing the valid fields in the spec. To
506
+ // make it easy on our users, we map the lowercase name to the name defined
507
+ // in the spec. Thus, the users can always user lowercase, and if the field
508
+ // should be in mixed case, it will be. See P4::Spec::method_missing
509
+ //
510
+
511
+ VALUE
512
+ SpecMgr::SpecFields( const char *type )
513
+ {
514
+ return SpecFields( specs->GetVar( type ) );
515
+ }
516
+
517
+ VALUE
518
+ SpecMgr::SpecFields( StrPtr *specDef )
519
+ {
520
+ if( !specDef ) return Qnil;
521
+
522
+ //
523
+ // Here we abuse the fact that SpecElem::tag is public, even though it's
524
+ // only supposed to be public to SpecData's subclasses. It's hard to
525
+ // see that changing anytime soon, and it makes this so simple and
526
+ // reliable. So...
527
+ //
528
+ VALUE hash = rb_hash_new();
529
+ Error e;
530
+
531
+ #if P4APIVER_ID >= 513538
532
+ Spec s( specDef->Text(), "", &e );
533
+ #else
534
+ Spec s( specDef->Text(), "" );
535
+ #endif
536
+ if( e.Test() ) return Qnil;
537
+
538
+ for( int i = 0; i < s.Count(); i++ )
539
+ {
540
+ StrBuf k;
541
+ StrBuf v;
542
+ SpecElem * se = s.Get( i );
543
+
544
+ v = se->tag;
545
+ k = v;
546
+ StrOps::Lower( k );
547
+
548
+ rb_hash_aset(hash,
549
+ P4Utils::ruby_string( k.Text(), k.Length() ),
550
+ P4Utils::ruby_string( v.Text(), v.Length() ) );
551
+ }
552
+ return hash;
553
+ }
554
+
555
+ //
556
+ // Split a key into its base name and its index. i.e. for a key "how1,0"
557
+ // the base name is "how" and they index is "1,0". We work backwards from
558
+ // the end of the key looking for the first char that is neither a
559
+ // digit, nor a comma.
560
+ //
561
+
562
+ void
563
+ SpecMgr::SplitKey( const StrPtr *key, StrBuf &base, StrBuf &index )
564
+ {
565
+ int i = 0;
566
+
567
+ base = *key;
568
+ index = "";
569
+ for ( i = key->Length(); i; i-- )
570
+ {
571
+ char prev = (*key)[ i-1 ];
572
+ if ( !isdigit( prev ) && prev != ',' )
573
+ {
574
+ base.Set( key->Text(), i );
575
+ index.Set( key->Text() + i );
576
+ break;
577
+ }
578
+ }
579
+ }
580
+
581
+ //
582
+ // Insert an element into the response structure. The element may need to
583
+ // be inserted into an array nested deeply within the enclosing hash.
584
+ //
585
+
586
+ void
587
+ SpecMgr::InsertItem( VALUE hash, const StrPtr *var, const StrPtr *val )
588
+ {
589
+ VALUE ary = 0;
590
+ VALUE tary = 0;
591
+ VALUE key;
592
+ ID idLength = rb_intern( "length" );
593
+ StrBuf base, index;
594
+ StrRef comma( "," );
595
+
596
+ if (convertArray) {
597
+ SplitKey( var, base, index );
598
+ }
599
+
600
+ // If there's no index, then we insert into the top level hash
601
+ // but if the key is already defined then we need to rename the key. This
602
+ // is probably one of those special keys like otherOpen which can be
603
+ // both an array element and a scalar. The scalar comes last, so we
604
+ // just rename it to "otherOpens" to avoid trashing the previous key
605
+ // value
606
+ if ( index == "" )
607
+ {
608
+ ID idHasKey = rb_intern( "has_key?");
609
+ ID idPlus = rb_intern( "+" );
610
+
611
+ key = P4Utils::ruby_string( var->Text() );
612
+ if ( rb_funcall( hash, idHasKey, 1, key ) == Qtrue )
613
+ key = rb_funcall( key, idPlus, 1, P4Utils::ruby_string( "s" ) );
614
+
615
+ if( P4RDB_DATA )
616
+ fprintf( stderr, "... %s -> %s\n", StringValuePtr( key ), val->Text() );
617
+
618
+ rb_hash_aset( hash, key, P4Utils::ruby_string( val->Text() ) );
619
+ return;
620
+ }
621
+
622
+ //
623
+ // Get or create the parent array from the hash.
624
+ //
625
+ key = P4Utils::ruby_string( base.Text() );
626
+ ary = rb_hash_aref( hash, key );
627
+
628
+ if ( Qnil == ary )
629
+ {
630
+ ary = rb_ary_new();
631
+ rb_hash_aset( hash, key, ary );
632
+ }
633
+ else if( rb_obj_is_kind_of( ary, rb_cArray ) != Qtrue )
634
+ {
635
+ //
636
+ // There's an index in our var name, but the name is already defined
637
+ // and the value it contains is not an array. This means we've got a
638
+ // name collision. This can happen in 'p4 diff2' for example, when
639
+ // one file gets 'depotFile' and the other gets 'depotFile2'. In
640
+ // these cases it makes sense to keep the structure flat so we
641
+ // just use the raw variable name.
642
+ //
643
+ if( P4RDB_DATA )
644
+ fprintf( stderr, "... %s -> %s\n", var->Text(), val->Text() );
645
+
646
+ rb_hash_aset( hash, P4Utils::ruby_string( var->Text() ) ,
647
+ P4Utils::ruby_string( val->Text() ) );
648
+ return;
649
+ }
650
+
651
+ // The index may be a simple digit, or it could be a comma separated
652
+ // list of digits. For each "level" in the index, we need a containing
653
+ // array.
654
+ if( P4RDB_DATA )
655
+ fprintf( stderr, "... %s -> [", base.Text() );
656
+
657
+ for( const char *c = 0 ; ( c = index.Contains( comma ) ); )
658
+ {
659
+ StrBuf level;
660
+ level.Set( index.Text(), c - index.Text() );
661
+ index.Set( c + 1 );
662
+
663
+ // Found another level so we need to get/create a nested array
664
+ // under the current entry. We use the level as an index so that
665
+ // missing entries are left empty deliberately.
666
+
667
+ tary = rb_ary_entry( ary, level.Atoi() );
668
+ if ( ! RTEST( tary ) )
669
+ {
670
+ tary = rb_ary_new();
671
+ rb_ary_store( ary, level.Atoi(), tary );
672
+ }
673
+ if( P4RDB_DATA )
674
+ fprintf( stderr, "%s][", level.Text() );
675
+ ary = tary;
676
+ }
677
+ int pos = index.Atoi();
678
+
679
+ if( P4RDB_DATA )
680
+ fprintf( stderr, "%d] = %s\n", pos, val->Text() );
681
+
682
+ rb_ary_store( ary, pos, P4Utils::ruby_string( val->Text() ) );
683
+ }
684
+
685
+ //
686
+ // Create a new P4::Spec object and return it.
687
+ //
688
+
689
+ VALUE
690
+ SpecMgr::NewSpec( StrPtr *specDef )
691
+ {
692
+ ID idNew = rb_intern( "new" );
693
+ ID idP4 = rb_intern( "P4" );
694
+ ID idP4Spec = rb_intern( "Spec" );
695
+ VALUE cP4 = rb_const_get_at( rb_cObject, idP4 );
696
+ VALUE cP4Spec = rb_const_get_at( cP4, idP4Spec );
697
+ VALUE fields = SpecFields( specDef );
698
+
699
+ return rb_funcall( cP4Spec, idNew, 1, fields );
700
+ }