Support tagging tuples and definitions
Rob Mensching committed
Oct 7, 2019 at 07:33 UTC
7e87a6b75d0570c3ebfbf0e537fcf04e1cf84224
4 files changed
+519
-4
src/WixToolset.Data/IntermediateTuple.cs
+159
@@ -9,6 +9,8 @@ namespace WixToolset.Data
9
[DebuggerDisplay("{DebuggerDisplay,nq}")]
10
public class IntermediateTuple
11
{
12
+ private object tags;
13
+
14
public IntermediateTuple(IntermediateTupleDefinition definition) : this(definition, null, null)
15
{
16
}
@@ -33,12 +35,132 @@ namespace WixToolset.Data
35
36
private string DebuggerDisplay => $"{this.Definition?.Name} {this.Id?.Id}";
37
38
+ public bool AddTag(string add)
39
+ {
40
+ if (this.tags == null)
41
+ {
42
+ this.tags = add;
43
+ }
44
+ else if (this.tags is string tag)
45
+ {
46
+ if (tag == add)
47
+ {
48
+ return false;
49
+ }
50
+
51
+ this.tags = new[] { tag, add };
52
+ }
53
+ else
54
+ {
55
+ var tagsArray = (string[])this.tags;
56
+ var array = new string[tagsArray.Length + 1];
57
+
58
+ for (var i = 0; i < tagsArray.Length; ++i)
59
+ {
60
+ if (tagsArray[i] == add)
61
+ {
62
+ return false;
63
+ }
64
+
65
+ array[i] = tagsArray[i];
66
+ }
67
+
68
+ array[tagsArray.Length] = add;
69
+
70
+ this.tags = array;
71
+ }
72
+
73
+ return true;
74
+ }
75
+
76
+ public bool HasTag(string has)
77
+ {
78
+ if (this.tags == null)
79
+ {
80
+ return false;
81
+ }
82
+ else if (this.tags is string tag)
83
+ {
84
+ return tag == has;
85
+ }
86
+ else
87
+ {
88
+ foreach (var element in (string[])this.tags)
89
+ {
90
+ if (element == has)
91
+ {
92
+ return true;
93
+ }
94
+ }
95
+ }
96
+
97
+ return false;
98
+ }
99
+
100
+ public bool RemoveTag(string remove)
101
+ {
102
+ if (this.tags is string tag)
103
+ {
104
+ if (tag == remove)
105
+ {
106
+ this.tags = null;
107
+ return true;
108
+ }
109
+ }
110
+ else if (this.tags is string[] tagsArray)
111
+ {
112
+ if (tagsArray.Length == 2)
113
+ {
114
+ if (tagsArray[0] == remove)
115
+ {
116
+ this.tags = tagsArray[1];
117
+ return true;
118
+ }
119
+ else if (tagsArray[1] == remove)
120
+ {
121
+ this.tags = tagsArray[0];
122
+ return true;
123
+ }
124
+ }
125
+ else
126
+ {
127
+ var array = new string[tagsArray.Length - 1];
128
+ var arrayIndex = 0;
129
+ var found = false;
130
+
131
+ for (var i = 0; i < tagsArray.Length; ++i)
132
+ {
133
+ if (tagsArray[i] == remove)
134
+ {
135
+ found = true;
136
+ continue;
137
+ }
138
+ else if (arrayIndex == array.Length)
139
+ {
140
+ break;
141
+ }
142
+
143
+ array[arrayIndex++] = tagsArray[i];
144
+ }
145
+
146
+ if (found)
147
+ {
148
+ this.tags = array;
149
+ return true;
150
+ }
151
+ }
152
+ }
153
+
154
+ return false;
155
+ }
156
+
157
internal static IntermediateTuple Deserialize(ITupleDefinitionCreator creator, Uri baseUri, JsonObject jsonObject)
158
{
159
var definitionName = jsonObject.GetValueOrDefault<string>("type");
160
var idJson = jsonObject.GetValueOrDefault<JsonObject>("id");
161
var sourceLineNumbersJson = jsonObject.GetValueOrDefault<JsonObject>("ln");
162
var fieldsJson = jsonObject.GetValueOrDefault<JsonArray>("fields");
163
+ var tagsJson = jsonObject.GetValueOrDefault<JsonArray>("tags");
164
165
var id = (idJson == null) ? null : Identifier.Deserialize(idJson);
166
var sourceLineNumbers = (sourceLineNumbersJson == null) ? null : SourceLineNumber.Deserialize(sourceLineNumbersJson);
@@ -54,6 +176,25 @@ namespace WixToolset.Data
176
}
177
}
178
179
+ if (tagsJson == null || tagsJson.Count == 0)
180
+ {
181
+ }
182
+ else if (tagsJson.Count == 1)
183
+ {
184
+ tuple.tags = (string)tagsJson[0];
185
+ }
186
+ else
187
+ {
188
+ var tags = new string[tagsJson.Count];
189
+
190
+ for (var i = 0; i < tagsJson.Count; ++i)
191
+ {
192
+ tags[i] = (string)tagsJson[i];
193
+ }
194
+
195
+ tuple.tags = tags;
196
+ }
197
+
198
return tuple;
199
}
200
@@ -86,6 +227,24 @@ namespace WixToolset.Data
227
228
jsonObject.Add("fields", fieldsJson);
229
230
+ if (this.tags is string || this.tags is string[])
231
+ {
232
+ JsonArray tagsJson;
233
+
234
+ if (this.tags is string tag)
235
+ {
236
+ tagsJson = new JsonArray(1) { tag };
237
+ }
238
+ else
239
+ {
240
+ var array = (string[])this.tags;
241
+ tagsJson = new JsonArray(array.Length);
242
+ tagsJson.AddRange(array);
243
+ }
244
+
245
+ jsonObject.Add("tags", tagsJson);
246
+ }
247
+
248
return jsonObject;
249
}
250
}
src/WixToolset.Data/IntermediateTupleDefinition.cs
+162
-1
@@ -7,6 +7,8 @@ namespace WixToolset.Data
7
8
public class IntermediateTupleDefinition
9
{
10
+ private object tags;
11
+
12
public IntermediateTupleDefinition(string name, IntermediateFieldDefinition[] fieldDefinitions, Type strongTupleType)
13
: this(TupleDefinitionType.MustBeFromAnExtension, name, 0, fieldDefinitions, strongTupleType)
14
{
@@ -53,11 +55,131 @@ namespace WixToolset.Data
55
return result;
56
}
57
58
+ public bool AddTag(string add)
59
+ {
60
+ if (this.tags == null)
61
+ {
62
+ this.tags = add;
63
+ }
64
+ else if (this.tags is string tag)
65
+ {
66
+ if (tag == add)
67
+ {
68
+ return false;
69
+ }
70
+
71
+ this.tags = new[] { tag, add };
72
+ }
73
+ else
74
+ {
75
+ var tagsArray = (string[])this.tags;
76
+ var array = new string[tagsArray.Length + 1];
77
+
78
+ for (var i = 0; i < tagsArray.Length; ++i)
79
+ {
80
+ if (tagsArray[i] == add)
81
+ {
82
+ return false;
83
+ }
84
+
85
+ array[i] = tagsArray[i];
86
+ }
87
+
88
+ array[tagsArray.Length] = add;
89
+
90
+ this.tags = array;
91
+ }
92
+
93
+ return true;
94
+ }
95
+
96
+ public bool HasTag(string has)
97
+ {
98
+ if (this.tags == null)
99
+ {
100
+ return false;
101
+ }
102
+ else if (this.tags is string tag)
103
+ {
104
+ return tag == has;
105
+ }
106
+ else
107
+ {
108
+ foreach (var element in (string[])this.tags)
109
+ {
110
+ if (element == has)
111
+ {
112
+ return true;
113
+ }
114
+ }
115
+ }
116
+
117
+ return false;
118
+ }
119
+
120
+ public bool RemoveTag(string remove)
121
+ {
122
+ if (this.tags is string tag)
123
+ {
124
+ if (tag == remove)
125
+ {
126
+ this.tags = null;
127
+ return true;
128
+ }
129
+ }
130
+ else if (this.tags is string[] tagsArray)
131
+ {
132
+ if (tagsArray.Length == 2)
133
+ {
134
+ if (tagsArray[0] == remove)
135
+ {
136
+ this.tags = tagsArray[1];
137
+ return true;
138
+ }
139
+ else if (tagsArray[1] == remove)
140
+ {
141
+ this.tags = tagsArray[0];
142
+ return true;
143
+ }
144
+ }
145
+ else
146
+ {
147
+ var array = new string[tagsArray.Length - 1];
148
+ var arrayIndex = 0;
149
+ var found = false;
150
+
151
+ for (var i = 0; i < tagsArray.Length; ++i)
152
+ {
153
+ if (tagsArray[i] == remove)
154
+ {
155
+ found = true;
156
+ continue;
157
+ }
158
+ else if (arrayIndex == array.Length)
159
+ {
160
+ break;
161
+ }
162
+
163
+ array[arrayIndex++] = tagsArray[i];
164
+ }
165
+
166
+ if (found)
167
+ {
168
+ this.tags = array;
169
+ return true;
170
+ }
171
+ }
172
+ }
173
+
174
+ return false;
175
+ }
176
+
177
internal static IntermediateTupleDefinition Deserialize(JsonObject jsonObject)
178
{
179
var name = jsonObject.GetValueOrDefault<string>("name");
180
var revision = jsonObject.GetValueOrDefault("rev", 0);
181
var definitionsJson = jsonObject.GetValueOrDefault<JsonArray>("fields");
182
+ var tagsJson = jsonObject.GetValueOrDefault<JsonArray>("tags");
183
184
var fieldDefinitions = new IntermediateFieldDefinition[definitionsJson.Count];
185
@@ -69,7 +191,28 @@ namespace WixToolset.Data
191
fieldDefinitions[i] = new IntermediateFieldDefinition(fieldName, fieldType);
192
}
193
72
- return new IntermediateTupleDefinition(name, revision, fieldDefinitions, null);
194
+ var definition = new IntermediateTupleDefinition(name, revision, fieldDefinitions, null);
195
+
196
+ if (tagsJson == null || tagsJson.Count == 0)
197
+ {
198
+ }
199
+ else if (tagsJson.Count == 1)
200
+ {
201
+ definition.tags = (string)tagsJson[0];
202
+ }
203
+ else
204
+ {
205
+ var tags = new string[tagsJson.Count];
206
+
207
+ for (var i = 0; i < tagsJson.Count; ++i)
208
+ {
209
+ tags[i] = (string)tagsJson[i];
210
+ }
211
+
212
+ definition.tags = tags;
213
+ }
214
+
215
+ return definition;
216
}
217
218
internal JsonObject Serialize()
@@ -103,6 +246,24 @@ namespace WixToolset.Data
246
247
jsonObject.Add("fields", fieldsJson);
248
249
+ if (this.tags is string || this.tags is string[])
250
+ {
251
+ JsonArray tagsJson;
252
+
253
+ if (this.tags is string tag)
254
+ {
255
+ tagsJson = new JsonArray(1) { tag };
256
+ }
257
+ else
258
+ {
259
+ var array = (string[])this.tags;
260
+ tagsJson = new JsonArray(array.Length);
261
+ tagsJson.AddRange(array);
262
+ }
263
+
264
+ jsonObject.Add("tags", tagsJson);
265
+ }
266
+
267
return jsonObject;
268
}
269
}
src/test/WixToolsetTest.Data/SerializeFixture.cs
+97
-3
@@ -55,7 +55,7 @@ namespace WixToolsetTest.Data
55
new IntermediateFieldDefinition("C", IntermediateFieldType.Bool),
56
};
57
58
- var tupleDef = new IntermediateTupleDefinition("CustomDef", fieldDefs, null);
58
+ var tupleDef = new IntermediateTupleDefinition("CustomDef2", fieldDefs, null);
59
60
var tuple = tupleDef.CreateTuple(sln, new Identifier(AccessModifier.Public, "customT"));
61
tuple.Set(0, "foo");
@@ -119,7 +119,93 @@ namespace WixToolsetTest.Data
119
new IntermediateFieldDefinition("D", IntermediateFieldType.String),
120
};
121
122
- var tupleDef2 = new IntermediateTupleDefinition("CustomDef", 1, fieldDefs2, null);
122
+ var tupleDef2 = new IntermediateTupleDefinition("CustomDef2", 1, fieldDefs2, null);
123
+
124
+ var tuple2 = tupleDef2.CreateTuple(sln, new Identifier(AccessModifier.Public, "customT2"));
125
+ tuple2.Set(0, "bar");
126
+ tuple2.Set(1, 3);
127
+ tuple2.Set(2, false);
128
+ tuple2.Set(3, "baz");
129
+
130
+ var section2 = new IntermediateSection("test2", SectionType.Fragment, 65001);
131
+ section2.Tuples.Add(tuple2);
132
+
133
+ var intermediate2 = new Intermediate("TestIntermediate2", new[] { section2 }, null, null);
134
+
135
+ // Save
136
+ var path1 = Path.GetTempFileName();
137
+ var path2 = Path.GetTempFileName();
138
+ try
139
+ {
140
+ intermediate1.Save(path1);
141
+ intermediate2.Save(path2);
142
+
143
+ var loaded = Intermediate.Load(new[] { path1, path2 });
144
+
145
+ var loaded1 = loaded.First();
146
+ var loaded2 = loaded.Skip(1).Single();
147
+
148
+ var loadedTuple1 = loaded1.Sections.Single().Tuples.Single();
149
+ var loadedTuple2 = loaded2.Sections.Single().Tuples.Single();
150
+
151
+ Assert.Equal("foo", loadedTuple1.AsString(0));
152
+ Assert.Equal(2, loadedTuple1[1].AsNumber());
153
+ Assert.True(loadedTuple1[2].AsBool());
154
+
155
+ Assert.Equal("bar", loadedTuple2.AsString(0));
156
+ Assert.Equal(3, loadedTuple2[1].AsNumber());
157
+ Assert.False(loadedTuple2[2].AsBool());
158
+ Assert.Equal("baz", loadedTuple2.AsString(3));
159
+ }
160
+ finally
161
+ {
162
+ File.Delete(path2);
163
+ File.Delete(path1);
164
+ }
165
+ }
166
+
167
+ [Fact]
168
+ public void CanSaveAndLoadMultipleIntermediateWithCustomDefinitionsAndTags()
169
+ {
170
+ var sln = new SourceLineNumber("test.wxs", 1);
171
+
172
+ // Intermediate #1
173
+ var fieldDefs = new[]
174
+ {
175
+ new IntermediateFieldDefinition("A", IntermediateFieldType.String),
176
+ new IntermediateFieldDefinition("B", IntermediateFieldType.Number),
177
+ new IntermediateFieldDefinition("C", IntermediateFieldType.Bool),
178
+ };
179
+
180
+ var tupleDef = new IntermediateTupleDefinition("CustomDef", fieldDefs, null);
181
+
182
+ tupleDef.AddTag("customDef");
183
+
184
+ var tuple = tupleDef.CreateTuple(sln, new Identifier(AccessModifier.Public, "customT"));
185
+ tuple.Set(0, "foo");
186
+ tuple.Set(1, 2);
187
+ tuple.Set(2, true);
188
+
189
+ tuple.AddTag("tuple1tag");
190
+
191
+ var section = new IntermediateSection("test", SectionType.Product, 65001);
192
+ section.Tuples.Add(tuple);
193
+
194
+ var intermediate1 = new Intermediate("TestIntermediate", new[] { section }, null, null);
195
+
196
+ // Intermediate #2
197
+ var fieldDefs2 = new[]
198
+ {
199
+ new IntermediateFieldDefinition("A", IntermediateFieldType.String),
200
+ new IntermediateFieldDefinition("B", IntermediateFieldType.Number),
201
+ new IntermediateFieldDefinition("C", IntermediateFieldType.Bool),
202
+ new IntermediateFieldDefinition("D", IntermediateFieldType.String),
203
+ };
204
+
205
+ var tupleDef2 = new IntermediateTupleDefinition("CustomDef2", 1, fieldDefs2, null);
206
+
207
+ tupleDef2.AddTag("customDef2");
208
+ tupleDef2.AddTag("customDef2 tag2");
209
210
var tuple2 = tupleDef2.CreateTuple(sln, new Identifier(AccessModifier.Public, "customT2"));
211
tuple2.Set(0, "bar");
@@ -127,6 +213,9 @@ namespace WixToolsetTest.Data
213
tuple2.Set(2, false);
214
tuple2.Set(3, "baz");
215
216
+ tuple2.AddTag("tuple2tag1");
217
+ tuple2.AddTag("tuple2tag2");
218
+
219
var section2 = new IntermediateSection("test2", SectionType.Fragment, 65001);
220
section2.Tuples.Add(tuple2);
221
@@ -148,15 +237,20 @@ namespace WixToolsetTest.Data
237
var loadedTuple1 = loaded1.Sections.Single().Tuples.Single();
238
var loadedTuple2 = loaded2.Sections.Single().Tuples.Single();
239
240
+ Assert.True(loadedTuple1.Definition.HasTag("customDef"));
241
Assert.Equal("foo", loadedTuple1.AsString(0));
242
Assert.Equal(2, loadedTuple1[1].AsNumber());
243
Assert.True(loadedTuple1[2].AsBool());
154
- Assert.Null(loadedTuple1.AsString(3));
244
+ Assert.True(loadedTuple1.HasTag("tuple1tag"));
245
246
+ Assert.True(loadedTuple2.Definition.HasTag("customDef2"));
247
+ Assert.True(loadedTuple2.Definition.HasTag("customDef2 tag2"));
248
Assert.Equal("bar", loadedTuple2.AsString(0));
249
Assert.Equal(3, loadedTuple2[1].AsNumber());
250
Assert.False(loadedTuple2[2].AsBool());
251
Assert.Equal("baz", loadedTuple2.AsString(3));
252
+ Assert.True(loadedTuple2.HasTag("tuple2tag1"));
253
+ Assert.True(loadedTuple2.HasTag("tuple2tag2"));
254
}
255
finally
256
{
src/test/WixToolsetTest.Data/TagFixture.cs
new
+101
@@ -0,0 +1,101 @@
1
+// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
+
3
+namespace WixToolsetTest.Data
4
+{
5
+ using WixToolset.Data;
6
+ using Xunit;
7
+
8
+ public class TagFixture
9
+ {
10
+ [Fact]
11
+ public void CanAddSingleTag()
12
+ {
13
+ var tuple = TupleDefinitions.File.CreateTuple();
14
+ Assert.True(tuple.AddTag("test"));
15
+ Assert.True(tuple.HasTag("test"));
16
+ }
17
+
18
+ [Fact]
19
+ public void CanAddDuplicateTag()
20
+ {
21
+ var tuple = TupleDefinitions.File.CreateTuple();
22
+ Assert.True(tuple.AddTag("test"));
23
+ Assert.False(tuple.AddTag("test"));
24
+ }
25
+
26
+ [Fact]
27
+ public void CanAddRemoveSingleTag()
28
+ {
29
+ var tuple = TupleDefinitions.File.CreateTuple();
30
+ Assert.True(tuple.AddTag("test"));
31
+ Assert.True(tuple.RemoveTag("test"));
32
+ Assert.False(tuple.HasTag("test"));
33
+ }
34
+
35
+ [Fact]
36
+ public void CanAddMultipleTags()
37
+ {
38
+ var tuple = TupleDefinitions.File.CreateTuple();
39
+ Assert.True(tuple.AddTag("test1"));
40
+ Assert.True(tuple.AddTag("test2"));
41
+ Assert.True(tuple.HasTag("test1"));
42
+ Assert.True(tuple.HasTag("test2"));
43
+ }
44
+
45
+ [Fact]
46
+ public void CanAddRemoveMultipleTags()
47
+ {
48
+ var tuple = TupleDefinitions.File.CreateTuple();
49
+ Assert.True(tuple.AddTag("test1"));
50
+ Assert.True(tuple.AddTag("test2"));
51
+ Assert.True(tuple.RemoveTag("test2"));
52
+ Assert.False(tuple.HasTag("test2"));
53
+ Assert.True(tuple.RemoveTag("test1"));
54
+ Assert.False(tuple.HasTag("test1"));
55
+ }
56
+
57
+ [Fact]
58
+ public void CanAddRemoveMissingTags()
59
+ {
60
+ var tuple = TupleDefinitions.File.CreateTuple();
61
+ Assert.True(tuple.AddTag("test1"));
62
+ Assert.True(tuple.AddTag("test2"));
63
+ Assert.False(tuple.RemoveTag("test3"));
64
+ }
65
+
66
+ [Fact]
67
+ public void CanAdd2AndRemoveAllTags()
68
+ {
69
+ var tuple = TupleDefinitions.File.CreateTuple();
70
+ Assert.True(tuple.AddTag("test1"));
71
+ Assert.True(tuple.AddTag("test2"));
72
+ Assert.True(tuple.RemoveTag("test1"));
73
+ Assert.True(tuple.RemoveTag("test2"));
74
+ }
75
+
76
+ [Fact]
77
+ public void CanAdd3AndRemoveAllTags()
78
+ {
79
+ var tuple = TupleDefinitions.File.CreateTuple();
80
+ Assert.True(tuple.AddTag("test1"));
81
+ Assert.True(tuple.AddTag("test2"));
82
+ Assert.True(tuple.AddTag("test3"));
83
+ Assert.True(tuple.RemoveTag("test1"));
84
+ Assert.True(tuple.RemoveTag("test3"));
85
+ Assert.True(tuple.RemoveTag("test2"));
86
+ }
87
+
88
+ [Fact]
89
+ public void CanAdd3AndRemoveMissingTags()
90
+ {
91
+ var tuple = TupleDefinitions.File.CreateTuple();
92
+ Assert.True(tuple.AddTag("test1"));
93
+ Assert.True(tuple.AddTag("test2"));
94
+ Assert.True(tuple.AddTag("test3"));
95
+ Assert.False(tuple.RemoveTag("test4"));
96
+ Assert.True(tuple.RemoveTag("test1"));
97
+ Assert.True(tuple.RemoveTag("test3"));
98
+ Assert.True(tuple.RemoveTag("test2"));
99
+ }
100
+ }
101
+}