Skip to content

Commit 46021ee

Browse files
authored
Only parse JSON files once (#662)
Then reuse the results for UnitsNet and WRC.
1 parent ca91c76 commit 46021ee

File tree

4 files changed

+242
-295
lines changed

4 files changed

+242
-295
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
// Licensed under MIT No Attribution, see LICENSE file at the root.
2+
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
using System.Linq;
8+
using System.Text;
9+
using CodeGen.Helpers;
10+
using CodeGen.JsonTypes;
11+
using Newtonsoft.Json;
12+
13+
namespace CodeGen.Generators
14+
{
15+
/// <summary>
16+
/// Parses JSON files that define quantities and their units.
17+
/// This will later be used to generate source code and can be reused for different targets such as .NET framework,
18+
/// WindowsRuntimeComponent and even other programming languages.
19+
/// </summary>
20+
internal static class QuantityJsonFilesParser
21+
{
22+
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
23+
{
24+
// Don't override the C# default assigned values if no value is set in JSON
25+
NullValueHandling = NullValueHandling.Ignore
26+
};
27+
28+
/// <summary>
29+
/// Parses JSON files that define quantities and their units.
30+
/// </summary>
31+
/// <param name="rootDir">Repository root directory, where you cloned the repo to such as "c:\dev\UnitsNet".</param>
32+
/// <returns>The parsed quantities and their units.</returns>
33+
public static Quantity[] ParseQuantities(string rootDir)
34+
{
35+
var jsonDir = Path.Combine(rootDir, "Common/UnitDefinitions");
36+
var jsonFiles = Directory.GetFiles(jsonDir, "*.json");
37+
return jsonFiles.Select(ParseQuantityFile).ToArray();
38+
}
39+
40+
private static Quantity ParseQuantityFile(string jsonFile)
41+
{
42+
try
43+
{
44+
var quantity = JsonConvert.DeserializeObject<Quantity>(File.ReadAllText(jsonFile, Encoding.UTF8), JsonSerializerSettings);
45+
AddPrefixUnits(quantity);
46+
FixConversionFunctionsForDecimalValueTypes(quantity);
47+
OrderUnitsByName(quantity);
48+
return quantity;
49+
}
50+
catch (Exception e)
51+
{
52+
throw new Exception($"Error parsing quantity JSON file: {jsonFile}", e);
53+
}
54+
}
55+
56+
private static void OrderUnitsByName(Quantity quantity)
57+
{
58+
quantity.Units = quantity.Units.OrderBy(u => u.SingularName).ToArray();
59+
}
60+
61+
private static void FixConversionFunctionsForDecimalValueTypes(Quantity quantity)
62+
{
63+
foreach (var u in quantity.Units)
64+
// Use decimal for internal calculations if base type is not double, such as for long or int.
65+
if (string.Equals(quantity.BaseType, "decimal", StringComparison.OrdinalIgnoreCase))
66+
{
67+
// Change any double literals like "1024d" to decimal literals "1024m"
68+
u.FromUnitToBaseFunc = u.FromUnitToBaseFunc.Replace("d", "m");
69+
u.FromBaseToUnitFunc = u.FromBaseToUnitFunc.Replace("d", "m");
70+
}
71+
}
72+
73+
private static void AddPrefixUnits(Quantity quantity)
74+
{
75+
var unitsToAdd = new List<Unit>();
76+
foreach (var unit in quantity.Units)
77+
// "Kilo", "Nano" etc.
78+
foreach (var prefix in unit.Prefixes)
79+
try
80+
{
81+
var prefixInfo = PrefixInfo.Entries[prefix];
82+
83+
unitsToAdd.Add(new Unit
84+
{
85+
SingularName = $"{prefix}{unit.SingularName.ToCamelCase()}", // "Kilo" + "NewtonPerMeter" => "KilonewtonPerMeter"
86+
PluralName = $"{prefix}{unit.PluralName.ToCamelCase()}", // "Kilo" + "NewtonsPerMeter" => "KilonewtonsPerMeter"
87+
BaseUnits = null, // Can we determine this somehow?
88+
FromBaseToUnitFunc = $"({unit.FromBaseToUnitFunc}) / {prefixInfo.Factor}",
89+
FromUnitToBaseFunc = $"({unit.FromUnitToBaseFunc}) * {prefixInfo.Factor}",
90+
Localization = GetLocalizationForPrefixUnit(unit.Localization, prefixInfo)
91+
});
92+
}
93+
catch (Exception e)
94+
{
95+
throw new Exception($"Error parsing prefix {prefix} for unit {quantity.Name}.{unit.SingularName}.", e);
96+
}
97+
98+
quantity.Units = quantity.Units.Concat(unitsToAdd).ToArray();
99+
}
100+
101+
/// <summary>
102+
/// Create unit abbreviations for a prefix unit, given a unit and the prefix.
103+
/// The unit abbreviations are either prefixed with the SI prefix or an explicitly configured abbreviation via
104+
/// <see cref="AbbreviationsForPrefixes" />.
105+
/// </summary>
106+
private static Localization[] GetLocalizationForPrefixUnit(IEnumerable<Localization> localizations, PrefixInfo prefixInfo)
107+
{
108+
return localizations.Select(loc =>
109+
{
110+
if (loc.TryGetAbbreviationsForPrefix(prefixInfo.Prefix, out var unitAbbreviationsForPrefix))
111+
return new Localization
112+
{
113+
Culture = loc.Culture,
114+
Abbreviations = unitAbbreviationsForPrefix
115+
};
116+
117+
// No prefix unit abbreviations are specified, so fall back to prepending the default SI prefix to each unit abbreviation:
118+
// kilo ("k") + meter ("m") => kilometer ("km")
119+
var prefix = prefixInfo.Abbreviation;
120+
unitAbbreviationsForPrefix = loc.Abbreviations.Select(unitAbbreviation => $"{prefix}{unitAbbreviation}").ToArray();
121+
122+
return new Localization
123+
{
124+
Culture = loc.Culture,
125+
Abbreviations = unitAbbreviationsForPrefix
126+
};
127+
}).ToArray();
128+
}
129+
}
130+
}
Lines changed: 38 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,65 @@
11
// Licensed under MIT No Attribution, see LICENSE file at the root.
22
// Copyright 2013 Andreas Gullberg Larsen ([email protected]). Maintained at https://github.com/angularsen/UnitsNet.
33

4-
using System;
5-
using System.Collections.Generic;
64
using System.IO;
75
using System.Linq;
86
using System.Text;
97
using CodeGen.Generators.UnitsNetGen;
10-
using CodeGen.Helpers;
118
using CodeGen.JsonTypes;
12-
using Newtonsoft.Json;
139
using Serilog;
1410

1511
namespace CodeGen.Generators
1612
{
13+
/// <summary>
14+
/// Code generator for UnitsNet and UnitsNet.Tests projects.
15+
/// </summary>
1716
internal static class UnitsNetGenerator
1817
{
1918
private const int AlignPad = 35;
2019

21-
private static readonly JsonSerializerSettings JsonSerializerSettings = new JsonSerializerSettings
22-
{
23-
// Don't override the C# default assigned values if no value is set in JSON
24-
NullValueHandling = NullValueHandling.Ignore
25-
};
26-
27-
public static void Generate(DirectoryInfo repositoryRoot)
20+
/// <summary>
21+
/// Generate source code for UnitsNet project for the given parsed quantities.
22+
/// Outputs files relative to the given root dir to these locations:
23+
/// <list type="bullet">
24+
/// <item>
25+
/// <description>UnitsNet/GeneratedCode (quantity and unit types, Quantity, UnitAbbreviationCache)</description>
26+
/// </item>
27+
/// <item>
28+
/// <description>UnitsNet.Tests/GeneratedCode (tests)</description>
29+
/// </item>
30+
/// <item>
31+
/// <description>UnitsNet.Tests/CustomCode (test stubs, one for each quantity if not already created)</description>
32+
/// </item>
33+
/// </list>
34+
/// </summary>
35+
/// <param name="rootDir">Path to repository root directory.</param>
36+
/// <param name="quantities">The parsed quantities.</param>
37+
public static void Generate(string rootDir, Quantity[] quantities)
2838
{
29-
if (repositoryRoot == null) throw new ArgumentNullException(nameof(repositoryRoot));
30-
var root = repositoryRoot.FullName;
31-
32-
var templatesDir = Path.Combine(root, "Common/UnitDefinitions");
33-
var jsonFiles = Directory.GetFiles(templatesDir, "*.json");
34-
var quantities = jsonFiles.Select(ParseQuantityFile).ToArray();
39+
var outputDir = $"{rootDir}/UnitsNet/GeneratedCode";
40+
var testProjectDir = $"{rootDir}/UnitsNet.Tests";
3541

36-
foreach (Quantity quantity in quantities)
42+
foreach (var quantity in quantities)
3743
{
3844
var sb = new StringBuilder($"{quantity.Name}:".PadRight(AlignPad));
39-
GenerateQuantity(sb, quantity, $"{root}/UnitsNet/GeneratedCode/Quantities/{quantity.Name}.NetFramework.g.cs"); // TODO Remove NetFramework suffix
40-
GenerateUnitType(sb, quantity, $"{root}/UnitsNet/GeneratedCode/Units/{quantity.Name}Unit.g.cs");
41-
GenerateUnitTestBaseClass(sb, quantity, $"{root}/UnitsNet.Tests/GeneratedCode/{quantity.Name}TestsBase.g.cs");
42-
GenerateUnitTestClassIfNotExists(sb, quantity, $"{root}/UnitsNet.Tests/CustomCode/{quantity.Name}Tests.cs");
45+
GenerateQuantity(sb, quantity, $"{outputDir}/Quantities/{quantity.Name}.NetFramework.g.cs"); // TODO Remove NetFramework suffix
46+
GenerateUnitType(sb, quantity, $"{outputDir}/Units/{quantity.Name}Unit.g.cs");
47+
GenerateUnitTestBaseClass(sb, quantity, $"{testProjectDir}/GeneratedCode/{quantity.Name}TestsBase.g.cs");
48+
GenerateUnitTestClassIfNotExists(sb, quantity, $"{testProjectDir}/CustomCode/{quantity.Name}Tests.cs");
4349
Log.Information(sb.ToString());
4450
}
4551

4652
Log.Information("");
47-
GenerateUnitAbbreviationsCache(quantities, $"{root}/UnitsNet/GeneratedCode/UnitAbbreviationsCache.g.cs");
48-
GenerateQuantityType(quantities, $"{root}/UnitsNet/GeneratedCode/QuantityType.g.cs");
49-
GenerateStaticQuantity(quantities, $"{root}/UnitsNet/GeneratedCode/Quantity.g.cs");
53+
GenerateUnitAbbreviationsCache(quantities, $"{outputDir}/UnitAbbreviationsCache.g.cs");
54+
GenerateQuantityType(quantities, $"{outputDir}/QuantityType.g.cs");
55+
GenerateStaticQuantity(quantities, $"{outputDir}/Quantity.g.cs");
5056

5157
var unitCount = quantities.SelectMany(q => q.Units).Count();
5258
Log.Information("");
5359
Log.Information($"Total of {unitCount} units and {quantities.Length} quantities.");
5460
Log.Information("");
5561
}
5662

57-
private static Quantity ParseQuantityFile(string jsonFile)
58-
{
59-
try
60-
{
61-
var quantity = JsonConvert.DeserializeObject<Quantity>(File.ReadAllText(jsonFile, Encoding.UTF8), JsonSerializerSettings);
62-
AddPrefixUnits(quantity);
63-
FixConversionFunctionsForDecimalValueTypes(quantity);
64-
OrderUnitsByName(quantity);
65-
return quantity;
66-
}
67-
catch (Exception e)
68-
{
69-
throw new Exception($"Error parsing quantity JSON file: {jsonFile}", e);
70-
}
71-
}
72-
7363
private static void GenerateUnitTestClassIfNotExists(StringBuilder sb, Quantity quantity, string filePath)
7464
{
7565
if (File.Exists(filePath))
@@ -78,133 +68,51 @@ private static void GenerateUnitTestClassIfNotExists(StringBuilder sb, Quantity
7868
return;
7969
}
8070

81-
string content = new UnitTestStubGenerator(quantity).Generate();
71+
var content = new UnitTestStubGenerator(quantity).Generate();
8272
File.WriteAllText(filePath, content, Encoding.UTF8);
8373
sb.Append("test stub(OK) ");
8474
}
8575

8676
private static void GenerateQuantity(StringBuilder sb, Quantity quantity, string filePath)
8777
{
88-
string content = new QuantityGenerator(quantity).Generate();
78+
var content = new QuantityGenerator(quantity).Generate();
8979
File.WriteAllText(filePath, content, Encoding.UTF8);
9080
sb.Append("quantity(OK) ");
9181
}
9282

9383
private static void GenerateUnitType(StringBuilder sb, Quantity quantity, string filePath)
9484
{
95-
string content = new UnitTypeGenerator(quantity).Generate();
85+
var content = new UnitTypeGenerator(quantity).Generate();
9686
File.WriteAllText(filePath, content, Encoding.UTF8);
9787
sb.Append("unit(OK) ");
9888
}
9989

10090
private static void GenerateUnitTestBaseClass(StringBuilder sb, Quantity quantity, string filePath)
10191
{
102-
string content = new UnitTestBaseClassGenerator(quantity).Generate();
92+
var content = new UnitTestBaseClassGenerator(quantity).Generate();
10393
File.WriteAllText(filePath, content, Encoding.UTF8);
10494
sb.Append("test base(OK) ");
10595
}
10696

10797
private static void GenerateUnitAbbreviationsCache(Quantity[] quantities, string filePath)
10898
{
109-
string content = new UnitAbbreviationsCacheGenerator(quantities).Generate();
99+
var content = new UnitAbbreviationsCacheGenerator(quantities).Generate();
110100
File.WriteAllText(filePath, content, Encoding.UTF8);
111101
Log.Information("UnitAbbreviationsCache.g.cs: ".PadRight(AlignPad) + "(OK)");
112102
}
113103

114104
private static void GenerateQuantityType(Quantity[] quantities, string filePath)
115105
{
116-
string content = new QuantityTypeGenerator(quantities).Generate();
106+
var content = new QuantityTypeGenerator(quantities).Generate();
117107
File.WriteAllText(filePath, content, Encoding.UTF8);
118108
Log.Information("QuantityType.g.cs: ".PadRight(AlignPad) + "(OK)");
119109
}
120110

121111
private static void GenerateStaticQuantity(Quantity[] quantities, string filePath)
122112
{
123-
string content = new StaticQuantityGenerator(quantities).Generate();
113+
var content = new StaticQuantityGenerator(quantities).Generate();
124114
File.WriteAllText(filePath, content, Encoding.UTF8);
125115
Log.Information("Quantity.g.cs: ".PadRight(AlignPad) + "(OK)");
126116
}
127-
128-
private static void OrderUnitsByName(Quantity quantity)
129-
{
130-
quantity.Units = quantity.Units.OrderBy(u => u.SingularName).ToArray();
131-
}
132-
133-
private static void FixConversionFunctionsForDecimalValueTypes(Quantity quantity)
134-
{
135-
foreach (Unit u in quantity.Units)
136-
{
137-
// Use decimal for internal calculations if base type is not double, such as for long or int.
138-
if (string.Equals(quantity.BaseType, "decimal", StringComparison.OrdinalIgnoreCase))
139-
{
140-
// Change any double literals like "1024d" to decimal literals "1024m"
141-
u.FromUnitToBaseFunc = u.FromUnitToBaseFunc.Replace("d", "m");
142-
u.FromBaseToUnitFunc = u.FromBaseToUnitFunc.Replace("d", "m");
143-
}
144-
}
145-
}
146-
147-
private static void AddPrefixUnits(Quantity quantity)
148-
{
149-
var unitsToAdd = new List<Unit>();
150-
foreach (Unit unit in quantity.Units)
151-
{
152-
// "Kilo", "Nano" etc.
153-
foreach (Prefix prefix in unit.Prefixes)
154-
{
155-
try
156-
{
157-
PrefixInfo prefixInfo = PrefixInfo.Entries[prefix];
158-
159-
unitsToAdd.Add(new Unit
160-
{
161-
SingularName = $"{prefix}{unit.SingularName.ToCamelCase()}", // "Kilo" + "NewtonPerMeter" => "KilonewtonPerMeter"
162-
PluralName = $"{prefix}{unit.PluralName.ToCamelCase()}", // "Kilo" + "NewtonsPerMeter" => "KilonewtonsPerMeter"
163-
BaseUnits = null, // Can we determine this somehow?
164-
FromBaseToUnitFunc = $"({unit.FromBaseToUnitFunc}) / {prefixInfo.Factor}",
165-
FromUnitToBaseFunc = $"({unit.FromUnitToBaseFunc}) * {prefixInfo.Factor}",
166-
Localization = GetLocalizationForPrefixUnit(unit.Localization, prefixInfo),
167-
});
168-
}
169-
catch (Exception e)
170-
{
171-
throw new Exception($"Error parsing prefix {prefix} for unit {quantity.Name}.{unit.SingularName}.", e);
172-
}
173-
}
174-
}
175-
176-
quantity.Units = quantity.Units.Concat(unitsToAdd).ToArray();
177-
}
178-
179-
/// <summary>
180-
/// Create unit abbreviations for a prefix unit, given a unit and the prefix.
181-
/// The unit abbreviations are either prefixed with the SI prefix or an explicitly configured abbreviation via <see cref="AbbreviationsForPrefixes"/>.
182-
/// </summary>
183-
private static Localization[] GetLocalizationForPrefixUnit(IEnumerable<Localization> localizations, PrefixInfo prefixInfo)
184-
{
185-
return localizations.Select(loc =>
186-
{
187-
if (loc.TryGetAbbreviationsForPrefix(prefixInfo.Prefix, out string[] unitAbbreviationsForPrefix))
188-
{
189-
// Use explicitly defined prefix unit abbreviations
190-
return new Localization
191-
{
192-
Culture = loc.Culture,
193-
Abbreviations = unitAbbreviationsForPrefix,
194-
};
195-
}
196-
197-
// No prefix unit abbreviations are specified, so fall back to prepending the default SI prefix to each unit abbreviation:
198-
// kilo ("k") + meter ("m") => kilometer ("km")
199-
string prefix = prefixInfo.Abbreviation;
200-
unitAbbreviationsForPrefix = loc.Abbreviations.Select(unitAbbreviation => $"{prefix}{unitAbbreviation}").ToArray();
201-
202-
return new Localization
203-
{
204-
Culture = loc.Culture,
205-
Abbreviations = unitAbbreviationsForPrefix,
206-
};
207-
}).ToArray();
208-
}
209117
}
210118
}

0 commit comments

Comments
 (0)