Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 33 additions & 30 deletions .github/scripts/check-package.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@
version = sys.argv[1]
package_directory = Path("artifacts/packages")
core_id = "DimensionalQuantities"
metric_id = "DimensionalQuantities.Extensions.Metric"
expected_packages = {
f"{core_id}.{version}.nupkg",
f"{metric_id}.{version}.nupkg",
}
expected_symbols = {
f"{core_id}.{version}.snupkg",
f"{metric_id}.{version}.snupkg",
}
actual_packages = {package.name for package in package_directory.glob("*.nupkg")}
actual_symbols = {package.name for package in package_directory.glob("*.snupkg")}
extension_ids = sorted(
Path(project.attrib["Path"]).stem
for project in ET.parse("src/DimensionalQuantities.slnx").iter("Project")
if Path(project.attrib["Path"]).stem.startswith("DimensionalQuantities.Extensions.")
and not Path(project.attrib["Path"]).stem.endswith(".Tests")
)
package_ids = [core_id, *extension_ids]
expected_packages = {f"{package_id}.{version}.nupkg" for package_id in package_ids}
expected_symbols = {f"{package_id}.{version}.snupkg" for package_id in package_ids}
actual_packages = {package.name for package in package_directory.glob(f"*.{version}.nupkg")}
actual_symbols = {package.name for package in package_directory.glob(f"*.{version}.snupkg")}
assert actual_packages == expected_packages, (actual_packages, expected_packages)
assert actual_symbols == expected_symbols, (actual_symbols, expected_symbols)

Expand All @@ -35,13 +35,12 @@ def read_package(package_id, extension):
return names, metadata


core_files, core_metadata = read_package(core_id, "nupkg")
metric_files, metric_metadata = read_package(metric_id, "nupkg")
core_symbols, _ = read_package(core_id, "snupkg")
metric_symbols, _ = read_package(metric_id, "snupkg")
packages = {package_id: read_package(package_id, "nupkg") for package_id in package_ids}
symbols = {package_id: read_package(package_id, "snupkg")[0] for package_id in package_ids}
core_files, core_metadata = packages[core_id]
core_symbols = symbols[core_id]

core_runtime = "lib/netstandard2.0/DimensionalQuantities.dll"
metric_runtime = "lib/netstandard2.0/DimensionalQuantities.Extensions.Metric.dll"
analyzer_directory = "analyzers/dotnet/cs/"
tooling = {
f"{analyzer_directory}DimensionalQuantities.dll",
Expand All @@ -50,15 +49,18 @@ def read_package(package_id, extension):
}
assert core_runtime in core_files
assert tooling <= core_files, tooling - core_files
assert metric_runtime in metric_files
assert core_runtime not in metric_files
assert not any(name.startswith(analyzer_directory) for name in metric_files)
for extension_id in extension_ids:
extension_files, _ = packages[extension_id]
assert f"lib/netstandard2.0/{extension_id}.dll" in extension_files
assert core_runtime not in extension_files
assert not any(name.startswith(analyzer_directory) for name in extension_files)
assert f"lib/netstandard2.0/{extension_id}.pdb" in symbols[extension_id]

for names in (core_files, metric_files):
for names, _ in packages.values():
assert not any("Microsoft.CodeAnalysis" in name for name in names)
assert not any(".Tests" in name for name in names)

for metadata in (core_metadata, metric_metadata):
for _, metadata in packages.values():
repository = metadata.find("{*}repository")
assert repository is not None
assert repository.attrib["type"] == "git"
Expand All @@ -71,17 +73,18 @@ def read_package(package_id, extension):
assert not any("Roslyn" in item.attrib["id"] or "CodeAnalysis" in item.attrib["id"] for item in dependencies)

core_dependencies = core_metadata.findall(".//{*}dependency")
assert not any(item.attrib["id"] == metric_id for item in core_dependencies)
metric_dependencies = metric_metadata.findall(".//{*}dependency")
core_dependency = [item for item in metric_dependencies if item.attrib["id"] == core_id]
assert len(core_dependency) == 1, core_dependency
assert core_dependency[0].attrib["version"] == f"[{version}]", core_dependency[0].attrib
assert core_dependency[0].attrib.get("include", "All").lower() == "all", core_dependency[0].attrib
assert "analyzers" not in core_dependency[0].attrib.get("exclude", "").lower(), core_dependency[0].attrib
assert not any(item.attrib["id"] in extension_ids for item in core_dependencies)
for extension_id in extension_ids:
_, metadata = packages[extension_id]
dependencies = metadata.findall(".//{*}dependency")
core_dependency = [item for item in dependencies if item.attrib["id"] == core_id]
assert len(core_dependency) == 1, (extension_id, core_dependency)
assert core_dependency[0].attrib["version"] == f"[{version}]", core_dependency[0].attrib
assert core_dependency[0].attrib.get("include", "All").lower() == "all", core_dependency[0].attrib
assert "analyzers" not in core_dependency[0].attrib.get("exclude", "").lower(), core_dependency[0].attrib

assert "lib/netstandard2.0/DimensionalQuantities.pdb" in core_symbols
assert "lib/netstandard2.0/DimensionalQuantities.Extensions.Metric.pdb" in metric_symbols
for assembly in tooling:
assert assembly.removesuffix(".dll") + ".pdb" in core_symbols

print(f"Verified {core_id} and {metric_id} packages and symbols at {version}")
print(f"Verified {', '.join(package_ids)} packages and symbols at {version}")
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ using DimensionalQuantities.Extensions.Metric;

Metric depends on the matching core version, so referencing Metric also supplies core and its compiler tooling. Quantity names and dimensional arithmetic are available with core alone.

Add `DimensionalQuantities.Extensions.Imperial` for British and US customary conversions. It includes feet, miles, stones, pounds-force, US and Imperial liquid measures, Fahrenheit, mechanical horsepower and the International Table British thermal unit. Import `DimensionalQuantities.Extensions.Imperial` to use its helpers:

```csharp
Length depth = Length.FromFeet(6);
Volume fuel = Volume.FromImperialGallons(2);
Energy heat = Energy.FromBritishThermalUnitsIT(1);

double metres = depth.AsSiBaseUnit(); // 1.8288
```

`BritishThermalUnitIT` is the International Table definition (exactly 1055.05585262 J); other BTU definitions differ. US and Imperial liquid measures are named separately. Imperial helpers do not generate SI-prefixed variants.

```csharp
Voltage voltage = Voltage.FromVolts(12);
Current current = Current.FromAmps(3);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<OutputType>Exe</OutputType>
<UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3" Version="4.0.2" />
<PackageReference Include="Microsoft.Testing.Extensions.TrxReport" Version="2.5.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../DimensionalQuantities.Extensions.Imperial/DimensionalQuantities.Extensions.Imperial.csproj" />
<ProjectReference Include="../DimensionalQuantities.Roslyn/DimensionalQuantities.Roslyn.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<Analyzer Include="../DimensionalQuantities/bin/$(Configuration)/netstandard2.0/DimensionalQuantities.dll" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.Reflection;
using DimensionalQuantities.Extensions.Imperial;
using Xunit;

namespace DimensionalQuantities.Extensions.Imperial.Tests;

public sealed class ImperialConversionsTests
{
[Fact]
public void LengthAndAreaUseInternationalFoot()
{
Assert.Multiple(
() => Assert.Equal(0.0254, Length.FromInches(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.3048, Length.FromFeet(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.9144, Length.FromYards(1).AsSiBaseUnit(), 12),
() => Assert.Equal(1609.344, Length.FromMiles(1).AsSiBaseUnit(), 9),
() => Assert.Equal(20.1168, Length.FromChains(1).AsSiBaseUnit(), 10),
() => Assert.Equal(201.168, Length.FromFurlongs(1).AsSiBaseUnit(), 10),
() => Assert.Equal(0.09290304, Area.FromSquareFeet(1).AsSiBaseUnit(), 12),
() => Assert.Equal(4046.8564224, Area.FromAcres(1).AsSiBaseUnit(), 8),
() => Assert.Equal(5280, Length.FromMiles(1).AsFeet(), 9));
}

[Fact]
public void VolumeKeepsUsAndImperialLiquidMeasuresDistinct()
{
Assert.Multiple(
() => Assert.Equal(0.000016387064, Volume.FromCubicInches(1).AsSiBaseUnit(), 15),
() => Assert.Equal(0.028316846592, Volume.FromCubicFeet(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.764554857984, Volume.FromCubicYards(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.003785411784, Volume.FromUSGallons(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.000946352946, Volume.FromUSQuarts(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.000473176473, Volume.FromUSPints(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.0000295735295625, Volume.FromUSFluidOunces(1).AsSiBaseUnit(), 15),
() => Assert.Equal(0.00454609, Volume.FromImperialGallons(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.0011365225, Volume.FromImperialQuarts(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.00056826125, Volume.FromImperialPints(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.0000284130625, Volume.FromImperialFluidOunces(1).AsSiBaseUnit(), 15),
() => Assert.Equal(128, Volume.FromUSGallons(1).AsUSFluidOunces(), 10),
() => Assert.Equal(160, Volume.FromImperialGallons(1).AsImperialFluidOunces(), 10));
}

[Fact]
public void MassSpeedForceAndPressureUseTheirOwnDimensions()
{
Assert.Multiple(
() => Assert.Equal(0.028349523125, Mass.FromOunces(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.45359237, Mass.FromPounds(1).AsSiBaseUnit(), 12),
() => Assert.Equal(6.35029318, Mass.FromStones(1).AsSiBaseUnit(), 10),
() => Assert.Equal(907.18474, Mass.FromUSShortTons(1).AsSiBaseUnit(), 8),
() => Assert.Equal(1016.0469088, Mass.FromImperialLongTons(1).AsSiBaseUnit(), 8),
() => Assert.Equal(0.44704, Speed.FromMilesPerHour(1).AsSiBaseUnit(), 12),
() => Assert.Equal(0.3048, Speed.FromFeetPerSecond(1).AsSiBaseUnit(), 12),
() => Assert.Equal(4.4482216152605, Force.FromPoundForces(1).AsSiBaseUnit(), 12),
() => Assert.Equal(4.4482216152605 / (0.0254 * 0.0254), Pressure.FromPsi(1).AsSiBaseUnit(), 9),
() => Assert.Equal(1, Stress.FromPsi(1).AsPsi(), 12),
() => Assert.Equal(1, YoungsModulus.FromPsi(1).AsPsi(), 12));
}

[Fact]
public void EnergyAndPowerUseExplicitBritishThermalUnitDefinition()
{
Assert.Multiple(
() => Assert.Equal(1.3558179483314004, Energy.FromFootPoundForces(1).AsSiBaseUnit(), 12),
() => Assert.Equal(1055.05585262, Energy.FromBritishThermalUnitsIT(1).AsSiBaseUnit(), 9),
() => Assert.Equal(745.69987158227022, Power.FromMechanicalHorsepower(1).AsSiBaseUnit(), 9),
() => Assert.Equal(1055.05585262 / 3600, Power.FromBritishThermalUnitsITPerHour(1).AsSiBaseUnit(), 12),
() => Assert.Equal(1, Energy.FromSiBaseUnit(1055.05585262).AsBritishThermalUnitsIT(), 12),
() => Assert.Equal(1, Power.FromSiBaseUnit(1055.05585262 / 3600).AsBritishThermalUnitsITPerHour(), 12));
}

[Fact]
public void FahrenheitUsesAffineConversion()
{
Assert.Multiple(
() => Assert.Equal(273.15, Temperature.FromFahrenheit(32).AsKelvin(), 10),
() => Assert.Equal(212, Temperature.FromKelvin(373.15).AsFahrenheit(), 10));
}

[Fact]
public void ProviderDoesNotGenerateSiPrefixHelpers()
{
string[] names = Assembly.Load("DimensionalQuantities.Extensions.Imperial").GetExportedTypes()
.Where(type => type.Namespace == "DimensionalQuantities.Extensions.Imperial")
.SelectMany(type => type.GetMethods()).Select(method => method.Name).ToArray();
Assert.Multiple(
() => Assert.DoesNotContain("FromKiloFeet", names),
() => Assert.DoesNotContain("AsMilliPounds", names),
() => Assert.DoesNotContain("FromPrefixedUnits", names),
() => Assert.DoesNotContain("AsPrefixedUnits", names));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Authors>GoSim</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageProjectUrl>https://github.com/GoSimulate/DimensionalQuantities</PackageProjectUrl>
<Description>Named Imperial and US customary conversions for dimensional quantities.</Description>
<RepositoryType>git</RepositoryType>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)$(Configuration)/$(TargetFramework)/generated</CompilerGeneratedFilesOutputPath>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<Deterministic>true</Deterministic>
<DebugType>portable</DebugType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../DimensionalQuantities/DimensionalQuantities.csproj" PrivateAssets="none" />
<ProjectReference Include="../DimensionalQuantities.Roslyn/DimensionalQuantities.Roslyn.csproj"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" PrivateAssets="all" />
<Analyzer Include="../DimensionalQuantities/bin/$(Configuration)/netstandard2.0/DimensionalQuantities.dll" />
<None Include="../../LICENSE" Pack="true" PackagePath="" />
<None Include="../../README.md" Pack="true" PackagePath="" />
</ItemGroup>
<Target Name="PinCorePackageDependency" AfterTargets="_GetProjectReferenceVersions" BeforeTargets="GenerateNuspec">
<ItemGroup>
<_ProjectReferencesWithVersions Update="@(_ProjectReferencesWithVersions)"
Condition="'%(_ProjectReferencesWithVersions.Filename)' == 'DimensionalQuantities'">
<ProjectVersion>[$(PackageVersion)]</ProjectVersion>
</_ProjectReferencesWithVersions>
</ItemGroup>
</Target>
</Project>
59 changes: 59 additions & 0 deletions src/DimensionalQuantities.Extensions.Imperial/ImperialSymbols.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using DimensionalQuantities.Units.Declarations;

namespace DimensionalQuantities.Extensions.Imperial;

internal static class ImperialSymbols
{
[UnitSymbol("Inch", "Inches", "in", Scale = 0.0254)]
internal sealed class Inch { }
[UnitSymbol("Foot", "Feet", "ft", Scale = 0.3048)]
internal sealed class Foot { }
[UnitSymbol("Yard", "Yards", "yd", Scale = 0.9144)]
internal sealed class Yard { }
[UnitSymbol("Mile", "Miles", "mi", Scale = 1609.344)]
internal sealed class Mile { }
[UnitSymbol("Chain", "Chains", "ch", Scale = 20.1168)]
internal sealed class Chain { }
[UnitSymbol("Furlong", "Furlongs", "fur", Scale = 201.168)]
internal sealed class Furlong { }
[UnitSymbol("Acre", "Acres", "ac", Scale = 4046.8564224)]
internal sealed class Acre { }
[UnitSymbol("USGallon", "USGallons", "gal (US)", Scale = 0.003785411784)]
internal sealed class USGallon { }
[UnitSymbol("USQuart", "USQuarts", "qt (US)", Scale = 0.000946352946)]
internal sealed class USQuart { }
[UnitSymbol("USPint", "USPints", "pt (US)", Scale = 0.000473176473)]
internal sealed class USPint { }
[UnitSymbol("USFluidOunce", "USFluidOunces", "fl oz (US)", Scale = 0.0000295735295625)]
internal sealed class USFluidOunce { }
[UnitSymbol("ImperialGallon", "ImperialGallons", "gal (imp)", Scale = 0.00454609)]
internal sealed class ImperialGallon { }
[UnitSymbol("ImperialQuart", "ImperialQuarts", "qt (imp)", Scale = 0.0011365225)]
internal sealed class ImperialQuart { }
[UnitSymbol("ImperialPint", "ImperialPints", "pt (imp)", Scale = 0.00056826125)]
internal sealed class ImperialPint { }
[UnitSymbol("ImperialFluidOunce", "ImperialFluidOunces", "fl oz (imp)", Scale = 0.0000284130625)]
internal sealed class ImperialFluidOunce { }
[UnitSymbol("Ounce", "Ounces", "oz", Scale = 0.028349523125)]
internal sealed class Ounce { }
[UnitSymbol("Pound", "Pounds", "lb", Scale = 0.45359237)]
internal sealed class Pound { }
[UnitSymbol("Stone", "Stones", "st", Scale = 6.35029318)]
internal sealed class Stone { }
[UnitSymbol("USShortTon", "USShortTons", "ton (US)", Scale = 907.18474)]
internal sealed class USShortTon { }
[UnitSymbol("ImperialLongTon", "ImperialLongTons", "ton (imp)", Scale = 1016.0469088)]
internal sealed class ImperialLongTon { }
[UnitSymbol("Hour", "Hours", "h", Scale = 3600)]
internal sealed class Hour { }
[UnitSymbol("PoundForce", "PoundForces", "lbf", Scale = 4.4482216152605)]
internal sealed class PoundForce { }
[UnitSymbol("Psi", "Psi", "psi", Scale = 4.4482216152605 / (0.0254 * 0.0254))]
internal sealed class Psi { }
[UnitSymbol("BritishThermalUnitIT", "BritishThermalUnitsIT", "Btu(IT)", Scale = 1055.05585262)]
internal sealed class BritishThermalUnitIT { }
[UnitSymbol("MechanicalHorsepower", "MechanicalHorsepower", "hp", Scale = 745.69987158227022)]
internal sealed class MechanicalHorsepower { }
[UnitSymbol("Fahrenheit", "Fahrenheit", "°F", Scale = 5d / 9d)]
internal sealed class Fahrenheit { }
}
Loading
Loading