/// <summary> /// Code generator for Units.NET. /// Reads unit definitions from JSON files and outputs C# files in GeneratedCode folders: /// <list type="number"> /// <item> /// <description>Quantity types (Length, Mass, ...)</description> /// </item> /// <item> /// <description>UnitsNet.QuantityType enum type (QuantityType.Length, QuantityType.Mass, ...)</description> /// </item> /// <item> /// <description>UnitsNet.Quantity type</description> /// </item> /// <item> /// <description>UnitsNet.UnitAbbreviationsCache</description> /// </item> /// <item> /// <description>Test stubs for testing conversion functions of all units, to be fleshed out by a human later</description> /// </item> /// <item> /// <description>Unit enum types (LengthUnit, MassUnit, ...)</description> /// </item> /// </list> /// </summary> /// <remarks> /// System.CommandLine.Dragonfruit based Main method, where CLI arguments are parsed and passed directly to this /// method. /// See https://github.com/dotnet/command-line-api/ /// </remarks> /// <param name="verbose">Verbose output? Defaults to false.</param> /// <param name="repositoryRoot">The repository root directory, defaults to searching parent directories for UnitsNet.sln.</param> /// <param name="skipWrc">Skip generate UnitsNet.WindowsRuntimeComponent? Defaults to false.</param> /// <param name="skipNanoFramework">Skip generate nanoFramework Units? Defaults to false</param> /// <param name="updateNanoFrameworkDependencies">Update nanoFramework nuget dependencies? Defaults to false.</param> public static int Main(bool verbose = false, DirectoryInfo?repositoryRoot = null, bool skipWrc = false, bool skipNanoFramework = false, bool updateNanoFrameworkDependencies = false) { Log.Logger = new LoggerConfiguration() .WriteTo .Console(verbose ? LogEventLevel.Verbose : LogEventLevel.Information) .CreateLogger(); // Enable emojis and other UTF8 symbols. Console.OutputEncoding = System.Text.Encoding.UTF8; try { repositoryRoot ??= FindRepositoryRoot(); var rootDir = repositoryRoot.FullName; Log.Information("Units.NET code generator {Version}", Assembly.GetExecutingAssembly().GetName().Version); if (verbose) { Log.Debug("Verbose output enabled"); } var sw = Stopwatch.StartNew(); var quantities = QuantityJsonFilesParser.ParseQuantities(repositoryRoot.FullName); QuantityNameToUnitEnumValues quantityNameToUnitEnumValues = UnitEnumValueAllocator.AllocateNewUnitEnumValues($"{rootDir}/Common/UnitEnumValues.g.json", quantities); UnitsNetGenerator.Generate(rootDir, quantities, quantityNameToUnitEnumValues); if (!skipWrc) { UnitsNetWrcGenerator.Generate(rootDir, quantities); } if (updateNanoFrameworkDependencies) { if (!NanoFrameworkGenerator.UpdateNanoFrameworkDependencies( rootDir, quantities)) { return(1); } } if (!skipNanoFramework) { Log.Information("Generate nanoFramework projects\n---"); NanoFrameworkGenerator.Generate(rootDir, quantities, quantityNameToUnitEnumValues); } Log.Information("Completed in {ElapsedMs} ms!", sw.ElapsedMilliseconds); return(0); } catch (Exception e) { Log.Error(e, "Unexpected error"); return(1); } }
/// <summary> /// Generate source code for UnitsNet project for the given parsed quantities. /// Outputs files relative to the given root dir to these locations: /// <list type="bullet"> /// <item> /// <description>UnitsNet/GeneratedCode (quantity and unit types, Quantity, UnitAbbreviationCache)</description> /// </item> /// <item> /// <description>UnitsNet.Tests/GeneratedCode (tests)</description> /// </item> /// <item> /// <description>UnitsNet.Tests/CustomCode (test stubs, one for each quantity if not already created)</description> /// </item> /// </list> /// </summary> /// <param name="rootDir">Path to repository root directory.</param> /// <param name="quantities">The parsed quantities.</param> /// <param name="quantityNameToUnitEnumValues">Allocated unit enum values for generating unit enum types.</param> public static void Generate(string rootDir, Quantity[] quantities, QuantityNameToUnitEnumValues quantityNameToUnitEnumValues) { var outputDir = $"{rootDir}/UnitsNet/GeneratedCode"; var extensionsOutputDir = $"{rootDir}/UnitsNet.NumberExtensions/GeneratedCode"; var extensionsTestOutputDir = $"{rootDir}/UnitsNet.NumberExtensions.Tests/GeneratedCode"; var testProjectDir = $"{rootDir}/UnitsNet.Tests"; // Ensure output directories exist Directory.CreateDirectory($"{outputDir}/Quantities"); Directory.CreateDirectory($"{outputDir}/Units"); Directory.CreateDirectory($"{extensionsOutputDir}"); Directory.CreateDirectory($"{extensionsTestOutputDir}"); Directory.CreateDirectory($"{testProjectDir}/GeneratedCode"); Directory.CreateDirectory($"{testProjectDir}/GeneratedCode/TestsBase"); Directory.CreateDirectory($"{testProjectDir}/GeneratedCode/QuantityTests"); foreach (var quantity in quantities) { UnitEnumNameToValue unitEnumValues = quantityNameToUnitEnumValues[quantity.Name]; GenerateQuantity(quantity, $"{outputDir}/Quantities/{quantity.Name}.g.cs"); GenerateUnitType(quantity, $"{outputDir}/Units/{quantity.Name}Unit.g.cs", unitEnumValues); GenerateNumberToExtensions(quantity, $"{extensionsOutputDir}/NumberTo{quantity.Name}Extensions.g.cs"); GenerateNumberToExtensionsTestClass(quantity, $"{extensionsTestOutputDir}/NumberTo{quantity.Name}ExtensionsTest.g.cs"); // Example: CustomCode/Quantities/LengthTests inherits GeneratedCode/TestsBase/LengthTestsBase // This way when new units are added to the quantity JSON definition, we auto-generate the new // conversion function tests that needs to be manually implemented by the developer to fix the compile error // so it cannot be forgotten. GenerateQuantityTestBaseClass(quantity, $"{testProjectDir}/GeneratedCode/TestsBase/{quantity.Name}TestsBase.g.cs"); GenerateQuantityTestClassIfNotExists(quantity, $"{testProjectDir}/CustomCode/{quantity.Name}Tests.cs"); Log.Information("✅ {Quantity}", quantity.Name); } Log.Information(""); GenerateIQuantityTests(quantities, $"{testProjectDir}/GeneratedCode/IQuantityTests.g.cs"); GenerateQuantityType(quantities, $"{outputDir}/QuantityType.g.cs"); GenerateStaticQuantity(quantities, $"{outputDir}/Quantity.g.cs"); var unitCount = quantities.SelectMany(q => q.Units).Count(); Log.Information(""); Log.Information("Total of {UnitCount} units and {QuantityCount} quantities", unitCount, quantities.Length); Log.Information(""); }
/// <summary> /// Creates an instance for the given JSON file path. /// </summary> /// <param name="jsonFile">Path to the JSON file that describes the currently allocated enum values.</param> private UnitEnumValueAllocator(string jsonFile) { _jsonFile = jsonFile; _quantityNameToUnitEnumValues = ReadFromFile(jsonFile); }