Example #1
0
        /// <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);
            }
        }
Example #2
0
        /// <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);
 }