From 8748d773b231365ba9ef47463d46a07ba9f1f935 Mon Sep 17 00:00:00 2001 From: Johan Larsson Date: Thu, 3 Dec 2015 22:44:52 +0100 Subject: [PATCH] Add converters. WPF IValueConverters as MarkupExtensions JsonConverters. TypeConverters for Units and Quanities. --- .gitignore | 1 + .../Gu.Units.Fsharp.Tests.fsproj | 45 +- .../Gu.Units.Generator.Tests.csproj | 45 +- Gu.Units.Generator/Descriptors/Settings.cs | 15 +- Gu.Units.Generator/GeneratorSettings.xml | 170 ++- Gu.Units.Generator/Gu.Units.Generator.csproj | 113 +- Gu.Units.Generator/Key.snk | Bin 0 -> 596 bytes Gu.Units.Generator/Properties/AssemblyInfo.cs | 6 +- Gu.Units.Generator/Templates/Enumerable.tt | 2 - Gu.Units.Generator/Templates/Enumerable.txt | 594 +++++++++ .../Templates/EnumerableGenerator.tt | 14 +- .../Templates/EnumerableGenerator.txt | 2 +- .../Templates/MultipleOutputHelper.ttinclude | 181 +++ Gu.Units.Generator/Templates/Quantity.tt | 294 +++-- Gu.Units.Generator/Templates/Quantity.txt | 492 +------ .../Templates/QuantityGenerator.tt | 32 - .../Templates/QuantityGenerator.txt | 100 -- .../Templates/QuantityJsonConverter.tt | 70 + .../Templates/QuantityJsonConverters.tt | 33 + .../Templates/QuantityTypeConverter.tt | 9 +- ...Generator.tt => QuantityTypeConverters.tt} | 20 +- .../Templates/QuantityValueConverter.tt | 341 +++++ .../Templates/QuantityValueConverters.tt | 34 + Gu.Units.Generator/Templates/Quantties.tt | 33 + Gu.Units.Generator/Templates/Readme.txt | 5 + .../TemplateFilemanager.CS.ttinclude | 1131 +++++++++++++++++ Gu.Units.Generator/Templates/Unit.tt | 43 +- Gu.Units.Generator/Templates/Unit.txt | 40 +- .../Templates/UnitGenerator.txt | 100 -- .../Templates/UnitTypeConverter.tt | 9 +- .../Templates/UnitTypeConverterGenerator.tt | 37 - .../Templates/UnitTypeConverters.tt | 38 + .../Templates/{UnitGenerator.tt => Units.tt} | 24 +- .../WpfStuff/UnitPartsConverter.cs | 4 +- Gu.Units.Json.Tests/DummyClass.cs | 7 + .../Gu.Units.Json.Tests.csproj | 122 ++ .../Properties/AssemblyInfo.cs | 36 + Gu.Units.Json.Tests/SerializationTests.cs | 64 + Gu.Units.Json.Tests/paket.references | 2 + .../AccelerationJsonConverter.generated.cs | 41 + Gu.Units.Json/AngleJsonConverter.generated.cs | 40 + ...AnglePerUnitlessJsonConverter.generated.cs | 41 + ...ularAccelerationJsonConverter.generated.cs | 44 + .../AngularJerkJsonConverter.generated.cs | 44 + .../AngularSpeedJsonConverter.generated.cs | 45 + Gu.Units.Json/AreaJsonConverter.generated.cs | 47 + .../CapacitanceJsonConverter.generated.cs | 39 + .../CurrentJsonConverter.generated.cs | 43 + .../DensityJsonConverter.generated.cs | 41 + .../ElectricChargeJsonConverter.generated.cs | 39 + .../EnergyJsonConverter.generated.cs | 46 + .../FlexibilityJsonConverter.generated.cs | 42 + Gu.Units.Json/ForceJsonConverter.generated.cs | 45 + ...ForcePerUnitlessJsonConverter.generated.cs | 43 + .../FrequencyJsonConverter.generated.cs | 43 + Gu.Units.Json/Gu.Units.Json.csproj | 218 ++++ .../InductanceJsonConverter.generated.cs | 39 + Gu.Units.Json/JerkJsonConverter.generated.cs | 49 + .../LengthJsonConverter.generated.cs | 49 + ...engthPerUnitlessJsonConverter.generated.cs | 43 + ...uminousIntensityJsonConverter.generated.cs | 39 + Gu.Units.Json/MassJsonConverter.generated.cs | 42 + Gu.Units.Json/PowerJsonConverter.generated.cs | 45 + .../PressureJsonConverter.generated.cs | 50 + Gu.Units.Json/Properties/AssemblyInfo.cs | 36 + .../QuantityJsonConverters.txt4 | 0 .../ResistanceJsonConverter.generated.cs | 43 + .../SpecificEnergyJsonConverter.generated.cs | 39 + Gu.Units.Json/SpeedJsonConverter.generated.cs | 48 + .../StiffnessJsonConverter.generated.cs | 39 + .../TemperatureJsonConverter.generated.cs | 41 + Gu.Units.Json/TimeJsonConverter.generated.cs | 44 + .../TorqueJsonConverter.generated.cs | 39 + .../UnitlessJsonConverter.generated.cs | 42 + .../VoltageJsonConverter.generated.cs | 43 + .../VolumeJsonConverter.generated.cs | 43 + .../VolumetricFlowJsonConverter.generated.cs | 39 + Gu.Units.Json/paket.references | 1 + Gu.Units.Json/paket.template | 5 + .../BenchMarks/FormattingBenchmarks.cs | 69 + .../BenchMarks/ParsingBenchMarks.cs | 277 ++++ .../BenchMarks/QuantityBenchmark.cs | 67 + .../BenchMarks/SubstringCacheBenchmarks.cs | 82 ++ Gu.Units.Tests/Constants/Benchmarks.cs | 7 + Gu.Units.Tests/Constants/Reminder.cs | 8 + Gu.Units.Tests/DoubleParserTests.cs | 162 --- Gu.Units.Tests/FormatTests.cs | 121 ++ Gu.Units.Tests/Gu.Units.Tests.csproj | 87 +- .../Gu.Units.Tests.csproj.DotSettings | 7 +- .../Internals/Caching/UnitFormatCacheTests.cs | 49 + Gu.Units.Tests/Internals/Helpers/MapTests.cs | 39 + .../Helpers/StringBuilderPoolTests.cs | 61 + .../Internals/Helpers/StringMapTests.cs | 103 ++ .../Parsing/CompositeFormatParserTests.cs | 74 ++ .../Parsing/DoubleFormatReaderTests.cs | 71 ++ .../Internals/Parsing/DoubleReaderTests.cs | 318 +++++ .../Internals/Parsing/Extensions/ParseExt.cs | 54 + .../Internals/Parsing/Extensions/ThreadExt.cs | 32 + .../Internals/Parsing/IntReaderTests.cs | 78 ++ .../Internals/Parsing/ParseLengthTests.cs | 146 +++ .../Internals/Parsing/ParseRoundtripTests.cs | 94 ++ .../Internals/Parsing/PowerReaderTests.cs | 48 + .../Internals/Parsing/SkipperTests.cs | 31 + .../Internals/Parsing/Sources/ErrorData.cs | 49 + .../Internals/Parsing/Sources/ErrorData{T}.cs | 32 + .../Internals/Parsing/Sources/IErrorData.cs | 19 + .../Internals/Parsing/Sources/ISuccessData.cs | 20 + .../Internals/Parsing/Sources/SuccessData.cs | 40 + .../Parsing/Sources/SuccessData{T}.cs | 77 ++ .../Parsing/SymbolAndPowerParserSetTests.cs | 59 + .../Parsing/SymbolAndPowerParserTests.cs | 79 ++ .../Internals/Parsing/UnitParserTests.cs | 70 + .../{QuantityTests.cs => LengthTests.cs} | 35 +- Gu.Units.Tests/ParserTests.cs | 86 -- Gu.Units.Tests/ParserTests.cs.orig | 129 -- Gu.Units.Tests/Samples/Samples.cs | 7 +- Gu.Units.Tests/Sandbox/SandboxTests.cs | 76 -- Gu.Units.Tests/Sources/ConversionProvider.cs | 15 +- Gu.Units.Tests/Sources/TokenSource.cs | 55 - Gu.Units.Tests/SymbolAndPowerTests.cs | 52 - .../{Helpers => TestHelpers}/DummyUnit.cs | 3 + Gu.Units.Tests/UnitParserTests.cs.orig | 98 -- Gu.Units.Tests/UnitTests.cs | 3 +- Gu.Units.Wpf.Demo/App.xaml | 25 +- Gu.Units.Wpf.Demo/DoubleControl.cs | 22 + Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj | 29 + Gu.Units.Wpf.Demo/InputOptionsView.xaml | 47 + Gu.Units.Wpf.Demo/InputOptionsView.xaml.cs | 28 + Gu.Units.Wpf.Demo/MainWindow.xaml | 41 +- Gu.Units.Wpf.Demo/StringFormatView.xaml | 53 + Gu.Units.Wpf.Demo/StringFormatView.xaml.cs | 15 + Gu.Units.Wpf.Demo/SymbolOptions.xaml | 55 + Gu.Units.Wpf.Demo/SymbolOptions.xaml.cs | 28 + Gu.Units.Wpf.Demo/UnitsOnlyView.xaml | 57 + Gu.Units.Wpf.Demo/UnitsOnlyView.xaml.cs | 15 + Gu.Units.Wpf.Demo/ViewModel.cs | 34 +- Gu.Units.Wpf.Tests/Class1.cs | 6 - Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj | 57 +- .../LengthConverterTests.convert.cs | 137 ++ .../LengthConverterTests.convertback.cs | 53 + Gu.Units.Wpf.Tests/LengthConverterTests.cs | 13 + .../LengthConverterTests.setup.cs | 104 ++ Gu.Units.Wpf.Tests/Reminder.cs | 7 + Gu.Units.Wpf.Tests/ServiceProviderMock.cs | 36 + Gu.Units.Wpf.Tests/StringFormatParserTests.cs | 21 + Gu.Units.Wpf.Tests/paket.references | 3 +- .../AccelerationConverter.generated.cs | 320 +++++ Gu.Units.Wpf/AngleConverter.generated.cs | 320 +++++ .../AnglePerUnitlessConverter.generated.cs | 320 +++++ .../AngularAccelerationConverter.generated.cs | 320 +++++ .../AngularJerkConverter.generated.cs | 320 +++++ .../AngularSpeedConverter.generated.cs | 320 +++++ Gu.Units.Wpf/AreaConverter.generated.cs | 320 +++++ .../CapacitanceConverter.generated.cs | 320 +++++ Gu.Units.Wpf/CurrentConverter.generated.cs | 320 +++++ Gu.Units.Wpf/DensityConverter.generated.cs | 320 +++++ .../ElectricChargeConverter.generated.cs | 320 +++++ Gu.Units.Wpf/EnergyConverter.generated.cs | 320 +++++ .../FlexibilityConverter.generated.cs | 320 +++++ Gu.Units.Wpf/ForceConverter.generated.cs | 320 +++++ .../ForcePerUnitlessConverter.generated.cs | 320 +++++ Gu.Units.Wpf/FrequencyConverter.generated.cs | 320 +++++ Gu.Units.Wpf/Gu.Units.Wpf.csproj | 115 +- Gu.Units.Wpf/Gu.Units.Wpf.csproj.DotSettings | 2 + Gu.Units.Wpf/InductanceConverter.generated.cs | 320 +++++ Gu.Units.Wpf/Internal/Is.cs | 11 + Gu.Units.Wpf/Internal/StringFormatParser.cs | 86 ++ Gu.Units.Wpf/JerkConverter.generated.cs | 320 +++++ Gu.Units.Wpf/LengthConverter.cs | 50 - Gu.Units.Wpf/LengthConverter.generated.cs | 320 +++++ .../LengthPerUnitlessConverter.generated.cs | 320 +++++ .../LuminousIntensityConverter.generated.cs | 320 +++++ Gu.Units.Wpf/MassConverter.generated.cs | 320 +++++ Gu.Units.Wpf/PowerConverter.generated.cs | 320 +++++ Gu.Units.Wpf/PressureConverter.generated.cs | 320 +++++ Gu.Units.Wpf/Properties/AssemblyInfo.cs | 6 + .../QuantityValueConverters.txt4 | 0 Gu.Units.Wpf/ResistanceConverter.generated.cs | 320 +++++ .../SpecificEnergyConverter.generated.cs | 320 +++++ Gu.Units.Wpf/SpeedConverter.generated.cs | 320 +++++ Gu.Units.Wpf/StiffnessConverter.generated.cs | 320 +++++ .../TemperatureConverter.generated.cs | 320 +++++ Gu.Units.Wpf/TimeConverter.generated.cs | 320 +++++ Gu.Units.Wpf/TorqueConverter.generated.cs | 320 +++++ Gu.Units.Wpf/UnitInput.cs | 21 + Gu.Units.Wpf/UnitlessConverter.generated.cs | 320 +++++ Gu.Units.Wpf/VoltageConverter.generated.cs | 320 +++++ Gu.Units.Wpf/VolumeConverter.generated.cs | 320 +++++ .../VolumetricFlowConverter.generated.cs | 320 +++++ Gu.Units.Wpf/paket.template | 5 + Gu.Units.sln | 48 +- Gu.Units.sln.DotSettings | 3 + Gu.Units/Acceleration.generated.cs | 282 ++-- Gu.Units/AccelerationUnit.generated.cs | 31 +- Gu.Units/Angle.generated.cs | 290 +++-- Gu.Units/AnglePerUnitless.generated.cs | 596 +++++++++ ...AnglePerUnitlessTypeConverter.generated.cs | 87 ++ Gu.Units/AnglePerUnitlessUnit.generated.cs | 157 +++ ...ePerUnitlessUnitTypeConverter.generated.cs | 88 ++ Gu.Units/AngleUnit.generated.cs | 32 +- Gu.Units/AngularAcceleration.generated.cs | 288 +++-- Gu.Units/AngularAccelerationUnit.generated.cs | 37 +- Gu.Units/AngularJerk.generated.cs | 288 +++-- Gu.Units/AngularJerkUnit.generated.cs | 37 +- Gu.Units/AngularSpeed.generated.cs | 290 +++-- Gu.Units/AngularSpeedUnit.generated.cs | 42 +- Gu.Units/Area.generated.cs | 294 +++-- Gu.Units/AreaUnit.generated.cs | 46 +- Gu.Units/Capacitance.generated.cs | 278 ++-- Gu.Units/CapacitanceUnit.generated.cs | 30 +- Gu.Units/Current.generated.cs | 286 +++-- Gu.Units/CurrentUnit.generated.cs | 50 +- Gu.Units/Density.generated.cs | 282 ++-- Gu.Units/DensityUnit.generated.cs | 31 +- Gu.Units/ElectricCharge.generated.cs | 278 ++-- Gu.Units/ElectricChargeUnit.generated.cs | 30 +- Gu.Units/Energy.generated.cs | 292 +++-- Gu.Units/EnergyUnit.generated.cs | 65 +- Gu.Units/Enumerable.generated.cs | 594 +++++++++ Gu.Units/Flexibility.generated.cs | 284 +++-- Gu.Units/FlexibilityUnit.generated.cs | 33 +- Gu.Units/Force.generated.cs | 295 +++-- Gu.Units/ForcePerUnitless.generated.cs | 641 ++++++++++ ...ForcePerUnitlessTypeConverter.generated.cs | 87 ++ Gu.Units/ForcePerUnitlessUnit.generated.cs | 169 +++ ...ePerUnitlessUnitTypeConverter.generated.cs | 88 ++ Gu.Units/ForceUnit.generated.cs | 60 +- Gu.Units/Frequency.generated.cs | 286 +++-- Gu.Units/FrequencyUnit.generated.cs | 50 +- Gu.Units/Gu.Units.csproj | 349 +++-- Gu.Units/Gu.Units.csproj.DotSettings | 7 + Gu.Units/Gu.Units.nuspec | 12 - Gu.Units/Inductance.generated.cs | 278 ++-- Gu.Units/InductanceUnit.generated.cs | 30 +- Gu.Units/Interfaces/IPower.cs | 38 - Gu.Units/Interfaces/IQuantity.cs | 53 +- Gu.Units/Interfaces/IQuantity{TUnit}.cs | 8 + Gu.Units/Interfaces/IUnit.cs | 2 + .../{IUnit`1.cs => IUnit{TQuantity}.cs} | 0 .../Internals/Caching/DoubleFormatCache.cs | 48 + Gu.Units/Internals/Caching/FormatCache.cs | 7 + .../Internals/Caching/FormatCache{TUnit}.cs | 230 ++++ Gu.Units/Internals/Caching/Map.cs | 51 + Gu.Units/Internals/Caching/ReadOnlySet.cs | 14 + Gu.Units/Internals/Caching/ReadonlySet{T}.cs | 103 ++ Gu.Units/Internals/Caching/StringMap.cs | 221 ++++ Gu.Units/Internals/Caching/SymbolComparer.cs | 19 + .../Caching/UnitFormatCache{TUnit}.cs | 201 +++ Gu.Units/Internals/Ensure/Ensure.compare.cs | 49 + Gu.Units/Internals/Ensure/Ensure.cs | 62 + Gu.Units/Internals/Ensure/Ensure.string.cs | 27 + .../Internals/Helpers/CollectionDebugView.cs | 37 + Gu.Units/Internals/Helpers/EnumerableExt.cs | 42 + .../Internals/Helpers/StringBuilderPool.cs | 198 +++ Gu.Units/Internals/Helpers/VoidStruct.cs | 7 + Gu.Units/Internals/PaddedFormat.cs | 66 + .../Parsing/CompositeFormatParser.cs | 65 + .../Internals/Parsing/DoubleFormatReader.cs | 94 ++ Gu.Units/Internals/Parsing/DoubleReader.cs | 303 +++++ Gu.Units/Internals/Parsing/IntReader.cs | 196 +++ Gu.Units/Internals/Parsing/OperatorReader.cs | 49 + Gu.Units/Internals/Parsing/PowerReader.cs | 172 +++ Gu.Units/Internals/Parsing/QuantityParser.cs | 77 ++ .../Parsing/Results/MultiplyOrDivide.cs | 9 + .../Parsing/Results}/Sign.cs | 0 Gu.Units/Internals/Parsing/Skipper.cs | 55 + .../Internals/Parsing/SymbolAndPowerReader.cs | 154 +++ Gu.Units/Internals/Parsing/UnitParser.cs | 84 ++ .../Internals/Parsing/WhiteSpaceReader.cs | 77 ++ .../{Utils => Internals/Parsing}/XmlExt.cs | 3 +- Gu.Units/Internals/QuantityFormat.cs | 238 ++++ Gu.Units/Internals/SymbolAndPower.cs | 72 ++ Gu.Units/Jerk.generated.cs | 298 +++-- Gu.Units/JerkUnit.generated.cs | 47 +- Gu.Units/Length.generated.cs | 308 +++-- Gu.Units/LengthPerUnitless.generated.cs | 636 +++++++++ ...engthPerUnitlessTypeConverter.generated.cs | 87 ++ Gu.Units/LengthPerUnitlessUnit.generated.cs | 169 +++ ...hPerUnitlessUnitTypeConverter.generated.cs | 88 ++ Gu.Units/LengthUnit.generated.cs | 77 +- Gu.Units/LuminousIntensity.generated.cs | 278 ++-- Gu.Units/LuminousIntensityUnit.generated.cs | 30 +- Gu.Units/Mass.generated.cs | 284 +++-- Gu.Units/MassUnit.generated.cs | 45 +- Gu.Units/Power.generated.cs | 290 +++-- Gu.Units/PowerUnit.generated.cs | 60 +- Gu.Units/Pressure.generated.cs | 354 ++++-- Gu.Units/PressureUnit.generated.cs | 88 +- Gu.Units/Properties/AssemblyInfo.cs | 4 +- ...rator.txt4 => QuantityTypeConverters.txt4} | 0 ...ConverterGenerator.txt4 => Quantties.txt4} | 0 Gu.Units/Resistance.generated.cs | 286 +++-- Gu.Units/ResistanceUnit.generated.cs | 50 +- Gu.Units/SpecificEnergy.generated.cs | 278 ++-- Gu.Units/SpecificEnergyUnit.generated.cs | 27 +- Gu.Units/Speed.generated.cs | 296 +++-- Gu.Units/SpeedUnit.generated.cs | 45 +- Gu.Units/Stiffness.generated.cs | 283 +++-- Gu.Units/StiffnessUnit.generated.cs | 27 +- Gu.Units/SuperScript.cs | 37 + Gu.Units/SymbolFormat.cs | 11 + Gu.Units/Temperature.generated.cs | 282 ++-- Gu.Units/TemperatureUnit.generated.cs | 34 +- Gu.Units/Time.generated.cs | 288 +++-- Gu.Units/TimeUnit.generated.cs | 55 +- Gu.Units/Torque.generated.cs | 278 ++-- Gu.Units/TorqueUnit.generated.cs | 27 +- Gu.Units/UnitTypeConverters.txt4 | 0 Gu.Units/Unitless.generated.cs | 294 +++-- Gu.Units/UnitlessUnit.generated.cs | 39 +- Gu.Units/Units.txt4 | 0 Gu.Units/Unit{T}.cs | 8 + Gu.Units/Utils/DoubleParser.cs | 336 ----- Gu.Units/Utils/Parser.cs | 196 --- Gu.Units/Utils/SymbolAndPower.cs | 397 ------ Gu.Units/Voltage.generated.cs | 286 +++-- Gu.Units/VoltageUnit.generated.cs | 50 +- Gu.Units/Volume.generated.cs | 286 +++-- Gu.Units/VolumeUnit.generated.cs | 38 +- Gu.Units/VolumetricFlow.generated.cs | 278 ++-- Gu.Units/VolumetricFlowUnit.generated.cs | 27 +- paket.dependencies | 5 +- paket.lock | 5 +- 323 files changed, 33687 insertions(+), 6495 deletions(-) create mode 100644 Gu.Units.Generator/Key.snk create mode 100644 Gu.Units.Generator/Templates/MultipleOutputHelper.ttinclude delete mode 100644 Gu.Units.Generator/Templates/QuantityGenerator.tt delete mode 100644 Gu.Units.Generator/Templates/QuantityGenerator.txt create mode 100644 Gu.Units.Generator/Templates/QuantityJsonConverter.tt create mode 100644 Gu.Units.Generator/Templates/QuantityJsonConverters.tt rename Gu.Units.Generator/Templates/{QuantityTypeConverterGenerator.tt => QuantityTypeConverters.tt} (62%) create mode 100644 Gu.Units.Generator/Templates/QuantityValueConverter.tt create mode 100644 Gu.Units.Generator/Templates/QuantityValueConverters.tt create mode 100644 Gu.Units.Generator/Templates/Quantties.tt create mode 100644 Gu.Units.Generator/Templates/Readme.txt create mode 100644 Gu.Units.Generator/Templates/TemplateFilemanager.CS.ttinclude delete mode 100644 Gu.Units.Generator/Templates/UnitGenerator.txt delete mode 100644 Gu.Units.Generator/Templates/UnitTypeConverterGenerator.tt create mode 100644 Gu.Units.Generator/Templates/UnitTypeConverters.tt rename Gu.Units.Generator/Templates/{UnitGenerator.tt => Units.tt} (64%) create mode 100644 Gu.Units.Json.Tests/DummyClass.cs create mode 100644 Gu.Units.Json.Tests/Gu.Units.Json.Tests.csproj create mode 100644 Gu.Units.Json.Tests/Properties/AssemblyInfo.cs create mode 100644 Gu.Units.Json.Tests/SerializationTests.cs create mode 100644 Gu.Units.Json.Tests/paket.references create mode 100644 Gu.Units.Json/AccelerationJsonConverter.generated.cs create mode 100644 Gu.Units.Json/AngleJsonConverter.generated.cs create mode 100644 Gu.Units.Json/AnglePerUnitlessJsonConverter.generated.cs create mode 100644 Gu.Units.Json/AngularAccelerationJsonConverter.generated.cs create mode 100644 Gu.Units.Json/AngularJerkJsonConverter.generated.cs create mode 100644 Gu.Units.Json/AngularSpeedJsonConverter.generated.cs create mode 100644 Gu.Units.Json/AreaJsonConverter.generated.cs create mode 100644 Gu.Units.Json/CapacitanceJsonConverter.generated.cs create mode 100644 Gu.Units.Json/CurrentJsonConverter.generated.cs create mode 100644 Gu.Units.Json/DensityJsonConverter.generated.cs create mode 100644 Gu.Units.Json/ElectricChargeJsonConverter.generated.cs create mode 100644 Gu.Units.Json/EnergyJsonConverter.generated.cs create mode 100644 Gu.Units.Json/FlexibilityJsonConverter.generated.cs create mode 100644 Gu.Units.Json/ForceJsonConverter.generated.cs create mode 100644 Gu.Units.Json/ForcePerUnitlessJsonConverter.generated.cs create mode 100644 Gu.Units.Json/FrequencyJsonConverter.generated.cs create mode 100644 Gu.Units.Json/Gu.Units.Json.csproj create mode 100644 Gu.Units.Json/InductanceJsonConverter.generated.cs create mode 100644 Gu.Units.Json/JerkJsonConverter.generated.cs create mode 100644 Gu.Units.Json/LengthJsonConverter.generated.cs create mode 100644 Gu.Units.Json/LengthPerUnitlessJsonConverter.generated.cs create mode 100644 Gu.Units.Json/LuminousIntensityJsonConverter.generated.cs create mode 100644 Gu.Units.Json/MassJsonConverter.generated.cs create mode 100644 Gu.Units.Json/PowerJsonConverter.generated.cs create mode 100644 Gu.Units.Json/PressureJsonConverter.generated.cs create mode 100644 Gu.Units.Json/Properties/AssemblyInfo.cs rename Gu.Units/QuantityGenerator.txt4 => Gu.Units.Json/QuantityJsonConverters.txt4 (100%) create mode 100644 Gu.Units.Json/ResistanceJsonConverter.generated.cs create mode 100644 Gu.Units.Json/SpecificEnergyJsonConverter.generated.cs create mode 100644 Gu.Units.Json/SpeedJsonConverter.generated.cs create mode 100644 Gu.Units.Json/StiffnessJsonConverter.generated.cs create mode 100644 Gu.Units.Json/TemperatureJsonConverter.generated.cs create mode 100644 Gu.Units.Json/TimeJsonConverter.generated.cs create mode 100644 Gu.Units.Json/TorqueJsonConverter.generated.cs create mode 100644 Gu.Units.Json/UnitlessJsonConverter.generated.cs create mode 100644 Gu.Units.Json/VoltageJsonConverter.generated.cs create mode 100644 Gu.Units.Json/VolumeJsonConverter.generated.cs create mode 100644 Gu.Units.Json/VolumetricFlowJsonConverter.generated.cs create mode 100644 Gu.Units.Json/paket.references create mode 100644 Gu.Units.Json/paket.template create mode 100644 Gu.Units.Tests/BenchMarks/FormattingBenchmarks.cs create mode 100644 Gu.Units.Tests/BenchMarks/ParsingBenchMarks.cs create mode 100644 Gu.Units.Tests/BenchMarks/QuantityBenchmark.cs create mode 100644 Gu.Units.Tests/BenchMarks/SubstringCacheBenchmarks.cs create mode 100644 Gu.Units.Tests/Constants/Benchmarks.cs create mode 100644 Gu.Units.Tests/Constants/Reminder.cs delete mode 100644 Gu.Units.Tests/DoubleParserTests.cs create mode 100644 Gu.Units.Tests/FormatTests.cs create mode 100644 Gu.Units.Tests/Internals/Caching/UnitFormatCacheTests.cs create mode 100644 Gu.Units.Tests/Internals/Helpers/MapTests.cs create mode 100644 Gu.Units.Tests/Internals/Helpers/StringBuilderPoolTests.cs create mode 100644 Gu.Units.Tests/Internals/Helpers/StringMapTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/CompositeFormatParserTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/DoubleFormatReaderTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/DoubleReaderTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/Extensions/ParseExt.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/Extensions/ThreadExt.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/IntReaderTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/ParseLengthTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/ParseRoundtripTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/PowerReaderTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/SkipperTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/Sources/ErrorData.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/Sources/ErrorData{T}.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/Sources/IErrorData.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/Sources/ISuccessData.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/Sources/SuccessData.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/Sources/SuccessData{T}.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserSetTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserTests.cs create mode 100644 Gu.Units.Tests/Internals/Parsing/UnitParserTests.cs rename Gu.Units.Tests/{QuantityTests.cs => LengthTests.cs} (67%) delete mode 100644 Gu.Units.Tests/ParserTests.cs delete mode 100644 Gu.Units.Tests/ParserTests.cs.orig delete mode 100644 Gu.Units.Tests/Sandbox/SandboxTests.cs delete mode 100644 Gu.Units.Tests/Sources/TokenSource.cs delete mode 100644 Gu.Units.Tests/SymbolAndPowerTests.cs rename Gu.Units.Tests/{Helpers => TestHelpers}/DummyUnit.cs (89%) delete mode 100644 Gu.Units.Tests/UnitParserTests.cs.orig create mode 100644 Gu.Units.Wpf.Demo/DoubleControl.cs create mode 100644 Gu.Units.Wpf.Demo/InputOptionsView.xaml create mode 100644 Gu.Units.Wpf.Demo/InputOptionsView.xaml.cs create mode 100644 Gu.Units.Wpf.Demo/StringFormatView.xaml create mode 100644 Gu.Units.Wpf.Demo/StringFormatView.xaml.cs create mode 100644 Gu.Units.Wpf.Demo/SymbolOptions.xaml create mode 100644 Gu.Units.Wpf.Demo/SymbolOptions.xaml.cs create mode 100644 Gu.Units.Wpf.Demo/UnitsOnlyView.xaml create mode 100644 Gu.Units.Wpf.Demo/UnitsOnlyView.xaml.cs delete mode 100644 Gu.Units.Wpf.Tests/Class1.cs create mode 100644 Gu.Units.Wpf.Tests/LengthConverterTests.convert.cs create mode 100644 Gu.Units.Wpf.Tests/LengthConverterTests.convertback.cs create mode 100644 Gu.Units.Wpf.Tests/LengthConverterTests.cs create mode 100644 Gu.Units.Wpf.Tests/LengthConverterTests.setup.cs create mode 100644 Gu.Units.Wpf.Tests/Reminder.cs create mode 100644 Gu.Units.Wpf.Tests/ServiceProviderMock.cs create mode 100644 Gu.Units.Wpf.Tests/StringFormatParserTests.cs create mode 100644 Gu.Units.Wpf/AccelerationConverter.generated.cs create mode 100644 Gu.Units.Wpf/AngleConverter.generated.cs create mode 100644 Gu.Units.Wpf/AnglePerUnitlessConverter.generated.cs create mode 100644 Gu.Units.Wpf/AngularAccelerationConverter.generated.cs create mode 100644 Gu.Units.Wpf/AngularJerkConverter.generated.cs create mode 100644 Gu.Units.Wpf/AngularSpeedConverter.generated.cs create mode 100644 Gu.Units.Wpf/AreaConverter.generated.cs create mode 100644 Gu.Units.Wpf/CapacitanceConverter.generated.cs create mode 100644 Gu.Units.Wpf/CurrentConverter.generated.cs create mode 100644 Gu.Units.Wpf/DensityConverter.generated.cs create mode 100644 Gu.Units.Wpf/ElectricChargeConverter.generated.cs create mode 100644 Gu.Units.Wpf/EnergyConverter.generated.cs create mode 100644 Gu.Units.Wpf/FlexibilityConverter.generated.cs create mode 100644 Gu.Units.Wpf/ForceConverter.generated.cs create mode 100644 Gu.Units.Wpf/ForcePerUnitlessConverter.generated.cs create mode 100644 Gu.Units.Wpf/FrequencyConverter.generated.cs create mode 100644 Gu.Units.Wpf/Gu.Units.Wpf.csproj.DotSettings create mode 100644 Gu.Units.Wpf/InductanceConverter.generated.cs create mode 100644 Gu.Units.Wpf/Internal/Is.cs create mode 100644 Gu.Units.Wpf/Internal/StringFormatParser.cs create mode 100644 Gu.Units.Wpf/JerkConverter.generated.cs delete mode 100644 Gu.Units.Wpf/LengthConverter.cs create mode 100644 Gu.Units.Wpf/LengthConverter.generated.cs create mode 100644 Gu.Units.Wpf/LengthPerUnitlessConverter.generated.cs create mode 100644 Gu.Units.Wpf/LuminousIntensityConverter.generated.cs create mode 100644 Gu.Units.Wpf/MassConverter.generated.cs create mode 100644 Gu.Units.Wpf/PowerConverter.generated.cs create mode 100644 Gu.Units.Wpf/PressureConverter.generated.cs rename Gu.Units/QuantityTypeConverterGenerator.txt4 => Gu.Units.Wpf/QuantityValueConverters.txt4 (100%) create mode 100644 Gu.Units.Wpf/ResistanceConverter.generated.cs create mode 100644 Gu.Units.Wpf/SpecificEnergyConverter.generated.cs create mode 100644 Gu.Units.Wpf/SpeedConverter.generated.cs create mode 100644 Gu.Units.Wpf/StiffnessConverter.generated.cs create mode 100644 Gu.Units.Wpf/TemperatureConverter.generated.cs create mode 100644 Gu.Units.Wpf/TimeConverter.generated.cs create mode 100644 Gu.Units.Wpf/TorqueConverter.generated.cs create mode 100644 Gu.Units.Wpf/UnitInput.cs create mode 100644 Gu.Units.Wpf/UnitlessConverter.generated.cs create mode 100644 Gu.Units.Wpf/VoltageConverter.generated.cs create mode 100644 Gu.Units.Wpf/VolumeConverter.generated.cs create mode 100644 Gu.Units.Wpf/VolumetricFlowConverter.generated.cs create mode 100644 Gu.Units.Wpf/paket.template create mode 100644 Gu.Units/AnglePerUnitless.generated.cs create mode 100644 Gu.Units/AnglePerUnitlessTypeConverter.generated.cs create mode 100644 Gu.Units/AnglePerUnitlessUnit.generated.cs create mode 100644 Gu.Units/AnglePerUnitlessUnitTypeConverter.generated.cs create mode 100644 Gu.Units/ForcePerUnitless.generated.cs create mode 100644 Gu.Units/ForcePerUnitlessTypeConverter.generated.cs create mode 100644 Gu.Units/ForcePerUnitlessUnit.generated.cs create mode 100644 Gu.Units/ForcePerUnitlessUnitTypeConverter.generated.cs delete mode 100644 Gu.Units/Gu.Units.nuspec delete mode 100644 Gu.Units/Interfaces/IPower.cs create mode 100644 Gu.Units/Interfaces/IQuantity{TUnit}.cs rename Gu.Units/Interfaces/{IUnit`1.cs => IUnit{TQuantity}.cs} (100%) create mode 100644 Gu.Units/Internals/Caching/DoubleFormatCache.cs create mode 100644 Gu.Units/Internals/Caching/FormatCache.cs create mode 100644 Gu.Units/Internals/Caching/FormatCache{TUnit}.cs create mode 100644 Gu.Units/Internals/Caching/Map.cs create mode 100644 Gu.Units/Internals/Caching/ReadOnlySet.cs create mode 100644 Gu.Units/Internals/Caching/ReadonlySet{T}.cs create mode 100644 Gu.Units/Internals/Caching/StringMap.cs create mode 100644 Gu.Units/Internals/Caching/SymbolComparer.cs create mode 100644 Gu.Units/Internals/Caching/UnitFormatCache{TUnit}.cs create mode 100644 Gu.Units/Internals/Ensure/Ensure.compare.cs create mode 100644 Gu.Units/Internals/Ensure/Ensure.cs create mode 100644 Gu.Units/Internals/Ensure/Ensure.string.cs create mode 100644 Gu.Units/Internals/Helpers/CollectionDebugView.cs create mode 100644 Gu.Units/Internals/Helpers/EnumerableExt.cs create mode 100644 Gu.Units/Internals/Helpers/StringBuilderPool.cs create mode 100644 Gu.Units/Internals/Helpers/VoidStruct.cs create mode 100644 Gu.Units/Internals/PaddedFormat.cs create mode 100644 Gu.Units/Internals/Parsing/CompositeFormatParser.cs create mode 100644 Gu.Units/Internals/Parsing/DoubleFormatReader.cs create mode 100644 Gu.Units/Internals/Parsing/DoubleReader.cs create mode 100644 Gu.Units/Internals/Parsing/IntReader.cs create mode 100644 Gu.Units/Internals/Parsing/OperatorReader.cs create mode 100644 Gu.Units/Internals/Parsing/PowerReader.cs create mode 100644 Gu.Units/Internals/Parsing/QuantityParser.cs create mode 100644 Gu.Units/Internals/Parsing/Results/MultiplyOrDivide.cs rename Gu.Units/{Utils => Internals/Parsing/Results}/Sign.cs (100%) create mode 100644 Gu.Units/Internals/Parsing/Skipper.cs create mode 100644 Gu.Units/Internals/Parsing/SymbolAndPowerReader.cs create mode 100644 Gu.Units/Internals/Parsing/UnitParser.cs create mode 100644 Gu.Units/Internals/Parsing/WhiteSpaceReader.cs rename Gu.Units/{Utils => Internals/Parsing}/XmlExt.cs (97%) create mode 100644 Gu.Units/Internals/QuantityFormat.cs create mode 100644 Gu.Units/Internals/SymbolAndPower.cs create mode 100644 Gu.Units/LengthPerUnitless.generated.cs create mode 100644 Gu.Units/LengthPerUnitlessTypeConverter.generated.cs create mode 100644 Gu.Units/LengthPerUnitlessUnit.generated.cs create mode 100644 Gu.Units/LengthPerUnitlessUnitTypeConverter.generated.cs rename Gu.Units/{UnitGenerator.txt4 => QuantityTypeConverters.txt4} (100%) rename Gu.Units/{UnitTypeConverterGenerator.txt4 => Quantties.txt4} (100%) create mode 100644 Gu.Units/SuperScript.cs create mode 100644 Gu.Units/SymbolFormat.cs create mode 100644 Gu.Units/UnitTypeConverters.txt4 create mode 100644 Gu.Units/Units.txt4 create mode 100644 Gu.Units/Unit{T}.cs delete mode 100644 Gu.Units/Utils/DoubleParser.cs delete mode 100644 Gu.Units/Utils/Parser.cs delete mode 100644 Gu.Units/Utils/SymbolAndPower.cs diff --git a/.gitignore b/.gitignore index 25faa223..9bb491c0 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ $tf*/ packages/* Gu.Units.Generator/Templates/*.txt publish/* +Generator/* diff --git a/Gu.Units.Fsharp.Tests/Gu.Units.Fsharp.Tests.fsproj b/Gu.Units.Fsharp.Tests/Gu.Units.Fsharp.Tests.fsproj index fd03d4e3..31fd19e5 100644 --- a/Gu.Units.Fsharp.Tests/Gu.Units.Fsharp.Tests.fsproj +++ b/Gu.Units.Fsharp.Tests/Gu.Units.Fsharp.Tests.fsproj @@ -86,42 +86,11 @@ --> - - - - - ..\packages\NUnit\lib\net20\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\net40\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\net45\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll - True - True - - - - + + + ..\packages\NUnit\lib\nunit.framework.dll + True + True + + \ No newline at end of file diff --git a/Gu.Units.Generator.Tests/Gu.Units.Generator.Tests.csproj b/Gu.Units.Generator.Tests/Gu.Units.Generator.Tests.csproj index 8f243bd0..12762745 100644 --- a/Gu.Units.Generator.Tests/Gu.Units.Generator.Tests.csproj +++ b/Gu.Units.Generator.Tests/Gu.Units.Generator.Tests.csproj @@ -73,42 +73,11 @@ --> - - - - - ..\packages\NUnit\lib\net20\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\net40\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\net45\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll - True - True - - - - + + + ..\packages\NUnit\lib\nunit.framework.dll + True + True + + \ No newline at end of file diff --git a/Gu.Units.Generator/Descriptors/Settings.cs b/Gu.Units.Generator/Descriptors/Settings.cs index 9e342f8d..3032ac1a 100644 --- a/Gu.Units.Generator/Descriptors/Settings.cs +++ b/Gu.Units.Generator/Descriptors/Settings.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; - using System.Collections.Specialized; using System.IO; using System.Linq; using System.Reflection; @@ -18,6 +17,7 @@ public class Settings private readonly ParentCollection _derivedUnits; private readonly ParentCollection _siUnits; private readonly ObservableCollection _prefixes = new ObservableCollection(); + protected Settings() { _derivedUnits = new ParentCollection(this, (unit, settings) => unit.Settings = settings); @@ -115,18 +115,9 @@ public ObservableCollection Prefixes get { return _prefixes; } } - public IEnumerable AllUnits - { - get { return SiUnits.Concat(DerivedUnits); } - } + public IReadOnlyList AllUnits => SiUnits.Concat(DerivedUnits).ToList(); - public IEnumerable Quantities - { - get - { - return AllUnits.Select(x => x.Quantity).ToArray(); - } - } + public IReadOnlyList Quantities => AllUnits.Select(x => x.Quantity).ToList(); public static Settings FromFile(string fullFileName) { diff --git a/Gu.Units.Generator/GeneratorSettings.xml b/Gu.Units.Generator/GeneratorSettings.xml index 0763c0a3..da9e0a13 100644 --- a/Gu.Units.Generator/GeneratorSettings.xml +++ b/Gu.Units.Generator/GeneratorSettings.xml @@ -268,7 +268,7 @@ 0 - + Bars bar @@ -287,6 +287,33 @@ + + NewtonsPerSquareMillimetre + N⋅mm⁻² + + 1000000 + 0 + + + + + KilonewtonsPerSquareMillimetre + kN⋅mm⁻² + + 1000000000 + 0 + + + + + NewtonsPerSquareMetre + N/m² + + 1 + 0 + + + @@ -1243,6 +1270,147 @@ + + MetresPerUnitless + m/ul + LengthPerUnitless + + + MillimetresPerPercent + mm/% + + 0.1 + 0 + + + + + MicrometresPerPercent + µm/% + + 9.9999999999999991E-05 + 0 + + + + + NanometresPerPercent + nm/% + + 1.0000000000000001E-07 + 0 + + + + + MetresPerPercent + m/% + + 100 + 0 + + + + + + + Metres + 1 + + + Scalar + -1 + + + + + RadiansPerUnitless + rad/ul + AnglePerUnitless + + + DegreesPerPercent + °/% + + 1.7453292519943295 + 0 + + + + + RadiansPerPercent + rad/% + + 100 + 0 + + + + + + + Radians + 1 + + + Scalar + -1 + + + + + NewtonsPerUnitless + N/ul + ForcePerUnitless + + + NewtonsPerPercent + N/% + + 100 + 0 + + + + + KilonewtonsPerPercent + kN/% + + 100000 + 0 + + + + + MeganewtonsPerPercent + MN/% + + 100000000 + 0 + + + + + GiganewtonsPerPercent + GN/% + + 100000000000 + 0 + + + + + + + Newtons + 1 + + + Scalar + -1 + + + diff --git a/Gu.Units.Generator/Gu.Units.Generator.csproj b/Gu.Units.Generator/Gu.Units.Generator.csproj index 6a319973..dc05b3b3 100644 --- a/Gu.Units.Generator/Gu.Units.Generator.csproj +++ b/Gu.Units.Generator/Gu.Units.Generator.csproj @@ -55,15 +55,14 @@ 4 - false + true - - + Key.snk - + true - bin\Generator Only\ + bin\GeneratorOnly\ DEBUG;TRACE full AnyCPU @@ -146,25 +145,23 @@ TextTemplatingFileGenerator UnitTypeConverter.txt - - True - True - UnitTypeConverterGenerator.tt - + + True True UnitTypeConverter.tt - + TextTemplatingFileGenerator - UnitTypeConverterGenerator.txt + UnitTypeConverters.txt ResXFileCodeGenerator Resources.Designer.cs Designer + SettingsSingleFileGenerator @@ -179,27 +176,22 @@ TextTemplatingFileGenerator EnumerableGenerator.txt - + TextTemplatingFileGenerator - QuantityTypeConverterGenerator.txt + QuantityTypeConverters.txt - - True - True - QuantityTypeConverterGenerator.tt - - + Code TextTemplatingFileGenerator - UnitGenerator.txt + Units.txt TextTemplatingFileGenerator Quantity.txt - + TextTemplatingFileGenerator - QuantityGenerator.txt + Quantties.txt TextTemplatingFileGenerator @@ -213,11 +205,6 @@ - - UnitGenerator.tt - True - True - True True @@ -242,21 +229,16 @@ Designer - - True - True - Enumerable.tt - True True EnumerableGenerator.tt - + True True - QuantityGenerator.tt - + Enumerable.tt + @@ -278,6 +260,65 @@ + + + TextTemplatingFileGenerator + QuantityValueConverter.txt + + + QuantityValueConverter.tt + True + True + + + TextTemplatingFileGenerator + QuantityValueConverters.txt + + + TextTemplatingFileGenerator + QuantityJsonConverter.txt + + + QuantityJsonConverter.tt + True + True + + + TextTemplatingFileGenerator + QuantityJsonConverters.txt + + + QuantityJsonConverters.tt + True + True + + + True + True + QuantityValueConverters.tt + + + True + True + QuantityTypeConverters.tt + + + True + True + Quantties.tt + + + + True + True + Units.tt + + + True + True + UnitTypeConverters.tt + + + + + + + ..\packages\Newtonsoft.Json\lib\net35\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net20\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\portable-net45+wp80+win8+wpa81+dnxcore50\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\NUnit\lib\nunit.framework.dll + True + True + + + \ No newline at end of file diff --git a/Gu.Units.Json.Tests/Properties/AssemblyInfo.cs b/Gu.Units.Json.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..cd139beb --- /dev/null +++ b/Gu.Units.Json.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Gu.Units.Json.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Gu.Units.Json.Tests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ddd84bca-0ff9-4854-bdb3-c69117b2978a")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Gu.Units.Json.Tests/SerializationTests.cs b/Gu.Units.Json.Tests/SerializationTests.cs new file mode 100644 index 00000000..ebbd3e32 --- /dev/null +++ b/Gu.Units.Json.Tests/SerializationTests.cs @@ -0,0 +1,64 @@ +namespace Gu.Units.Json.Tests +{ + using System.Globalization; + using Newtonsoft.Json; + using NUnit.Framework; + + public class SerializationTests + { + [Test] + public void ToJsonDegrees() + { + var dummyClass = new DummyClass { Angle = Angle.FromDegrees(1.23) }; + JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Degrees, "sv-SE"); + var actual = JsonConvert.SerializeObject(dummyClass); + Assert.AreEqual("{\"Angle\":\"1,23\"}", actual); + } + + [Test] + public void ToJsonRadians() + { + var dummyClass = new DummyClass { Angle = Angle.FromRadians(1.23) }; + JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Radians, "en-US"); + var actual = JsonConvert.SerializeObject(dummyClass); + Assert.AreEqual("{\"Angle\":\"1.23\u00A0rad\"}", actual); + } + + [TestCase("{\"Angle\":\"1,23\"}", "sv-SE")] + [TestCase("{\"Angle\":\"1.23\"}", "en-US")] + public void FromJsonDefault(string json, string culture) + { + JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Default, culture); + var actual = JsonConvert.DeserializeObject(json); + Assert.AreEqual(Angle.FromDegrees(1.23), actual.Angle); + } + + [TestCase("{\"Angle\":\"1,23\"}", "sv-SE")] + [TestCase("{\"Angle\":\"1.23\"}", "en-US")] + public void FromJsonDegrees(string json, string culture) + { + JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Degrees, culture); + var actual = JsonConvert.DeserializeObject(json); + Assert.AreEqual(Angle.FromDegrees(1.23), actual.Angle); + } + + [TestCase("{\"Angle\":\"1,23\"}", "sv-SE")] + [TestCase("{\"Angle\":\"1.23\"}", "en-US")] + public void FromJsonRadians(string json, string culture) + { + JsonConvert.DefaultSettings = () => CreateSettings(AngleJsonConverter.Default, culture); + // yes radians converter can read degrees it only outputs radians + var actual = JsonConvert.DeserializeObject(json); + Assert.AreEqual(Angle.FromDegrees(1.23), actual.Angle); + } + + private JsonSerializerSettings CreateSettings(AngleJsonConverter converter, string culture) + { + return new JsonSerializerSettings + { + Converters = new[] { converter }, + Culture = CultureInfo.GetCultureInfo(culture) + }; + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json.Tests/paket.references b/Gu.Units.Json.Tests/paket.references new file mode 100644 index 00000000..91a7da98 --- /dev/null +++ b/Gu.Units.Json.Tests/paket.references @@ -0,0 +1,2 @@ +Newtonsoft.Json +NUnit \ No newline at end of file diff --git a/Gu.Units.Json/AccelerationJsonConverter.generated.cs b/Gu.Units.Json/AccelerationJsonConverter.generated.cs new file mode 100644 index 00000000..9910e4fd --- /dev/null +++ b/Gu.Units.Json/AccelerationJsonConverter.generated.cs @@ -0,0 +1,41 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class AccelerationJsonConverter : JsonConverter + { + public static readonly AccelerationJsonConverter Default = new AccelerationJsonConverter(AccelerationUnit.MetresPerSecondSquared); + public static readonly AccelerationJsonConverter MetresPerSecondSquared = new AccelerationJsonConverter(AccelerationUnit.MetresPerSecondSquared); + public static readonly AccelerationJsonConverter MillimetresPerSecondSquared = new AccelerationJsonConverter(AccelerationUnit.MillimetresPerSecondSquared); + public static readonly AccelerationJsonConverter CentimetresPerSecondSquared = new AccelerationJsonConverter(AccelerationUnit.CentimetresPerSecondSquared); + + private readonly AccelerationUnit unit; + + private AccelerationJsonConverter(AccelerationUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var acceleration = (Acceleration)value; + serializer.Serialize(writer, acceleration.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Acceleration); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Acceleration.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/AngleJsonConverter.generated.cs b/Gu.Units.Json/AngleJsonConverter.generated.cs new file mode 100644 index 00000000..c199ebf2 --- /dev/null +++ b/Gu.Units.Json/AngleJsonConverter.generated.cs @@ -0,0 +1,40 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class AngleJsonConverter : JsonConverter + { + public static readonly AngleJsonConverter Default = new AngleJsonConverter(AngleUnit.Radians); + public static readonly AngleJsonConverter Radians = new AngleJsonConverter(AngleUnit.Radians); + public static readonly AngleJsonConverter Degrees = new AngleJsonConverter(AngleUnit.Degrees); + + private readonly AngleUnit unit; + + private AngleJsonConverter(AngleUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var angle = (Angle)value; + serializer.Serialize(writer, angle.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Angle); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Angle.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/AnglePerUnitlessJsonConverter.generated.cs b/Gu.Units.Json/AnglePerUnitlessJsonConverter.generated.cs new file mode 100644 index 00000000..68573263 --- /dev/null +++ b/Gu.Units.Json/AnglePerUnitlessJsonConverter.generated.cs @@ -0,0 +1,41 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class AnglePerUnitlessJsonConverter : JsonConverter + { + public static readonly AnglePerUnitlessJsonConverter Default = new AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit.RadiansPerUnitless); + public static readonly AnglePerUnitlessJsonConverter RadiansPerUnitless = new AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit.RadiansPerUnitless); + public static readonly AnglePerUnitlessJsonConverter DegreesPerPercent = new AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit.DegreesPerPercent); + public static readonly AnglePerUnitlessJsonConverter RadiansPerPercent = new AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit.RadiansPerPercent); + + private readonly AnglePerUnitlessUnit unit; + + private AnglePerUnitlessJsonConverter(AnglePerUnitlessUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var anglePerUnitless = (AnglePerUnitless)value; + serializer.Serialize(writer, anglePerUnitless.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(AnglePerUnitless); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return AnglePerUnitless.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/AngularAccelerationJsonConverter.generated.cs b/Gu.Units.Json/AngularAccelerationJsonConverter.generated.cs new file mode 100644 index 00000000..e076580b --- /dev/null +++ b/Gu.Units.Json/AngularAccelerationJsonConverter.generated.cs @@ -0,0 +1,44 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class AngularAccelerationJsonConverter : JsonConverter + { + public static readonly AngularAccelerationJsonConverter Default = new AngularAccelerationJsonConverter(AngularAccelerationUnit.RadiansPerSecondSquared); + public static readonly AngularAccelerationJsonConverter RadiansPerSecondSquared = new AngularAccelerationJsonConverter(AngularAccelerationUnit.RadiansPerSecondSquared); + public static readonly AngularAccelerationJsonConverter DegreesPerSquareSecond = new AngularAccelerationJsonConverter(AngularAccelerationUnit.DegreesPerSquareSecond); + public static readonly AngularAccelerationJsonConverter RadiansPerSquareHour = new AngularAccelerationJsonConverter(AngularAccelerationUnit.RadiansPerSquareHour); + public static readonly AngularAccelerationJsonConverter DegreesPerSquareHour = new AngularAccelerationJsonConverter(AngularAccelerationUnit.DegreesPerSquareHour); + public static readonly AngularAccelerationJsonConverter DegreesPerSquareMinute = new AngularAccelerationJsonConverter(AngularAccelerationUnit.DegreesPerSquareMinute); + public static readonly AngularAccelerationJsonConverter RadiansPerSquareMinute = new AngularAccelerationJsonConverter(AngularAccelerationUnit.RadiansPerSquareMinute); + + private readonly AngularAccelerationUnit unit; + + private AngularAccelerationJsonConverter(AngularAccelerationUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var angularAcceleration = (AngularAcceleration)value; + serializer.Serialize(writer, angularAcceleration.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(AngularAcceleration); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return AngularAcceleration.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/AngularJerkJsonConverter.generated.cs b/Gu.Units.Json/AngularJerkJsonConverter.generated.cs new file mode 100644 index 00000000..d7e3d811 --- /dev/null +++ b/Gu.Units.Json/AngularJerkJsonConverter.generated.cs @@ -0,0 +1,44 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class AngularJerkJsonConverter : JsonConverter + { + public static readonly AngularJerkJsonConverter Default = new AngularJerkJsonConverter(AngularJerkUnit.RadiansPerSecondCubed); + public static readonly AngularJerkJsonConverter RadiansPerSecondCubed = new AngularJerkJsonConverter(AngularJerkUnit.RadiansPerSecondCubed); + public static readonly AngularJerkJsonConverter DegreesPerSecondCubed = new AngularJerkJsonConverter(AngularJerkUnit.DegreesPerSecondCubed); + public static readonly AngularJerkJsonConverter RadiansPerHourCubed = new AngularJerkJsonConverter(AngularJerkUnit.RadiansPerHourCubed); + public static readonly AngularJerkJsonConverter DegreesPerHourCubed = new AngularJerkJsonConverter(AngularJerkUnit.DegreesPerHourCubed); + public static readonly AngularJerkJsonConverter RadiansPerMinuteCubed = new AngularJerkJsonConverter(AngularJerkUnit.RadiansPerMinuteCubed); + public static readonly AngularJerkJsonConverter DegreesPerMinuteCubed = new AngularJerkJsonConverter(AngularJerkUnit.DegreesPerMinuteCubed); + + private readonly AngularJerkUnit unit; + + private AngularJerkJsonConverter(AngularJerkUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var angularJerk = (AngularJerk)value; + serializer.Serialize(writer, angularJerk.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(AngularJerk); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return AngularJerk.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/AngularSpeedJsonConverter.generated.cs b/Gu.Units.Json/AngularSpeedJsonConverter.generated.cs new file mode 100644 index 00000000..94a35190 --- /dev/null +++ b/Gu.Units.Json/AngularSpeedJsonConverter.generated.cs @@ -0,0 +1,45 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class AngularSpeedJsonConverter : JsonConverter + { + public static readonly AngularSpeedJsonConverter Default = new AngularSpeedJsonConverter(AngularSpeedUnit.RadiansPerSecond); + public static readonly AngularSpeedJsonConverter RadiansPerSecond = new AngularSpeedJsonConverter(AngularSpeedUnit.RadiansPerSecond); + public static readonly AngularSpeedJsonConverter RevolutionsPerMinute = new AngularSpeedJsonConverter(AngularSpeedUnit.RevolutionsPerMinute); + public static readonly AngularSpeedJsonConverter DegreesPerSecond = new AngularSpeedJsonConverter(AngularSpeedUnit.DegreesPerSecond); + public static readonly AngularSpeedJsonConverter DegreesPerMinute = new AngularSpeedJsonConverter(AngularSpeedUnit.DegreesPerMinute); + public static readonly AngularSpeedJsonConverter RadiansPerMinute = new AngularSpeedJsonConverter(AngularSpeedUnit.RadiansPerMinute); + public static readonly AngularSpeedJsonConverter DegreesPerHour = new AngularSpeedJsonConverter(AngularSpeedUnit.DegreesPerHour); + public static readonly AngularSpeedJsonConverter RadiansPerHour = new AngularSpeedJsonConverter(AngularSpeedUnit.RadiansPerHour); + + private readonly AngularSpeedUnit unit; + + private AngularSpeedJsonConverter(AngularSpeedUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var angularSpeed = (AngularSpeed)value; + serializer.Serialize(writer, angularSpeed.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(AngularSpeed); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return AngularSpeed.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/AreaJsonConverter.generated.cs b/Gu.Units.Json/AreaJsonConverter.generated.cs new file mode 100644 index 00000000..5d4e1721 --- /dev/null +++ b/Gu.Units.Json/AreaJsonConverter.generated.cs @@ -0,0 +1,47 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class AreaJsonConverter : JsonConverter + { + public static readonly AreaJsonConverter Default = new AreaJsonConverter(AreaUnit.SquareMetres); + public static readonly AreaJsonConverter SquareMetres = new AreaJsonConverter(AreaUnit.SquareMetres); + public static readonly AreaJsonConverter SquareMillimetres = new AreaJsonConverter(AreaUnit.SquareMillimetres); + public static readonly AreaJsonConverter SquareCentimetres = new AreaJsonConverter(AreaUnit.SquareCentimetres); + public static readonly AreaJsonConverter SquareDecimetres = new AreaJsonConverter(AreaUnit.SquareDecimetres); + public static readonly AreaJsonConverter SquareKilometres = new AreaJsonConverter(AreaUnit.SquareKilometres); + public static readonly AreaJsonConverter SquareInches = new AreaJsonConverter(AreaUnit.SquareInches); + public static readonly AreaJsonConverter Hectare = new AreaJsonConverter(AreaUnit.Hectare); + public static readonly AreaJsonConverter SquareMile = new AreaJsonConverter(AreaUnit.SquareMile); + public static readonly AreaJsonConverter SquareYard = new AreaJsonConverter(AreaUnit.SquareYard); + + private readonly AreaUnit unit; + + private AreaJsonConverter(AreaUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var area = (Area)value; + serializer.Serialize(writer, area.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Area); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Area.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/CapacitanceJsonConverter.generated.cs b/Gu.Units.Json/CapacitanceJsonConverter.generated.cs new file mode 100644 index 00000000..2463585e --- /dev/null +++ b/Gu.Units.Json/CapacitanceJsonConverter.generated.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class CapacitanceJsonConverter : JsonConverter + { + public static readonly CapacitanceJsonConverter Default = new CapacitanceJsonConverter(CapacitanceUnit.Farads); + public static readonly CapacitanceJsonConverter Farads = new CapacitanceJsonConverter(CapacitanceUnit.Farads); + + private readonly CapacitanceUnit unit; + + private CapacitanceJsonConverter(CapacitanceUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var capacitance = (Capacitance)value; + serializer.Serialize(writer, capacitance.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Capacitance); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Capacitance.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/CurrentJsonConverter.generated.cs b/Gu.Units.Json/CurrentJsonConverter.generated.cs new file mode 100644 index 00000000..43fbe7a1 --- /dev/null +++ b/Gu.Units.Json/CurrentJsonConverter.generated.cs @@ -0,0 +1,43 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class CurrentJsonConverter : JsonConverter + { + public static readonly CurrentJsonConverter Default = new CurrentJsonConverter(CurrentUnit.Amperes); + public static readonly CurrentJsonConverter Amperes = new CurrentJsonConverter(CurrentUnit.Amperes); + public static readonly CurrentJsonConverter Milliamperes = new CurrentJsonConverter(CurrentUnit.Milliamperes); + public static readonly CurrentJsonConverter Kiloamperes = new CurrentJsonConverter(CurrentUnit.Kiloamperes); + public static readonly CurrentJsonConverter Megaamperes = new CurrentJsonConverter(CurrentUnit.Megaamperes); + public static readonly CurrentJsonConverter Microamperes = new CurrentJsonConverter(CurrentUnit.Microamperes); + + private readonly CurrentUnit unit; + + private CurrentJsonConverter(CurrentUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var current = (Current)value; + serializer.Serialize(writer, current.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Current); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Current.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/DensityJsonConverter.generated.cs b/Gu.Units.Json/DensityJsonConverter.generated.cs new file mode 100644 index 00000000..8b74d6cb --- /dev/null +++ b/Gu.Units.Json/DensityJsonConverter.generated.cs @@ -0,0 +1,41 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class DensityJsonConverter : JsonConverter + { + public static readonly DensityJsonConverter Default = new DensityJsonConverter(DensityUnit.KilogramsPerCubicMetre); + public static readonly DensityJsonConverter KilogramsPerCubicMetre = new DensityJsonConverter(DensityUnit.KilogramsPerCubicMetre); + public static readonly DensityJsonConverter GramsPerCubicMillimetre = new DensityJsonConverter(DensityUnit.GramsPerCubicMillimetre); + public static readonly DensityJsonConverter GramsPerCubicCentimetre = new DensityJsonConverter(DensityUnit.GramsPerCubicCentimetre); + + private readonly DensityUnit unit; + + private DensityJsonConverter(DensityUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var density = (Density)value; + serializer.Serialize(writer, density.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Density); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Density.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/ElectricChargeJsonConverter.generated.cs b/Gu.Units.Json/ElectricChargeJsonConverter.generated.cs new file mode 100644 index 00000000..537dea09 --- /dev/null +++ b/Gu.Units.Json/ElectricChargeJsonConverter.generated.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class ElectricChargeJsonConverter : JsonConverter + { + public static readonly ElectricChargeJsonConverter Default = new ElectricChargeJsonConverter(ElectricChargeUnit.Coulombs); + public static readonly ElectricChargeJsonConverter Coulombs = new ElectricChargeJsonConverter(ElectricChargeUnit.Coulombs); + + private readonly ElectricChargeUnit unit; + + private ElectricChargeJsonConverter(ElectricChargeUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var electricCharge = (ElectricCharge)value; + serializer.Serialize(writer, electricCharge.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ElectricCharge); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return ElectricCharge.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/EnergyJsonConverter.generated.cs b/Gu.Units.Json/EnergyJsonConverter.generated.cs new file mode 100644 index 00000000..cb379172 --- /dev/null +++ b/Gu.Units.Json/EnergyJsonConverter.generated.cs @@ -0,0 +1,46 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class EnergyJsonConverter : JsonConverter + { + public static readonly EnergyJsonConverter Default = new EnergyJsonConverter(EnergyUnit.Joules); + public static readonly EnergyJsonConverter Joules = new EnergyJsonConverter(EnergyUnit.Joules); + public static readonly EnergyJsonConverter Nanojoules = new EnergyJsonConverter(EnergyUnit.Nanojoules); + public static readonly EnergyJsonConverter Microjoules = new EnergyJsonConverter(EnergyUnit.Microjoules); + public static readonly EnergyJsonConverter Millijoules = new EnergyJsonConverter(EnergyUnit.Millijoules); + public static readonly EnergyJsonConverter Kilojoules = new EnergyJsonConverter(EnergyUnit.Kilojoules); + public static readonly EnergyJsonConverter Megajoules = new EnergyJsonConverter(EnergyUnit.Megajoules); + public static readonly EnergyJsonConverter Gigajoules = new EnergyJsonConverter(EnergyUnit.Gigajoules); + public static readonly EnergyJsonConverter KilowattHours = new EnergyJsonConverter(EnergyUnit.KilowattHours); + + private readonly EnergyUnit unit; + + private EnergyJsonConverter(EnergyUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var energy = (Energy)value; + serializer.Serialize(writer, energy.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Energy); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Energy.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/FlexibilityJsonConverter.generated.cs b/Gu.Units.Json/FlexibilityJsonConverter.generated.cs new file mode 100644 index 00000000..2f5b06c3 --- /dev/null +++ b/Gu.Units.Json/FlexibilityJsonConverter.generated.cs @@ -0,0 +1,42 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class FlexibilityJsonConverter : JsonConverter + { + public static readonly FlexibilityJsonConverter Default = new FlexibilityJsonConverter(FlexibilityUnit.MetresPerNewton); + public static readonly FlexibilityJsonConverter MetresPerNewton = new FlexibilityJsonConverter(FlexibilityUnit.MetresPerNewton); + public static readonly FlexibilityJsonConverter MillimetresPerNewton = new FlexibilityJsonConverter(FlexibilityUnit.MillimetresPerNewton); + public static readonly FlexibilityJsonConverter MillimetresPerKilonewton = new FlexibilityJsonConverter(FlexibilityUnit.MillimetresPerKilonewton); + public static readonly FlexibilityJsonConverter MicrometresPerKilonewton = new FlexibilityJsonConverter(FlexibilityUnit.MicrometresPerKilonewton); + + private readonly FlexibilityUnit unit; + + private FlexibilityJsonConverter(FlexibilityUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var flexibility = (Flexibility)value; + serializer.Serialize(writer, flexibility.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Flexibility); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Flexibility.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/ForceJsonConverter.generated.cs b/Gu.Units.Json/ForceJsonConverter.generated.cs new file mode 100644 index 00000000..38c401f9 --- /dev/null +++ b/Gu.Units.Json/ForceJsonConverter.generated.cs @@ -0,0 +1,45 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class ForceJsonConverter : JsonConverter + { + public static readonly ForceJsonConverter Default = new ForceJsonConverter(ForceUnit.Newtons); + public static readonly ForceJsonConverter Newtons = new ForceJsonConverter(ForceUnit.Newtons); + public static readonly ForceJsonConverter Nanonewtons = new ForceJsonConverter(ForceUnit.Nanonewtons); + public static readonly ForceJsonConverter Micronewtons = new ForceJsonConverter(ForceUnit.Micronewtons); + public static readonly ForceJsonConverter Millinewtons = new ForceJsonConverter(ForceUnit.Millinewtons); + public static readonly ForceJsonConverter Kilonewtons = new ForceJsonConverter(ForceUnit.Kilonewtons); + public static readonly ForceJsonConverter Meganewtons = new ForceJsonConverter(ForceUnit.Meganewtons); + public static readonly ForceJsonConverter Giganewtons = new ForceJsonConverter(ForceUnit.Giganewtons); + + private readonly ForceUnit unit; + + private ForceJsonConverter(ForceUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var force = (Force)value; + serializer.Serialize(writer, force.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Force); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Force.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/ForcePerUnitlessJsonConverter.generated.cs b/Gu.Units.Json/ForcePerUnitlessJsonConverter.generated.cs new file mode 100644 index 00000000..931fda19 --- /dev/null +++ b/Gu.Units.Json/ForcePerUnitlessJsonConverter.generated.cs @@ -0,0 +1,43 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class ForcePerUnitlessJsonConverter : JsonConverter + { + public static readonly ForcePerUnitlessJsonConverter Default = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.NewtonsPerUnitless); + public static readonly ForcePerUnitlessJsonConverter NewtonsPerUnitless = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.NewtonsPerUnitless); + public static readonly ForcePerUnitlessJsonConverter NewtonsPerPercent = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.NewtonsPerPercent); + public static readonly ForcePerUnitlessJsonConverter KilonewtonsPerPercent = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.KilonewtonsPerPercent); + public static readonly ForcePerUnitlessJsonConverter MeganewtonsPerPercent = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.MeganewtonsPerPercent); + public static readonly ForcePerUnitlessJsonConverter GiganewtonsPerPercent = new ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit.GiganewtonsPerPercent); + + private readonly ForcePerUnitlessUnit unit; + + private ForcePerUnitlessJsonConverter(ForcePerUnitlessUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var forcePerUnitless = (ForcePerUnitless)value; + serializer.Serialize(writer, forcePerUnitless.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(ForcePerUnitless); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return ForcePerUnitless.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/FrequencyJsonConverter.generated.cs b/Gu.Units.Json/FrequencyJsonConverter.generated.cs new file mode 100644 index 00000000..a2eb03d8 --- /dev/null +++ b/Gu.Units.Json/FrequencyJsonConverter.generated.cs @@ -0,0 +1,43 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class FrequencyJsonConverter : JsonConverter + { + public static readonly FrequencyJsonConverter Default = new FrequencyJsonConverter(FrequencyUnit.Hertz); + public static readonly FrequencyJsonConverter Hertz = new FrequencyJsonConverter(FrequencyUnit.Hertz); + public static readonly FrequencyJsonConverter Millihertz = new FrequencyJsonConverter(FrequencyUnit.Millihertz); + public static readonly FrequencyJsonConverter Kilohertz = new FrequencyJsonConverter(FrequencyUnit.Kilohertz); + public static readonly FrequencyJsonConverter Megahertz = new FrequencyJsonConverter(FrequencyUnit.Megahertz); + public static readonly FrequencyJsonConverter Gigahertz = new FrequencyJsonConverter(FrequencyUnit.Gigahertz); + + private readonly FrequencyUnit unit; + + private FrequencyJsonConverter(FrequencyUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var frequency = (Frequency)value; + serializer.Serialize(writer, frequency.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Frequency); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Frequency.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/Gu.Units.Json.csproj b/Gu.Units.Json/Gu.Units.Json.csproj new file mode 100644 index 00000000..e87e7e14 --- /dev/null +++ b/Gu.Units.Json/Gu.Units.Json.csproj @@ -0,0 +1,218 @@ + + + + + Debug + AnyCPU + {C794758C-AFE9-489E-9525-81DF7AC98193} + Library + Properties + Gu.Units.Json + Gu.Units.Json + v4.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + QuantityJsonConverters.txt4 + + + + + {f1f8b138-becc-4475-a7ab-b3019338bc7b} + Gu.Units + + + + + + + + + + + + + ..\packages\Newtonsoft.Json\lib\net35\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net20\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net40\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\portable-net45+wp80+win8+wpa81+dnxcore50\Newtonsoft.Json.dll + True + True + + + + + + + ..\packages\Newtonsoft.Json\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll + True + True + + + + + \ No newline at end of file diff --git a/Gu.Units.Json/InductanceJsonConverter.generated.cs b/Gu.Units.Json/InductanceJsonConverter.generated.cs new file mode 100644 index 00000000..1bae52df --- /dev/null +++ b/Gu.Units.Json/InductanceJsonConverter.generated.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class InductanceJsonConverter : JsonConverter + { + public static readonly InductanceJsonConverter Default = new InductanceJsonConverter(InductanceUnit.Henrys); + public static readonly InductanceJsonConverter Henrys = new InductanceJsonConverter(InductanceUnit.Henrys); + + private readonly InductanceUnit unit; + + private InductanceJsonConverter(InductanceUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var inductance = (Inductance)value; + serializer.Serialize(writer, inductance.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Inductance); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Inductance.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/JerkJsonConverter.generated.cs b/Gu.Units.Json/JerkJsonConverter.generated.cs new file mode 100644 index 00000000..2061084a --- /dev/null +++ b/Gu.Units.Json/JerkJsonConverter.generated.cs @@ -0,0 +1,49 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class JerkJsonConverter : JsonConverter + { + public static readonly JerkJsonConverter Default = new JerkJsonConverter(JerkUnit.MetresPerSecondCubed); + public static readonly JerkJsonConverter MetresPerSecondCubed = new JerkJsonConverter(JerkUnit.MetresPerSecondCubed); + public static readonly JerkJsonConverter MillimetresPerSecondCubed = new JerkJsonConverter(JerkUnit.MillimetresPerSecondCubed); + public static readonly JerkJsonConverter MillimetresPerHourCubed = new JerkJsonConverter(JerkUnit.MillimetresPerHourCubed); + public static readonly JerkJsonConverter MillimetresPerMinuteCubed = new JerkJsonConverter(JerkUnit.MillimetresPerMinuteCubed); + public static readonly JerkJsonConverter MetresPerHourCubed = new JerkJsonConverter(JerkUnit.MetresPerHourCubed); + public static readonly JerkJsonConverter MetresPerMinuteCubed = new JerkJsonConverter(JerkUnit.MetresPerMinuteCubed); + public static readonly JerkJsonConverter NanometresPerHourCubed = new JerkJsonConverter(JerkUnit.NanometresPerHourCubed); + public static readonly JerkJsonConverter NanometresPerMinuteCubed = new JerkJsonConverter(JerkUnit.NanometresPerMinuteCubed); + public static readonly JerkJsonConverter CentimetresPerSecondCubed = new JerkJsonConverter(JerkUnit.CentimetresPerSecondCubed); + public static readonly JerkJsonConverter CentimetresPerHourCubed = new JerkJsonConverter(JerkUnit.CentimetresPerHourCubed); + public static readonly JerkJsonConverter CentimetresPerMinuteCubed = new JerkJsonConverter(JerkUnit.CentimetresPerMinuteCubed); + + private readonly JerkUnit unit; + + private JerkJsonConverter(JerkUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var jerk = (Jerk)value; + serializer.Serialize(writer, jerk.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Jerk); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Jerk.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/LengthJsonConverter.generated.cs b/Gu.Units.Json/LengthJsonConverter.generated.cs new file mode 100644 index 00000000..debd2c15 --- /dev/null +++ b/Gu.Units.Json/LengthJsonConverter.generated.cs @@ -0,0 +1,49 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class LengthJsonConverter : JsonConverter + { + public static readonly LengthJsonConverter Default = new LengthJsonConverter(LengthUnit.Metres); + public static readonly LengthJsonConverter Metres = new LengthJsonConverter(LengthUnit.Metres); + public static readonly LengthJsonConverter Nanometres = new LengthJsonConverter(LengthUnit.Nanometres); + public static readonly LengthJsonConverter Micrometres = new LengthJsonConverter(LengthUnit.Micrometres); + public static readonly LengthJsonConverter Millimetres = new LengthJsonConverter(LengthUnit.Millimetres); + public static readonly LengthJsonConverter Centimetres = new LengthJsonConverter(LengthUnit.Centimetres); + public static readonly LengthJsonConverter Decimetres = new LengthJsonConverter(LengthUnit.Decimetres); + public static readonly LengthJsonConverter Kilometres = new LengthJsonConverter(LengthUnit.Kilometres); + public static readonly LengthJsonConverter Inches = new LengthJsonConverter(LengthUnit.Inches); + public static readonly LengthJsonConverter Mile = new LengthJsonConverter(LengthUnit.Mile); + public static readonly LengthJsonConverter Yard = new LengthJsonConverter(LengthUnit.Yard); + public static readonly LengthJsonConverter NauticalMile = new LengthJsonConverter(LengthUnit.NauticalMile); + + private readonly LengthUnit unit; + + private LengthJsonConverter(LengthUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var length = (Length)value; + serializer.Serialize(writer, length.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Length); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Length.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/LengthPerUnitlessJsonConverter.generated.cs b/Gu.Units.Json/LengthPerUnitlessJsonConverter.generated.cs new file mode 100644 index 00000000..b79391b2 --- /dev/null +++ b/Gu.Units.Json/LengthPerUnitlessJsonConverter.generated.cs @@ -0,0 +1,43 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class LengthPerUnitlessJsonConverter : JsonConverter + { + public static readonly LengthPerUnitlessJsonConverter Default = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MetresPerUnitless); + public static readonly LengthPerUnitlessJsonConverter MetresPerUnitless = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MetresPerUnitless); + public static readonly LengthPerUnitlessJsonConverter MillimetresPerPercent = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MillimetresPerPercent); + public static readonly LengthPerUnitlessJsonConverter MicrometresPerPercent = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MicrometresPerPercent); + public static readonly LengthPerUnitlessJsonConverter NanometresPerPercent = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.NanometresPerPercent); + public static readonly LengthPerUnitlessJsonConverter MetresPerPercent = new LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit.MetresPerPercent); + + private readonly LengthPerUnitlessUnit unit; + + private LengthPerUnitlessJsonConverter(LengthPerUnitlessUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var lengthPerUnitless = (LengthPerUnitless)value; + serializer.Serialize(writer, lengthPerUnitless.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(LengthPerUnitless); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return LengthPerUnitless.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/LuminousIntensityJsonConverter.generated.cs b/Gu.Units.Json/LuminousIntensityJsonConverter.generated.cs new file mode 100644 index 00000000..79df1df8 --- /dev/null +++ b/Gu.Units.Json/LuminousIntensityJsonConverter.generated.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class LuminousIntensityJsonConverter : JsonConverter + { + public static readonly LuminousIntensityJsonConverter Default = new LuminousIntensityJsonConverter(LuminousIntensityUnit.Candelas); + public static readonly LuminousIntensityJsonConverter Candelas = new LuminousIntensityJsonConverter(LuminousIntensityUnit.Candelas); + + private readonly LuminousIntensityUnit unit; + + private LuminousIntensityJsonConverter(LuminousIntensityUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var luminousIntensity = (LuminousIntensity)value; + serializer.Serialize(writer, luminousIntensity.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(LuminousIntensity); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return LuminousIntensity.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/MassJsonConverter.generated.cs b/Gu.Units.Json/MassJsonConverter.generated.cs new file mode 100644 index 00000000..7cd73e0d --- /dev/null +++ b/Gu.Units.Json/MassJsonConverter.generated.cs @@ -0,0 +1,42 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class MassJsonConverter : JsonConverter + { + public static readonly MassJsonConverter Default = new MassJsonConverter(MassUnit.Kilograms); + public static readonly MassJsonConverter Kilograms = new MassJsonConverter(MassUnit.Kilograms); + public static readonly MassJsonConverter Grams = new MassJsonConverter(MassUnit.Grams); + public static readonly MassJsonConverter Milligrams = new MassJsonConverter(MassUnit.Milligrams); + public static readonly MassJsonConverter Micrograms = new MassJsonConverter(MassUnit.Micrograms); + + private readonly MassUnit unit; + + private MassJsonConverter(MassUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var mass = (Mass)value; + serializer.Serialize(writer, mass.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Mass); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Mass.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/PowerJsonConverter.generated.cs b/Gu.Units.Json/PowerJsonConverter.generated.cs new file mode 100644 index 00000000..1cf8c264 --- /dev/null +++ b/Gu.Units.Json/PowerJsonConverter.generated.cs @@ -0,0 +1,45 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class PowerJsonConverter : JsonConverter + { + public static readonly PowerJsonConverter Default = new PowerJsonConverter(PowerUnit.Watts); + public static readonly PowerJsonConverter Watts = new PowerJsonConverter(PowerUnit.Watts); + public static readonly PowerJsonConverter Nanowatts = new PowerJsonConverter(PowerUnit.Nanowatts); + public static readonly PowerJsonConverter Microwatts = new PowerJsonConverter(PowerUnit.Microwatts); + public static readonly PowerJsonConverter Milliwatts = new PowerJsonConverter(PowerUnit.Milliwatts); + public static readonly PowerJsonConverter Kilowatts = new PowerJsonConverter(PowerUnit.Kilowatts); + public static readonly PowerJsonConverter Megawatts = new PowerJsonConverter(PowerUnit.Megawatts); + public static readonly PowerJsonConverter Gigawatts = new PowerJsonConverter(PowerUnit.Gigawatts); + + private readonly PowerUnit unit; + + private PowerJsonConverter(PowerUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var power = (Power)value; + serializer.Serialize(writer, power.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Power); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Power.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/PressureJsonConverter.generated.cs b/Gu.Units.Json/PressureJsonConverter.generated.cs new file mode 100644 index 00000000..dec35977 --- /dev/null +++ b/Gu.Units.Json/PressureJsonConverter.generated.cs @@ -0,0 +1,50 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class PressureJsonConverter : JsonConverter + { + public static readonly PressureJsonConverter Default = new PressureJsonConverter(PressureUnit.Pascals); + public static readonly PressureJsonConverter Pascals = new PressureJsonConverter(PressureUnit.Pascals); + public static readonly PressureJsonConverter Nanopascals = new PressureJsonConverter(PressureUnit.Nanopascals); + public static readonly PressureJsonConverter Micropascals = new PressureJsonConverter(PressureUnit.Micropascals); + public static readonly PressureJsonConverter Millipascals = new PressureJsonConverter(PressureUnit.Millipascals); + public static readonly PressureJsonConverter Kilopascals = new PressureJsonConverter(PressureUnit.Kilopascals); + public static readonly PressureJsonConverter Megapascals = new PressureJsonConverter(PressureUnit.Megapascals); + public static readonly PressureJsonConverter Gigapascals = new PressureJsonConverter(PressureUnit.Gigapascals); + public static readonly PressureJsonConverter Bars = new PressureJsonConverter(PressureUnit.Bars); + public static readonly PressureJsonConverter Millibars = new PressureJsonConverter(PressureUnit.Millibars); + public static readonly PressureJsonConverter NewtonsPerSquareMillimetre = new PressureJsonConverter(PressureUnit.NewtonsPerSquareMillimetre); + public static readonly PressureJsonConverter KilonewtonsPerSquareMillimetre = new PressureJsonConverter(PressureUnit.KilonewtonsPerSquareMillimetre); + public static readonly PressureJsonConverter NewtonsPerSquareMetre = new PressureJsonConverter(PressureUnit.NewtonsPerSquareMetre); + + private readonly PressureUnit unit; + + private PressureJsonConverter(PressureUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var pressure = (Pressure)value; + serializer.Serialize(writer, pressure.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Pressure); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Pressure.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/Properties/AssemblyInfo.cs b/Gu.Units.Json/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..81278408 --- /dev/null +++ b/Gu.Units.Json/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Gu.Units.Json")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Gu.Units.Json")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c794758c-afe9-489e-9525-81df7ac98193")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Gu.Units/QuantityGenerator.txt4 b/Gu.Units.Json/QuantityJsonConverters.txt4 similarity index 100% rename from Gu.Units/QuantityGenerator.txt4 rename to Gu.Units.Json/QuantityJsonConverters.txt4 diff --git a/Gu.Units.Json/ResistanceJsonConverter.generated.cs b/Gu.Units.Json/ResistanceJsonConverter.generated.cs new file mode 100644 index 00000000..63847214 --- /dev/null +++ b/Gu.Units.Json/ResistanceJsonConverter.generated.cs @@ -0,0 +1,43 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class ResistanceJsonConverter : JsonConverter + { + public static readonly ResistanceJsonConverter Default = new ResistanceJsonConverter(ResistanceUnit.Ohm); + public static readonly ResistanceJsonConverter Ohm = new ResistanceJsonConverter(ResistanceUnit.Ohm); + public static readonly ResistanceJsonConverter Microohm = new ResistanceJsonConverter(ResistanceUnit.Microohm); + public static readonly ResistanceJsonConverter Milliohm = new ResistanceJsonConverter(ResistanceUnit.Milliohm); + public static readonly ResistanceJsonConverter Kiloohm = new ResistanceJsonConverter(ResistanceUnit.Kiloohm); + public static readonly ResistanceJsonConverter Megaohm = new ResistanceJsonConverter(ResistanceUnit.Megaohm); + + private readonly ResistanceUnit unit; + + private ResistanceJsonConverter(ResistanceUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var resistance = (Resistance)value; + serializer.Serialize(writer, resistance.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Resistance); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Resistance.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/SpecificEnergyJsonConverter.generated.cs b/Gu.Units.Json/SpecificEnergyJsonConverter.generated.cs new file mode 100644 index 00000000..6d09ba05 --- /dev/null +++ b/Gu.Units.Json/SpecificEnergyJsonConverter.generated.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class SpecificEnergyJsonConverter : JsonConverter + { + public static readonly SpecificEnergyJsonConverter Default = new SpecificEnergyJsonConverter(SpecificEnergyUnit.JoulesPerKilogram); + public static readonly SpecificEnergyJsonConverter JoulesPerKilogram = new SpecificEnergyJsonConverter(SpecificEnergyUnit.JoulesPerKilogram); + + private readonly SpecificEnergyUnit unit; + + private SpecificEnergyJsonConverter(SpecificEnergyUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var specificEnergy = (SpecificEnergy)value; + serializer.Serialize(writer, specificEnergy.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(SpecificEnergy); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return SpecificEnergy.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/SpeedJsonConverter.generated.cs b/Gu.Units.Json/SpeedJsonConverter.generated.cs new file mode 100644 index 00000000..0d6f0c84 --- /dev/null +++ b/Gu.Units.Json/SpeedJsonConverter.generated.cs @@ -0,0 +1,48 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class SpeedJsonConverter : JsonConverter + { + public static readonly SpeedJsonConverter Default = new SpeedJsonConverter(SpeedUnit.MetresPerSecond); + public static readonly SpeedJsonConverter MetresPerSecond = new SpeedJsonConverter(SpeedUnit.MetresPerSecond); + public static readonly SpeedJsonConverter MillimetresPerSecond = new SpeedJsonConverter(SpeedUnit.MillimetresPerSecond); + public static readonly SpeedJsonConverter CentimetresPerSecond = new SpeedJsonConverter(SpeedUnit.CentimetresPerSecond); + public static readonly SpeedJsonConverter KilometresPerHour = new SpeedJsonConverter(SpeedUnit.KilometresPerHour); + public static readonly SpeedJsonConverter CentimetresPerMinute = new SpeedJsonConverter(SpeedUnit.CentimetresPerMinute); + public static readonly SpeedJsonConverter MetresPerMinute = new SpeedJsonConverter(SpeedUnit.MetresPerMinute); + public static readonly SpeedJsonConverter MetresPerHour = new SpeedJsonConverter(SpeedUnit.MetresPerHour); + public static readonly SpeedJsonConverter MillimetresPerHour = new SpeedJsonConverter(SpeedUnit.MillimetresPerHour); + public static readonly SpeedJsonConverter CentimetresPerHour = new SpeedJsonConverter(SpeedUnit.CentimetresPerHour); + public static readonly SpeedJsonConverter MillimetresPerMinute = new SpeedJsonConverter(SpeedUnit.MillimetresPerMinute); + + private readonly SpeedUnit unit; + + private SpeedJsonConverter(SpeedUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var speed = (Speed)value; + serializer.Serialize(writer, speed.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Speed); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Speed.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/StiffnessJsonConverter.generated.cs b/Gu.Units.Json/StiffnessJsonConverter.generated.cs new file mode 100644 index 00000000..7691ce6c --- /dev/null +++ b/Gu.Units.Json/StiffnessJsonConverter.generated.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class StiffnessJsonConverter : JsonConverter + { + public static readonly StiffnessJsonConverter Default = new StiffnessJsonConverter(StiffnessUnit.NewtonsPerMetre); + public static readonly StiffnessJsonConverter NewtonsPerMetre = new StiffnessJsonConverter(StiffnessUnit.NewtonsPerMetre); + + private readonly StiffnessUnit unit; + + private StiffnessJsonConverter(StiffnessUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var stiffness = (Stiffness)value; + serializer.Serialize(writer, stiffness.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Stiffness); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Stiffness.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/TemperatureJsonConverter.generated.cs b/Gu.Units.Json/TemperatureJsonConverter.generated.cs new file mode 100644 index 00000000..fc44c4b3 --- /dev/null +++ b/Gu.Units.Json/TemperatureJsonConverter.generated.cs @@ -0,0 +1,41 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class TemperatureJsonConverter : JsonConverter + { + public static readonly TemperatureJsonConverter Default = new TemperatureJsonConverter(TemperatureUnit.Kelvin); + public static readonly TemperatureJsonConverter Kelvin = new TemperatureJsonConverter(TemperatureUnit.Kelvin); + public static readonly TemperatureJsonConverter Celsius = new TemperatureJsonConverter(TemperatureUnit.Celsius); + public static readonly TemperatureJsonConverter Fahrenheit = new TemperatureJsonConverter(TemperatureUnit.Fahrenheit); + + private readonly TemperatureUnit unit; + + private TemperatureJsonConverter(TemperatureUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var temperature = (Temperature)value; + serializer.Serialize(writer, temperature.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Temperature); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Temperature.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/TimeJsonConverter.generated.cs b/Gu.Units.Json/TimeJsonConverter.generated.cs new file mode 100644 index 00000000..bda6f9a7 --- /dev/null +++ b/Gu.Units.Json/TimeJsonConverter.generated.cs @@ -0,0 +1,44 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class TimeJsonConverter : JsonConverter + { + public static readonly TimeJsonConverter Default = new TimeJsonConverter(TimeUnit.Seconds); + public static readonly TimeJsonConverter Seconds = new TimeJsonConverter(TimeUnit.Seconds); + public static readonly TimeJsonConverter Nanoseconds = new TimeJsonConverter(TimeUnit.Nanoseconds); + public static readonly TimeJsonConverter Microseconds = new TimeJsonConverter(TimeUnit.Microseconds); + public static readonly TimeJsonConverter Milliseconds = new TimeJsonConverter(TimeUnit.Milliseconds); + public static readonly TimeJsonConverter Hours = new TimeJsonConverter(TimeUnit.Hours); + public static readonly TimeJsonConverter Minutes = new TimeJsonConverter(TimeUnit.Minutes); + + private readonly TimeUnit unit; + + private TimeJsonConverter(TimeUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var time = (Time)value; + serializer.Serialize(writer, time.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Time); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Time.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/TorqueJsonConverter.generated.cs b/Gu.Units.Json/TorqueJsonConverter.generated.cs new file mode 100644 index 00000000..d6304c28 --- /dev/null +++ b/Gu.Units.Json/TorqueJsonConverter.generated.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class TorqueJsonConverter : JsonConverter + { + public static readonly TorqueJsonConverter Default = new TorqueJsonConverter(TorqueUnit.NewtonMetres); + public static readonly TorqueJsonConverter NewtonMetres = new TorqueJsonConverter(TorqueUnit.NewtonMetres); + + private readonly TorqueUnit unit; + + private TorqueJsonConverter(TorqueUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var torque = (Torque)value; + serializer.Serialize(writer, torque.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Torque); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Torque.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/UnitlessJsonConverter.generated.cs b/Gu.Units.Json/UnitlessJsonConverter.generated.cs new file mode 100644 index 00000000..b47aaa2e --- /dev/null +++ b/Gu.Units.Json/UnitlessJsonConverter.generated.cs @@ -0,0 +1,42 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class UnitlessJsonConverter : JsonConverter + { + public static readonly UnitlessJsonConverter Default = new UnitlessJsonConverter(UnitlessUnit.Scalar); + public static readonly UnitlessJsonConverter Scalar = new UnitlessJsonConverter(UnitlessUnit.Scalar); + public static readonly UnitlessJsonConverter PartsPerMillion = new UnitlessJsonConverter(UnitlessUnit.PartsPerMillion); + public static readonly UnitlessJsonConverter Promilles = new UnitlessJsonConverter(UnitlessUnit.Promilles); + public static readonly UnitlessJsonConverter Percents = new UnitlessJsonConverter(UnitlessUnit.Percents); + + private readonly UnitlessUnit unit; + + private UnitlessJsonConverter(UnitlessUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var unitless = (Unitless)value; + serializer.Serialize(writer, unitless.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Unitless); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Unitless.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/VoltageJsonConverter.generated.cs b/Gu.Units.Json/VoltageJsonConverter.generated.cs new file mode 100644 index 00000000..7c2f2192 --- /dev/null +++ b/Gu.Units.Json/VoltageJsonConverter.generated.cs @@ -0,0 +1,43 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class VoltageJsonConverter : JsonConverter + { + public static readonly VoltageJsonConverter Default = new VoltageJsonConverter(VoltageUnit.Volts); + public static readonly VoltageJsonConverter Volts = new VoltageJsonConverter(VoltageUnit.Volts); + public static readonly VoltageJsonConverter Millivolts = new VoltageJsonConverter(VoltageUnit.Millivolts); + public static readonly VoltageJsonConverter Kilovolts = new VoltageJsonConverter(VoltageUnit.Kilovolts); + public static readonly VoltageJsonConverter Megavolts = new VoltageJsonConverter(VoltageUnit.Megavolts); + public static readonly VoltageJsonConverter Microvolts = new VoltageJsonConverter(VoltageUnit.Microvolts); + + private readonly VoltageUnit unit; + + private VoltageJsonConverter(VoltageUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var voltage = (Voltage)value; + serializer.Serialize(writer, voltage.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Voltage); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Voltage.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/VolumeJsonConverter.generated.cs b/Gu.Units.Json/VolumeJsonConverter.generated.cs new file mode 100644 index 00000000..7e58add0 --- /dev/null +++ b/Gu.Units.Json/VolumeJsonConverter.generated.cs @@ -0,0 +1,43 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class VolumeJsonConverter : JsonConverter + { + public static readonly VolumeJsonConverter Default = new VolumeJsonConverter(VolumeUnit.CubicMetres); + public static readonly VolumeJsonConverter CubicMetres = new VolumeJsonConverter(VolumeUnit.CubicMetres); + public static readonly VolumeJsonConverter Litres = new VolumeJsonConverter(VolumeUnit.Litres); + public static readonly VolumeJsonConverter CubicCentimetres = new VolumeJsonConverter(VolumeUnit.CubicCentimetres); + public static readonly VolumeJsonConverter CubicMillimetres = new VolumeJsonConverter(VolumeUnit.CubicMillimetres); + public static readonly VolumeJsonConverter CubicInches = new VolumeJsonConverter(VolumeUnit.CubicInches); + + private readonly VolumeUnit unit; + + private VolumeJsonConverter(VolumeUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var volume = (Volume)value; + serializer.Serialize(writer, volume.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(Volume); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return Volume.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/VolumetricFlowJsonConverter.generated.cs b/Gu.Units.Json/VolumetricFlowJsonConverter.generated.cs new file mode 100644 index 00000000..0323e748 --- /dev/null +++ b/Gu.Units.Json/VolumetricFlowJsonConverter.generated.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Json +{ + using System; + using Newtonsoft.Json; + + /// + /// for the quantity . + /// + [Serializable] + public class VolumetricFlowJsonConverter : JsonConverter + { + public static readonly VolumetricFlowJsonConverter Default = new VolumetricFlowJsonConverter(VolumetricFlowUnit.CubicMetresPerSecond); + public static readonly VolumetricFlowJsonConverter CubicMetresPerSecond = new VolumetricFlowJsonConverter(VolumetricFlowUnit.CubicMetresPerSecond); + + private readonly VolumetricFlowUnit unit; + + private VolumetricFlowJsonConverter(VolumetricFlowUnit unit) + { + this.unit = unit; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var volumetricFlow = (VolumetricFlow)value; + serializer.Serialize(writer, volumetricFlow.ToString(this.unit, writer.Culture)); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(VolumetricFlow); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var stringValue = reader.Value as string; + return VolumetricFlow.Parse(stringValue, reader.Culture); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Json/paket.references b/Gu.Units.Json/paket.references new file mode 100644 index 00000000..1063d003 --- /dev/null +++ b/Gu.Units.Json/paket.references @@ -0,0 +1 @@ +Newtonsoft.Json \ No newline at end of file diff --git a/Gu.Units.Json/paket.template b/Gu.Units.Json/paket.template new file mode 100644 index 00000000..e9ef7135 --- /dev/null +++ b/Gu.Units.Json/paket.template @@ -0,0 +1,5 @@ +type project +title Gu.Units.Json +tags unit units quantity quantities unitsofmeasure +projectUrl https://github.com/JohanLarsson/Gu.Units +licenseUrl http://opensource.org/licenses/MIT diff --git a/Gu.Units.Tests/BenchMarks/FormattingBenchmarks.cs b/Gu.Units.Tests/BenchMarks/FormattingBenchmarks.cs new file mode 100644 index 00000000..e335202c --- /dev/null +++ b/Gu.Units.Tests/BenchMarks/FormattingBenchmarks.cs @@ -0,0 +1,69 @@ +namespace Gu.Units.Tests +{ + using System; + using System.Diagnostics; + using Internals.Parsing; + using NUnit.Framework; + + // run benchmarks in release build + [Explicit(Benchmarks.LongRunning)] + public class FormattingBenchmarks + { + // 2015-11-28| speed.ToString("F1 m/s") 1 000 000 times took: 995 ms + // 2015-11-28| $"{ speed.metresPerSecond:F1} {SpeedUnit.MetresPerSecond}" 1 000 000 times took: 742 ms + [Test] + public void Benchmark() + { + // this is not a fair comparison as .ToString parses the arguments. Still interesting as a base line. + var speed = Speed.FromMetresPerSecond(1.2); + var toString = speed.ToString("F1 m/s"); + toString = speed.ToString("F0", "s⁻¹⋅mm"); + toString = speed.ToString("F2 m⋅s⁻¹"); + var string_Format = $"{speed.metresPerSecond:F1} {SpeedUnit.MetresPerSecond}"; + // end warmup + + var sw = Stopwatch.StartNew(); + var n = 1000000; + for (int i = 0; i < n / 3; i++) + { + toString = speed.ToString("F1 m/s"); + toString = speed.ToString("F0", "s⁻¹⋅mm"); + toString = speed.ToString("F2 m⋅s⁻¹"); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| speed.ToString(\"F1 m/s\") {n:N0} times took: {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + for (int i = 0; i < n / 3; i++) + { + string_Format = $"{speed.metresPerSecond:F1} {SpeedUnit.MetresPerSecond}"; + string_Format = $"{speed.metresPerSecond:F0} s⁻¹⋅mm"; + string_Format = $"{speed.metresPerSecond:F2} m⋅s⁻¹"; + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| $\"{{ speed.metresPerSecond:F1}} {{SpeedUnit.MetresPerSecond}}\" {n:N0} times took: {sw.ElapsedMilliseconds} ms"); + } + + [Test] + public void ForProfiling() + { + // this is not a fair comparison as .ToString parses the arguments. Still interesting as a base line. + var speed = Speed.FromMetresPerSecond(1.2); + var toString = speed.ToString("F1 m/s"); + toString = speed.ToString("F0", "s⁻¹⋅mm"); + toString = speed.ToString("F2 m⋅s⁻¹"); + var string_Format = $"{speed.metresPerSecond:F1} {SpeedUnit.MetresPerSecond}"; + // end warmup + + var n = 1000000; + for (int i = 0; i < n/3; i++) + { + toString = speed.ToString("F1 m/s"); + toString = speed.ToString("F0", "s⁻¹⋅mm"); + toString = speed.ToString("F2 m⋅s⁻¹"); + } + } + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/BenchMarks/ParsingBenchMarks.cs b/Gu.Units.Tests/BenchMarks/ParsingBenchMarks.cs new file mode 100644 index 00000000..adef490b --- /dev/null +++ b/Gu.Units.Tests/BenchMarks/ParsingBenchMarks.cs @@ -0,0 +1,277 @@ +namespace Gu.Units.Tests +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using Internals.Parsing; + using NUnit.Framework; + using Units.Internals; + + // run benchmarks in release build + [Explicit(Benchmarks.LongRunning)] + public class ParsingBenchmarks + { + // 2015-11-28| TryRead("#0.00#") 1 000 000 times with regex took: 355 ms + // 2015-11-28| TryRead("e5") 1 000 000 times with optimized took: 50 ms <- don't remember what was different. + // 2015-11-28| TryRead("e5") 1 000 000 times took: 118 ms + // 2015-11-28| TryRead("#0.00#") 1 000 000 times took: 95 ms + // 2015-11-28| TryRead("e5") 1 000 000 times took: 45 ms + // 2015-11-28| TryRead("#0.00#") 1 000 000 times took: 22 ms + [Test] + public void TryReadDoubleFormatSubstring() + { + int pos = 0; + PaddedFormat actual = DoubleFormatCache.GetOrCreate("e5", ref pos); + pos = 0; + actual = DoubleFormatCache.GetOrCreate("#0.00#", ref pos); + var sw = Stopwatch.StartNew(); + var n = 1000000; + for (int i = 0; i < n; i++) + { + pos = 0; + DoubleFormatCache.GetOrCreate("#0.00#", ref pos); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| TryReadDoubleFormat(\"e5\") {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + for (int i = 0; i < n; i++) + { + pos = 0; + actual = DoubleFormatCache.GetOrCreate("#0.00#", ref pos); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| TryReadDoubleFormat(\"#0.00#\") {n:N0} times {sw.ElapsedMilliseconds} ms"); + } + + // 2015-12-02| TryRead("e5") 1 000 000 times 49 ms + // 2015-12-02| TryRead("#0.00#") 1 000 000 times 46 ms + [Test] + public void TryReadDoubleFormat() + { + var paddedFormat = DoubleFormatCache.GetOrCreate("e5"); + paddedFormat = DoubleFormatCache.GetOrCreate("F5"); + paddedFormat = DoubleFormatCache.GetOrCreate("#0.00#"); + paddedFormat = DoubleFormatCache.GetOrCreate("#0.0#"); + var sw = Stopwatch.StartNew(); + var n = 1000000; + for (int i = 0; i < n; i++) + { + paddedFormat = DoubleFormatCache.GetOrCreate("e5"); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| TryRead(\"e5\") {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + for (int i = 0; i < n; i++) + { + paddedFormat = DoubleFormatCache.GetOrCreate("#0.00#"); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| TryRead(\"#0.00#\") {n:N0} times {sw.ElapsedMilliseconds} ms"); + } + + // 2015-12-02| DoubleReader.TryRead(" 123.45", 4, ...) 1 000 000 times 217 ms + // 2015-12-02| double.TryParse(substring, ...) 1 000 000 times 141 ms + // 2015-12-02| double.TryParse("123.45", ...) 1 000 000 times 124 ms + [Test] + public void TryReadDouble() + { + int pos = 4; + double actual; + const string text = "ab 123.45"; + DoubleReader.TryRead(text, ref pos, NumberStyles.Float, CultureInfo.InvariantCulture, out actual); + var sw = Stopwatch.StartNew(); + var n = 1000000; + for (int i = 0; i < n; i++) + { + pos = 4; + DoubleReader.TryRead(text, ref pos, NumberStyles.Float, CultureInfo.InvariantCulture, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| DoubleReader.TryRead(\" 123.45\", 4, ...) {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + string substring; + for (int i = 0; i < n; i++) + { + substring = text.Substring(4); + double.TryParse(substring, NumberStyles.Float, CultureInfo.InvariantCulture, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| double.TryParse(substring, ...) {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + substring = text.Substring(4); + + for (int i = 0; i < n; i++) + { + double.TryParse(substring, NumberStyles.Float, CultureInfo.InvariantCulture, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| double.TryParse(\"{substring}\", ...) {n:N0} times {sw.ElapsedMilliseconds} ms"); + } + + // 2015-12-02| IntReader.TryReadInt32("ab 12345", 4, ...) 1 000 000 times 19 ms + // 2015-12-02| int.TryParse(substring: "12345", ...) 1 000 000 times 140 ms + // 2015-12-02| int.TryParse("12345", ...) 1 000 000 times 121 ms + [Test] + public void TryReadInt() + { + int pos = 4; + int actual; + const string text = "ab 12345"; + IntReader.TryReadInt32(text, ref pos, out actual); + var sw = Stopwatch.StartNew(); + var n = 1000000; + for (int i = 0; i < n; i++) + { + pos = 4; + IntReader.TryReadInt32(text, ref pos, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| IntReader.TryReadInt32(\"{text}\", 4, ...) {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + string substring = null; + for (int i = 0; i < n; i++) + { + substring = text.Substring(4); + int.TryParse(substring, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| int.TryParse(substring: \"{substring}\", ...) {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + for (int i = 0; i < n; i++) + { + int.TryParse(substring, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| int.TryParse(\"{substring}\", ...) {n:N0} times {sw.ElapsedMilliseconds} ms"); + } + + // 2015-11-28| IntReader.TryReadInt32(" 12", 4, ...) 1 000 000 times took: 40 ms + // 2015-11-28| int.TryParse(substring, ...) 1 000 000 times took: 134 ms + [Test] + public void TryGetValue() + { + string actual; + var dict = new Dictionary(); + for (int i = 0; i < 10; i++) + { + dict[i] = i.ToString(); + } + dict.TryGetValue(2, out actual); + var sw = Stopwatch.StartNew(); + var n = 1000000; + for (int i = 0; i < n; i++) + { + dict.TryGetValue(i % 10, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| dict.TryGetValue(i % 10, out actual) {n:N0} times {sw.ElapsedMilliseconds} ms"); + + var cdict = new ConcurrentDictionary(); + for (int i = 0; i < 10; i++) + { + cdict[i] = i.ToString(); + } + + cdict.TryGetValue(2, out actual); + sw.Restart(); + for (int i = 0; i < n; i++) + { + cdict.TryGetValue(i % 10, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| cdict.TryGetValue(i % 10, out actual); {n:N0} times {sw.ElapsedMilliseconds} ms"); + + var slist = new SortedList(); + for (int i = 0; i < 10; i++) + { + slist[i] = i.ToString(); + } + + slist.TryGetValue(2, out actual); + sw.Restart(); + for (int i = 0; i < n; i++) + { + slist.TryGetValue(i % 10, out actual); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| slist.TryGetValue(i % 10, out actual); {n:N0} times {sw.ElapsedMilliseconds} ms"); + + var array = new KeyValuePair[10]; + for (int i = 0; i < 10; i++) + { + array[i] = new KeyValuePair(i, i.ToString()); + } + + var kvp = Array.Find(array, x => x.Key == 2); + sw.Restart(); + for (int i = 0; i < n; i++) + { + kvp = Array.Find(array, x => x.Key == i % 10); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Array.Find(array, x => x.Key == i % 10) {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + var kvpComparer = new KvpComparer(); + + for (int i = 0; i < n; i++) + { + var j = Array.BinarySearch(array, array[i % 10], kvpComparer); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Array.BinarySearch(array, array[i % 10]) {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + + for (int i = 0; i < n; i++) + { + var j = array.BinarySearchBy(x => x.Key, i % 10); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| array.BinarySearchBy(x => x.Key, i % 10 {n:N0} times {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + + for (int i = 0; i < n; i++) + { + kvp = array.First(x => x.Key == i % 10); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| array.First(x => x.Key == i % 10) {n:N0} times {sw.ElapsedMilliseconds} ms"); + } + + private class KvpComparer : IComparer> + { + public int Compare(KeyValuePair x, KeyValuePair y) + { + return x.Key.CompareTo(y.Key); + } + } + } +} diff --git a/Gu.Units.Tests/BenchMarks/QuantityBenchmark.cs b/Gu.Units.Tests/BenchMarks/QuantityBenchmark.cs new file mode 100644 index 00000000..c46d62f3 --- /dev/null +++ b/Gu.Units.Tests/BenchMarks/QuantityBenchmark.cs @@ -0,0 +1,67 @@ +namespace Gu.Units.Tests +{ + using System; + using System.Diagnostics; + using Internals.Parsing; + using NUnit.Framework; + + // run benchmarks in release build + [Explicit(Benchmarks.LongRunning)] + public class QuantityBenchmark + { + private const int n = 1000000; + + // Summing 1E+007 Doubles took: 11 ms + // Summing 1E+007 Lengths took: 8 ms + // 2015-11-28| Summing 1 000 000 doubles took: 1 ms + // 2015-11-28| Summing 1 000 000 Lengths took: 6 ms + [Test] + public void CompareLengthAndDouble() + { + var sw = Stopwatch.StartNew(); + double sum1 = 0; + for (double i = 0; i < n; i++) + { + sum1 += i; + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Summing {n:N0} doubles took: {sw.ElapsedMilliseconds} ms"); + sw.Restart(); + var sum2 = Length.FromMetres(1); + for (var i = 0; i < n; i++) + { + sum2 += Length.FromMetres(i); + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Summing {n:N0} Lengths took: {sw.ElapsedMilliseconds} ms"); + } + + // Summing 1E+007 Int32s took: 5 ms + // Summing 1E+007 Lengths took: 8 ms + // 2015-12-02| Summing 1 000 000 ints took: 0 ms + // 2015-12-02| Summing 1 000 000 Lengths took: 6 ms + [Test] + public void CompareLengthAndInt() + { + var sw = Stopwatch.StartNew(); + int sum1 = 0; + for (int i = 0; i < n; i++) + { + sum1 += i; + } + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Summing {n:N0} ints took: {sw.ElapsedMilliseconds} ms"); + sw.Restart(); + var sum2 = Length.FromMetres(1); + for (var i = 0; i < n; i++) + { + sum2 += Length.FromMetres(i); + } + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| Summing {n:N0} Lengths took: {sw.ElapsedMilliseconds} ms"); + + } + } +} diff --git a/Gu.Units.Tests/BenchMarks/SubstringCacheBenchmarks.cs b/Gu.Units.Tests/BenchMarks/SubstringCacheBenchmarks.cs new file mode 100644 index 00000000..61a980a9 --- /dev/null +++ b/Gu.Units.Tests/BenchMarks/SubstringCacheBenchmarks.cs @@ -0,0 +1,82 @@ +namespace Gu.Units.Tests +{ + using System; + using System.Diagnostics; + using Internals.Parsing; + using NUnit.Framework; + + // run benchmarks in release build + [Explicit(Benchmarks.LongRunning)] + public class SubstringCacheBenchmarks + { + // 2015-12-02| cache.TryFind(text, 4, out cached) 1 000 000 times with cache took: 29 ms + // 2015-12-02| text.Substring(3, 4) 1 000 000 times took: 16 ms + [Test] + public void TryFindSubStringWorstCase() + { + // this is not a really meaningful comparison as Find does much more work + // finding the end while substring gets the end for free. + // interesting as a base line. + var cache = new StringMap(); + cache.Add("abc0", "d"); + cache.Add("abc01", "e"); + cache.Add("abc1", "e"); + cache.Add("abc11", "e"); + cache.Add("abc2", "f"); + cache.Add("abc21", "f"); + + const string text0 = " abc0 "; + const string text1 = " abc1 "; + const string text2 = " abc2 "; + string cached; + cache.TryGetBySubString(text0, 4, out cached); + + var subString = text0.Substring(3, 4); + + var sw = Stopwatch.StartNew(); + var n = 1000000; + for (int i = 0; i < n; i++) + { + switch (i % 3) + { + case 0: + cache.TryGetBySubString(text0, 4, out cached); + continue; + case 1: + cache.TryGetBySubString(text1, 4, out cached); + continue; + case 2: + cache.TryGetBySubString(text2, 4, out cached); + continue; + default: + throw new Exception(); + } + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| cache.TryFind(text, 4, out cached) {n:N0} times with cache took: {sw.ElapsedMilliseconds} ms"); + + sw.Restart(); + for (int i = 0; i < n; i++) + { + switch (i % 3) + { + case 0: + subString = text0.Substring(3, 4); + continue; + case 1: + subString = text1.Substring(3, 4); + continue; + case 2: + subString = text2.Substring(3, 4); + continue; + default: + throw new Exception(); + } + } + + sw.Stop(); + Console.WriteLine($"// {DateTime.Today.ToShortDateString()}| text.Substring(3, 4) {n:N0} times took: {sw.ElapsedMilliseconds} ms"); + } + } +} diff --git a/Gu.Units.Tests/Constants/Benchmarks.cs b/Gu.Units.Tests/Constants/Benchmarks.cs new file mode 100644 index 00000000..1ab64a38 --- /dev/null +++ b/Gu.Units.Tests/Constants/Benchmarks.cs @@ -0,0 +1,7 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + public static class Benchmarks + { + public const string LongRunning = "Long running benchmark"; + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Constants/Reminder.cs b/Gu.Units.Tests/Constants/Reminder.cs new file mode 100644 index 00000000..530d2162 --- /dev/null +++ b/Gu.Units.Tests/Constants/Reminder.cs @@ -0,0 +1,8 @@ +namespace Gu.Units.Tests +{ + public class Reminder + { + public const string ToDo = "Fix so this test passes!"; + public const string CandidateForRemoval = "Maybe remove these"; + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/DoubleParserTests.cs b/Gu.Units.Tests/DoubleParserTests.cs deleted file mode 100644 index 33814c8e..00000000 --- a/Gu.Units.Tests/DoubleParserTests.cs +++ /dev/null @@ -1,162 +0,0 @@ -namespace Gu.Units.Tests -{ - using System; - using System.Collections.Generic; - using System.Globalization; - - using NUnit.Framework; - - public class DoubleParserTests - { - private static readonly string[] Formats = { "abc{0}def", "abcd{0}ef" }; - - [TestCaseSource(typeof(DoubleParseHappyPathSource))] - public void Parse(DoubleParseData data) - { - var culture = data.Culture; - var style = data.Styles; - var s = data.Text; - foreach (var format in Formats) - { - var ns = string.Format(format, s); - var start = format.IndexOf('{'); - int end; - double expected = double.Parse(s, style, culture); - var actual = DoubleParser.Parse(ns, start, style, culture, out end); - Assert.AreEqual(expected, actual); - var expectedEnd = start + s.Length; - Assert.AreEqual(expectedEnd, end); - } - } - - [TestCaseSource(typeof(DoubleParseErrorSource))] - public void ParseError(DoubleParseData data) - { - var culture = data.Culture; - var style = data.Styles; - var s = data.Text; - foreach (var format in Formats) - { - var ns = string.Format(format, s); - var start = format.IndexOf('{'); - int end = -1; - Assert.Throws(() => double.Parse(s, style, culture)); - Assert.Throws(() => DoubleParser.Parse(ns, start, style, culture, out end)); - //Assert.AreEqual(start, end); - } - } - - [TestCaseSource(typeof(DoubleParseHappyPathSource))] - public void TryParse(DoubleParseData data) - { - var culture = data.Culture; - var style = data.Styles; - var s = data.Text; - foreach (var format in Formats) - { - var ns = string.Format(format, s); - var start = format.IndexOf('{'); - int end; - double expected; - Assert.IsTrue(double.TryParse(s, style, culture, out expected)); - double actual; - Assert.IsTrue(DoubleParser.TryParse(ns, start, style, culture, out actual, out end)); - Assert.AreEqual(expected, actual); - var expectedEnd = start + s.Length; - Assert.AreEqual(expectedEnd, end); - } - } - - [TestCaseSource(typeof(DoubleParseErrorSource))] - public void TryParseError(DoubleParseData data) - { - var culture = data.Culture; - var style = data.Styles; - var s = data.Text; - foreach (var format in Formats) - { - var ns = string.Format(format, s); - var start = format.IndexOf('{'); - int end; - double expected; - Assert.IsFalse(double.TryParse(s, style, culture, out expected)); - double actual; - Assert.IsFalse(DoubleParser.TryParse(ns, start, style, culture, out actual, out end)); - Assert.AreEqual(expected, actual); - //Assert.AreEqual(start, end); - } - } - - public class DoubleParseHappyPathSource : List - { - public DoubleParseHappyPathSource() - { - var en = CultureInfo.GetCultureInfo("en-US"); - var sv = CultureInfo.GetCultureInfo("sv-SE"); - Add("1", NumberStyles.Float, en); - Add(" 1", NumberStyles.Float, en); - Add("-1", NumberStyles.Float, en); - Add("+1", NumberStyles.Float, en); - Add(".1", NumberStyles.Float, en); - Add("1.", NumberStyles.Float, en); - Add("+1.2", NumberStyles.Float, en); - Add("+1,2", NumberStyles.Float, sv); - Add("+1.2e3", NumberStyles.Float, en); - Add("+1.2E3", NumberStyles.Float, en); - Add("+1.2e-3", NumberStyles.Float, en); - Add("+1.2E-3", NumberStyles.Float, en); - Add("+1.2e+3", NumberStyles.Float, en); - Add(sv.NumberFormat.NaNSymbol, NumberStyles.Float, sv); - Add(sv.NumberFormat.PositiveInfinitySymbol, NumberStyles.Float, sv); - Add(sv.NumberFormat.NegativeInfinitySymbol, NumberStyles.Float, sv); - } - - public void Add(string text, NumberStyles styles, CultureInfo culture) - { - Add(new DoubleParseData(text, styles, culture)); - } - } - - public class DoubleParseErrorSource : List - { - public DoubleParseErrorSource() - { - var en = CultureInfo.GetCultureInfo("en-US"); - var sv = CultureInfo.GetCultureInfo("sv-SE"); - Add("e1", NumberStyles.Float, en); - Add(" 1", NumberStyles.None, en); - Add("-1", NumberStyles.None, en); - Add(".1", NumberStyles.None, en); - //Add("1.", NumberStyles.Float | NumberStyles.AllowHexSpecifier, en); - Add(".", NumberStyles.Float, en); - //Add("+1,2", NumberStyles.Float, en); - //Add("+1.2", NumberStyles.Float, sv); - Add("+1.2e3", NumberStyles.None|NumberStyles.AllowDecimalPoint|NumberStyles.AllowLeadingSign, en); - } - - public void Add(string text, NumberStyles styles, CultureInfo culture) - { - Add(new DoubleParseData(text, styles, culture)); - } - } - - public class DoubleParseData - { - public readonly string Text; - public readonly NumberStyles Styles; - public readonly CultureInfo Culture; - - public DoubleParseData(string text, NumberStyles styles, CultureInfo culture) - { - Text = text; - Styles = styles; - Culture = culture; - } - - public override string ToString() - { - return string.Format("Text: {0}, Styles: {1}, Culture: {2}", Text, Styles, Culture); - } - } - } -} diff --git a/Gu.Units.Tests/FormatTests.cs b/Gu.Units.Tests/FormatTests.cs new file mode 100644 index 00000000..f120e61c --- /dev/null +++ b/Gu.Units.Tests/FormatTests.cs @@ -0,0 +1,121 @@ +namespace Gu.Units.Tests +{ + using System.Globalization; + using System.Threading; + using Internals.Parsing; + using NUnit.Framework; + + public class FormatTests + { + private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; + private const char MultiplyDot = '⋅'; + const string UnknownFormat = "unknown format"; + + [Test] + public void FormatAngle() + { + var angle = Angle.FromDegrees(1.2); + using (Thread.CurrentThread.UsingTempCulture(CultureInfo.InvariantCulture)) + { + Assert.AreEqual("0.020943951023932\u00A0rad", angle.ToString()); + Assert.AreEqual("1.200°", angle.ToString("F3°")); + Assert.AreEqual("1.2°", angle.ToString(AngleUnit.Degrees)); + Assert.AreEqual(" 1.2 ° ", angle.ToString(" F1 ° ")); + Assert.AreEqual(" 0.02 rad ", angle.ToString(" F2 rad ")); + Assert.AreEqual("1.200°", angle.ToString("F3", AngleUnit.Degrees)); + Assert.AreEqual("0.02\u00A0rad", angle.ToString("F2", AngleUnit.Radians)); + } + + var sv = CultureInfo.GetCultureInfo("sv-SE"); + Assert.AreEqual("0,020943951023932\u00A0rad", angle.ToString(sv)); + Assert.AreEqual("1,200°", angle.ToString("F3°", sv)); + Assert.AreEqual("1,2°", angle.ToString(AngleUnit.Degrees, sv)); + Assert.AreEqual(" 1,2 ° ", angle.ToString(" F1 ° ", sv)); + Assert.AreEqual(" 0,02 rad", angle.ToString(" F2 rad", sv)); + Assert.AreEqual("1,200°", angle.ToString("F3", AngleUnit.Degrees, sv)); + Assert.AreEqual("0,02\u00A0rad", angle.ToString("F2", AngleUnit.Radians, sv)); + } + + [Test] + public void FormatSpeed() + { + var speed = Speed.FromMetresPerSecond(1.2); + using (Thread.CurrentThread.UsingTempCulture(CultureInfo.InvariantCulture)) + { + Assert.AreEqual("1.2\u00A0m/s", speed.ToString()); + Assert.AreEqual("1.20 m/s", speed.ToString("F2 m/s")); + Assert.AreEqual(UnknownFormat, 1.2.ToString(UnknownFormat)); // for comparison + Assert.AreEqual(UnknownFormat, speed.ToString(UnknownFormat)); + Assert.AreEqual("F1\u00A0{unit: invalid}", speed.ToString("F1 invalid")); + Assert.AreEqual("1.20 m⋅s⁻¹", speed.ToString("F2 m⋅s⁻¹")); + Assert.AreEqual("1.2\u00A0m⋅s⁻¹", speed.ToString("f1", "m⋅s⁻¹")); + Assert.AreEqual("1.2 m⋅s⁻¹", speed.ToString("f1 ", "m⋅s⁻¹")); + Assert.AreEqual("1.2 m⋅s⁻¹", speed.ToString("f1", " m⋅s⁻¹")); + Assert.AreEqual("1.2 m⋅s⁻¹", speed.ToString("f1 ", " m⋅s⁻¹")); + Assert.AreEqual("{value: null} mm⋅s⁻¹", speed.ToString("mm⋅s⁻¹")); + Assert.AreEqual("1200\u00A0s⁻¹⋅mm", speed.ToString("F0", "s⁻¹⋅mm")); + Assert.AreEqual("1200\u00A0s⁻¹⋅mm¹", speed.ToString("F0", "s⁻¹⋅mm¹")); + Assert.AreEqual("1.2\u00A0m*s^-1", speed.ToString("F1", "m*s^-1")); + Assert.AreEqual("1.2\u00A0s^-1*m", speed.ToString("F1", "s^-1*m")); + Assert.AreEqual("1.2\u00A0s^-1*m^1", speed.ToString("F1", "s^-1*m^1")); + Assert.AreEqual("4.32\u00A0km/h", speed.ToString(SpeedUnit.KilometresPerHour)); + Assert.AreEqual("1.2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.Default)); + Assert.AreEqual("1.2\u00A0m/s", speed.ToString( SpeedUnit.MetresPerSecond, SymbolFormat.FractionHatPowers)); + Assert.AreEqual("1.2\u00A0m*s^-1", speed.ToString( SpeedUnit.MetresPerSecond, SymbolFormat.SignedHatPowers)); + Assert.AreEqual("1.2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.FractionSuperScript)); + Assert.AreEqual("1.2\u00A0m⋅s⁻¹", speed.ToString( SpeedUnit.MetresPerSecond, SymbolFormat.SignedSuperScript)); + Assert.AreEqual("4.3\u00A0km/h", speed.ToString("F1", SpeedUnit.KilometresPerHour)); + Assert.AreEqual("1.2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.Default)); + Assert.AreEqual("1.2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.FractionHatPowers)); + Assert.AreEqual("1.2\u00A0m*s^-1", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.SignedHatPowers)); + Assert.AreEqual("1.2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.FractionSuperScript)); + Assert.AreEqual("1.2\u00A0m⋅s⁻¹", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.SignedSuperScript)); + Assert.AreEqual("1,200.00 mm⋅s⁻¹", speed.ToString("N mm⋅s⁻¹")); + } + + var sv = CultureInfo.GetCultureInfo("sv-SE"); + + Assert.AreEqual("1,2\u00A0m/s", speed.ToString(sv)); + Assert.AreEqual("1,20 m/s", speed.ToString("F2 m/s", sv)); + Assert.AreEqual(UnknownFormat, 1.2.ToString(UnknownFormat, sv)); // for comparison + Assert.AreEqual(UnknownFormat, speed.ToString(UnknownFormat, sv)); + Assert.AreEqual("F1\u00A0{unit: invalid}", speed.ToString("F1 invalid", sv)); + Assert.AreEqual("1,20 m⋅s⁻¹", speed.ToString("F2 m⋅s⁻¹", sv)); + Assert.AreEqual("1,2\u00A0m⋅s⁻¹", speed.ToString("f1", "m⋅s⁻¹", sv)); + Assert.AreEqual("1,2 m⋅s⁻¹", speed.ToString("f1 ", "m⋅s⁻¹", sv)); + Assert.AreEqual("1,2 m⋅s⁻¹", speed.ToString("f1", " m⋅s⁻¹", sv)); + Assert.AreEqual("1,2 m⋅s⁻¹", speed.ToString("f1 ", " m⋅s⁻¹", sv)); + Assert.AreEqual("{value: null} mm⋅s⁻¹", speed.ToString("mm⋅s⁻¹", sv)); + Assert.AreEqual("1200\u00A0s⁻¹⋅mm", speed.ToString("F0", "s⁻¹⋅mm", sv)); + Assert.AreEqual("1200\u00A0s⁻¹⋅mm¹", speed.ToString("F0", "s⁻¹⋅mm¹", sv)); + Assert.AreEqual("1,2\u00A0m*s^-1", speed.ToString("F1", "m*s^-1", sv)); + Assert.AreEqual("1,2\u00A0s^-1*m", speed.ToString("F1", "s^-1*m", sv)); + Assert.AreEqual("1,2\u00A0s^-1*m^1", speed.ToString("F1", "s^-1*m^1", sv)); + Assert.AreEqual("4,32\u00A0km/h", speed.ToString(SpeedUnit.KilometresPerHour, sv)); + Assert.AreEqual("1,2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.Default, sv)); + Assert.AreEqual("1,2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.FractionHatPowers, sv)); + Assert.AreEqual("1,2\u00A0m*s^-1", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.SignedHatPowers, sv)); + Assert.AreEqual("1,2\u00A0m/s", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.FractionSuperScript, sv)); + Assert.AreEqual("1,2\u00A0m⋅s⁻¹", speed.ToString(SpeedUnit.MetresPerSecond, SymbolFormat.SignedSuperScript, sv)); + Assert.AreEqual("4,3\u00A0km/h", speed.ToString("F1", SpeedUnit.KilometresPerHour, sv)); + Assert.AreEqual("1,2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.Default, sv)); + Assert.AreEqual("1,2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.FractionHatPowers, sv)); + Assert.AreEqual("1,2\u00A0m*s^-1", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.SignedHatPowers, sv)); + Assert.AreEqual("1,2\u00A0m/s", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.FractionSuperScript, sv)); + Assert.AreEqual("1,2\u00A0m⋅s⁻¹", speed.ToString("F1", SpeedUnit.MetresPerSecond, SymbolFormat.SignedSuperScript, sv)); + Assert.AreEqual("1\u00A0200,00 mm⋅s⁻¹", speed.ToString("N mm⋅s⁻¹", sv)); + } + + [Explicit(Reminder.ToDo)] + [Test] + public void FormatPressure() + { + var pressure = Pressure.FromMegapascals(1.2); + Assert.AreEqual("1.20 N/m^2", pressure.ToString("F2 N/m^2")); + Assert.AreEqual("1.20 N/m^2", pressure.ToString("F2 N⋅m⁻²")); + Assert.AreEqual("1.20 N/m^2", pressure.ToString("F2 N⋅mm⁻²")); + Assert.AreEqual("1.20 MPa", pressure.ToString("F2 MPa")); + Assert.AreEqual("1.20E6 Pa", pressure.ToString("E Pa")); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Gu.Units.Tests.csproj b/Gu.Units.Tests/Gu.Units.Tests.csproj index 85b8d095..4e4fb0c6 100644 --- a/Gu.Units.Tests/Gu.Units.Tests.csproj +++ b/Gu.Units.Tests/Gu.Units.Tests.csproj @@ -44,23 +44,48 @@ + + + - + + + + + - + + + + + + + + + + + + + + + + + + + + + - + - - - + + - @@ -82,6 +107,9 @@ --> + + + @@ -111,42 +139,11 @@ - - - - - ..\packages\NUnit\lib\net20\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\net40\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\net45\nunit.framework.dll - True - True - - - - - - - ..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll - True - True - - - - + + + ..\packages\NUnit\lib\nunit.framework.dll + True + True + + \ No newline at end of file diff --git a/Gu.Units.Tests/Gu.Units.Tests.csproj.DotSettings b/Gu.Units.Tests/Gu.Units.Tests.csproj.DotSettings index 5d56eba4..cd946618 100644 --- a/Gu.Units.Tests/Gu.Units.Tests.csproj.DotSettings +++ b/Gu.Units.Tests/Gu.Units.Tests.csproj.DotSettings @@ -1,2 +1,7 @@  - True \ No newline at end of file + True + True + True + True + True + True \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Caching/UnitFormatCacheTests.cs b/Gu.Units.Tests/Internals/Caching/UnitFormatCacheTests.cs new file mode 100644 index 00000000..e1ce5339 --- /dev/null +++ b/Gu.Units.Tests/Internals/Caching/UnitFormatCacheTests.cs @@ -0,0 +1,49 @@ +namespace Gu.Units.Tests.Internals +{ + using System; + using System.Collections.Generic; + using System.Linq; + using NUnit.Framework; + + public class UnitFormatCacheTests + { + private const string Unicodes = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; + + [TestCaseSource(nameof(SymbolFormatsSource))] + public void GetOrCreateSymbolFormatLength(SymbolFormat symbolFormat) + { + var format = UnitFormatCache.GetOrCreate(LengthUnit.Millimetres, symbolFormat); + Assert.AreEqual(null, format.PrePadding); + Assert.AreEqual("mm", format.Format); + Assert.AreEqual(null, format.PostPadding); + } + + [TestCase(SymbolFormat.Default, "mm/s")] + [TestCase(SymbolFormat.SignedHatPowers, "mm*s^-1")] + [TestCase(SymbolFormat.FractionHatPowers, "mm/s")] + [TestCase(SymbolFormat.SignedSuperScript, "mm⋅s⁻¹")] + [TestCase(SymbolFormat.FractionSuperScript, "mm/s")] + public void GetOrCreateSymbolFormatSpeed(SymbolFormat symbolFormat, string expected) + { + var format = UnitFormatCache.GetOrCreate(SpeedUnit.MillimetresPerSecond, symbolFormat); + Assert.AreEqual(null, format.PrePadding); + Assert.AreEqual(expected, format.Format); + Assert.AreEqual(null, format.PostPadding); + } + + [TestCase(SymbolFormat.Default, "Hz")] + [TestCase(SymbolFormat.SignedHatPowers, "Hz")] + [TestCase(SymbolFormat.FractionHatPowers, "Hz")] + [TestCase(SymbolFormat.SignedSuperScript, "Hz")] + [TestCase(SymbolFormat.FractionSuperScript, "Hz")] + public void GetOrCreateSymbolFormatFrequency(SymbolFormat symbolFormat, string expected) + { + var format = UnitFormatCache.GetOrCreate(FrequencyUnit.Hz, symbolFormat); + Assert.AreEqual(null, format.PrePadding); + Assert.AreEqual(expected, format.Format); + Assert.AreEqual(null, format.PostPadding); + } + + private static IReadOnlyList SymbolFormatsSource = Enum.GetValues(typeof(SymbolFormat)).Cast().ToList(); + } +} diff --git a/Gu.Units.Tests/Internals/Helpers/MapTests.cs b/Gu.Units.Tests/Internals/Helpers/MapTests.cs new file mode 100644 index 00000000..fcd6cbe8 --- /dev/null +++ b/Gu.Units.Tests/Internals/Helpers/MapTests.cs @@ -0,0 +1,39 @@ +namespace Gu.Units.Tests.Internals.Helpers +{ + using NUnit.Framework; + + public class MapTests + { + [Test] + public void TryGetSuccess() + { + var map = new Map(); + map.TryAdd(1, "1"); + map.TryAdd(2, "2"); + + string actualString; + Assert.True(map.TryGet(1, out actualString)); + Assert.AreEqual("1", actualString); + + int actualInt; + Assert.True(map.TryGet("1", out actualInt)); + Assert.AreEqual(1, actualInt); + } + + [Test] + public void TryGetFail() + { + var map = new Map(); + map.TryAdd(1, "1"); + map.TryAdd(2, "2"); + + string actualString; + Assert.False(map.TryGet(3, out actualString)); + Assert.AreEqual(null, actualString); + + int actualInt; + Assert.False(map.TryGet("3", out actualInt)); + Assert.AreEqual(0, actualInt); + } + } +} diff --git a/Gu.Units.Tests/Internals/Helpers/StringBuilderPoolTests.cs b/Gu.Units.Tests/Internals/Helpers/StringBuilderPoolTests.cs new file mode 100644 index 00000000..0915800b --- /dev/null +++ b/Gu.Units.Tests/Internals/Helpers/StringBuilderPoolTests.cs @@ -0,0 +1,61 @@ +namespace Gu.Units.Tests.Internals.Helpers +{ + using System.Collections.Concurrent; + using System.Reflection; + using System.Text; + using NUnit.Framework; + + public class StringBuilderPoolTests + { + [Test] + public void UseTwice() + { + Clear(); // testrunner may run in parallel creating more than one builder. + StringBuilder inner1; + using (var builder = StringBuilderPool.Borrow()) + { + builder.Append("a"); + inner1 = GetInner(builder); + Assert.AreEqual("a", builder.ToString()); + } + + using (var builder = StringBuilderPool.Borrow()) + { + builder.Append("bc"); + var inner2 = GetInner(builder); + Assert.AreSame(inner1, inner2); + Assert.AreEqual("bc", builder.ToString()); + } + } + + [Test] + public void Parallel() + { + using (var builder1 = StringBuilderPool.Borrow()) + using (var builder2 = StringBuilderPool.Borrow()) + { + builder1.Append("a"); + builder2.Append("b"); + Assert.AreNotSame(GetInner(builder1), GetInner(builder2)); + Assert.AreEqual("a", builder1.ToString()); + Assert.AreEqual("b", builder2.ToString()); + } + } + + private static StringBuilder GetInner(StringBuilderPool.Builder outer) + { + var fieldInfo = typeof(StringBuilderPool.Builder).GetField("builder", BindingFlags.Instance | BindingFlags.NonPublic); + return (StringBuilder)fieldInfo.GetValue(outer); + } + + private static void Clear() + { + var buildersField = typeof (StringBuilderPool).GetField("Builders", BindingFlags.Static | BindingFlags.NonPublic); + var builders =(ConcurrentQueue) buildersField.GetValue(null); + StringBuilder temp; + while (builders.TryDequeue(out temp)) + { + } + } + } +} diff --git a/Gu.Units.Tests/Internals/Helpers/StringMapTests.cs b/Gu.Units.Tests/Internals/Helpers/StringMapTests.cs new file mode 100644 index 00000000..4d56327a --- /dev/null +++ b/Gu.Units.Tests/Internals/Helpers/StringMapTests.cs @@ -0,0 +1,103 @@ +namespace Gu.Units.Tests.Internals.Helpers +{ + using System; + using System.Reflection; + using NUnit.Framework; + + public class StringMapTests + { + [Test] + public void AddSameTwice() + { + var cache = new StringMap(); + cache.Add("abc", "d"); + cache.Add("abc", "d"); + var cachedItems = GetInnerCache(cache); + Assert.AreEqual(1, cachedItems.Length); + } + + [Test] + public void AddDifferentWithSameKeyTwice() + { + var cache = new StringMap(); + cache.Add("abc", "d"); + Assert.Throws(() => cache.Add("abc", "e")); + } + + [Test] + public void Sorts() + { + var cache = new StringMap(); + var item1 = cache.Add("abcde", "1"); + var item2 = cache.Add("abc", "2"); + var item3 = cache.Add("abcd", "3"); + var item4 = cache.Add("bar", "4"); + var actual = GetInnerCache(cache); + var expected = new[] { item2, item3, item1, item4 }; + CollectionAssert.AreEqual(expected, actual); + } + + [TestCase("abc", 0, "abc", "2")] + [TestCase("abcdef", 0, "abcde", "1")] + [TestCase(" abc", 1, "abc", "2")] + [TestCase(" abcd", 1, "abcd", "3")] + public void TryFindSubStringSuccess(string key, int pos, string expectedKey, string expectedValue) + { + var cache = new StringMap(); + cache.Add("abcde", "1"); + cache.Add("abc", "2"); + cache.Add("abcd", "3"); + cache.Add("bar", "4"); + string actual; + string actualKey; + var success = cache.TryGetBySubString(key, pos, out actualKey, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(expectedKey, actualKey); + Assert.AreEqual(expectedValue, actual); + + success = cache.TryGetBySubString(key, pos, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(expectedValue, actual); + } + + [TestCase("abc", "2")] + [TestCase("abcde", "1")] + [TestCase("abcd", "3")] + public void TryGetSuccess(string key, string expected) + { + var cache = new StringMap(); + cache.Add("abcde", "1"); + cache.Add("abc", "2"); + cache.Add("abcd", "3"); + cache.Add("bar", "4"); + string actual; + var success = cache.TryGet(key, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(expected, actual); + } + + [TestCase(null, 0)] + [TestCase("", 0)] + [TestCase(" ggg", 1)] + [TestCase("ggg", 1)] + [TestCase("g", 1)] + public void AddThenGetFail(string key, int pos) + { + var cache = new StringMap(); + cache.Add("abc", "d"); + cache.Add("foo", "e"); + cache.Add("bar", "f"); + string actual; + var success = cache.TryGetBySubString(key, pos, out actual); + Assert.AreEqual(false, success); + Assert.AreEqual(null, actual); + Assert.AreEqual(null, actual); + } + + private static StringMap.CachedItem[] GetInnerCache(StringMap cache) + { + var fieldInfo = typeof(StringMap).GetField("cache", BindingFlags.NonPublic | BindingFlags.Instance); + return (StringMap.CachedItem[])fieldInfo.GetValue(cache); + } + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/CompositeFormatParserTests.cs b/Gu.Units.Tests/Internals/Parsing/CompositeFormatParserTests.cs new file mode 100644 index 00000000..eca67dfa --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/CompositeFormatParserTests.cs @@ -0,0 +1,74 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System.Globalization; + using NUnit.Framework; + + public class CompositeFormatParserTests + { + private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; + private const char MultiplyDot = '⋅'; + + [TestCase("", "{0}\u00A0m", "1.2\u00A0m")] + [TestCase(null, "{0}\u00A0m", "1.2\u00A0m")] + [TestCase("E mm", "{0:E} mm", "1.200000E+003 mm")] + [TestCase("ecm", "{0:e}cm", "1.200000e+002cm")] + [TestCase("E5m", "{0:E5}m", "1.20000E+000m")] + [TestCase("e5 m", "{0:e5} m", "1.20000e+000 m")] + [TestCase("Fcm", "{0:F}cm", "120.00cm")] + [TestCase(" f mm ", " {0:f} mm ", " 1200.00 mm ")] + [TestCase("F5km", "{0:F5}km", "0.00120km")] + [TestCase("f5 m", "{0:f5} m", "1.20000 m")] + [TestCase("Gcm", "{0:G}cm", "120cm")] + [TestCase("g cm", "{0:g} cm", "120 cm")] + [TestCase("G5m", "{0:G5}m", "1.2m")] + [TestCase("g5 m", "{0:g5} m", "1.2 m")] + [TestCase("Nm", "{0:N}m", "1.20m")] + [TestCase("n m", "{0:n} m", "1.20 m")] + [TestCase("N5m", "{0:N5}m", "1.20000m")] + [TestCase("n5 m", "{0:n5} m", "1.20000 m")] + [TestCase("Rm", "{0:R}m", "1.2m")] + [TestCase("r m", "{0:r} m", "1.2 m")] + [TestCase("0m", "{0:0}m", "1m")] + [TestCase("0.00 m", "{0:0.00} m", "1.20 m")] + [TestCase("#m", "{0:#}m", "1m")] + [TestCase("#.# m", "{0:#.#} m", "1.2 m")] + [TestCase("#.0#m", "{0:#.0#}m", "1.2m")] + [TestCase("#0.00# m", "{0:#0.00#} m", "1.20 m")] + public void TryParseSuccess(string format, string expectedFormat, string expectedFormatted) + { + QuantityFormat actual; + var success = CompositeFormatParser.TryParse(format, out actual); + + Assert.AreEqual(true, success); + Assert.AreEqual(expectedFormat, actual.CompositeFormat); + + var length = Length.FromMetres(1.2); + var actualFormatted = length.ToString(format, CultureInfo.InvariantCulture); + Assert.AreEqual(expectedFormatted, actualFormatted); + } + + [Explicit(Reminder.ToDo)] + [TestCase("1\u00A0200,00 mm⋅s⁻¹", "#_##0.00 mm⋅s⁻¹", "meh")] + [TestCase("F3 N/mm^2", "{0:F3} N/m^2", "MPa")] + [TestCase("F3 N⋅mm⁻²", "{0:F3} N⋅m⁻²", "MPa")] + [TestCase("E Pa", "{0:E} Pa", "Pa")] + public void TryParsePressure(string format, string expectedFormat, string expectedSymbol) + { + QuantityFormat actual; + var success = CompositeFormatParser.TryParse(format, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(expectedFormat, actual.CompositeFormat); + Assert.AreEqual(expectedSymbol, actual.Unit.Symbol); + } + + [TestCase("E", "E\u00A0{unit: null}")] + [TestCase("mm", "{value: null}\u00A0mm")] + public void TryParseError(string text, string expectedError) + { + QuantityFormat actual; + var success = CompositeFormatParser.TryParse(text, out actual); + Assert.AreEqual(false, success); + Assert.AreEqual(expectedError, actual.ErrorText); + } + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/DoubleFormatReaderTests.cs b/Gu.Units.Tests/Internals/Parsing/DoubleFormatReaderTests.cs new file mode 100644 index 00000000..648d1a6e --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/DoubleFormatReaderTests.cs @@ -0,0 +1,71 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System.Collections.Generic; + using NUnit.Framework; + + public class DoubleFormatReaderTests + { + [TestCase(null, 0, null, 0)] + [TestCase("", 0, null, 0)] + [TestCase("E", 0, "E", 1)] + [TestCase("e", 0, "e", 1)] + [TestCase("E5", 0, "E5", 2)] + [TestCase("E20", 0, "E20", 3)] // double + [TestCase("e5", 0, "e5", 2)] + [TestCase("F", 0, "F", 1)] + [TestCase("f", 0, "f", 1)] + [TestCase("F5", 0, "F5", 2)] + [TestCase("f5", 0, "f5", 2)] + [TestCase("G", 0, "G", 1)] + [TestCase("g", 0, "g", 1)] + [TestCase("G5", 0, "G5", 2)] + [TestCase("g5", 0, "g5", 2)] + [TestCase("N", 0, "N", 1)] + [TestCase("n", 0, "n", 1)] + [TestCase("N5", 0, "N5", 2)] + [TestCase("n5", 0, "n5", 2)] + [TestCase("R", 0, "R", 1)] + [TestCase("r", 0, "r", 1)] + [TestCase("0", 0, "0", 1)] + [TestCase("0.00", 0, "0.00", 4)] + [TestCase("#", 0, "#", 1)] + [TestCase("#.#", 0, "#.#", 3)] + [TestCase("#.0#", 0, "#.0#", 4)] + [TestCase("#0.00#", 0, "#0.00#", 6)] + public void TryRead(string text, int pos, string expected, int expectedPos) + { + PaddedFormat actual = DoubleFormatCache.GetOrCreate(text, ref pos); + Assert.AreEqual(expected, actual.Format); + Assert.AreEqual(expectedPos, pos); + } + + [TestCase("J", 0, null)] + [TestCase("J5", 0, null)] + //[TestCase("E100", 0, "E101")] + //[TestCase("E101", 0, "E111")] + //[TestCase("E102", 0, "E112")] + //[TestCase("E-1", 0, "E-1")] + [TestCase("abc", 0, "abc")] + public void TryReadError(string text, int pos, string expectedFormatted) + { + PaddedFormat actual = DoubleFormatCache.GetOrCreate(text, ref pos); + Assert.AreEqual(text, actual.Format); + Assert.AreEqual(0, pos); + string formatted = null; + try + { + formatted = 1.2.ToString(text); + } + catch + { + } + + Assert.AreEqual(expectedFormatted, formatted); + } + + private static readonly IReadOnlyList> HappyPaths = new[] + { + SuccessData.Create("e", 0, new PaddedFormat(null, "e", null), 1), + }; + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/DoubleReaderTests.cs b/Gu.Units.Tests/Internals/Parsing/DoubleReaderTests.cs new file mode 100644 index 00000000..40a5cbd4 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/DoubleReaderTests.cs @@ -0,0 +1,318 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Text; + using NUnit.Framework; + + public class DoubleReaderTests + { + private static readonly string[] PadFormats = { "abc{0}def", "abcd{0}ef", "{0}" }; + + [Explicit("Runs forever unless")] + [Test] + public void Fuzzer() + { + var rnd = new Random(DateTime.Now.Millisecond); + long count = 0; + var builder = new StringBuilder(); + string text; + + double parsed; + double read; + + bool parseSuccess; + bool readSuccess; + bool success; + do + { + var pos = 0; + var length = rnd.Next(2, 15); + var decimalPlace = rnd.Next(length); + builder.Clear(); + for (int i = 0; i < length; i++) + { + if (i == decimalPlace) + { + builder.Append('.'); + continue; + } + builder.Append((char)rnd.Next('0', '9')); + } + + text = builder.ToString(); + parseSuccess = double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out parsed); + readSuccess = DoubleReader.TryRead(text, ref pos, NumberStyles.Float, CultureInfo.InvariantCulture, out read); + success = parseSuccess && readSuccess && parsed == read; + count++; + } while (success); + + Console.WriteLine($"Count: {count}"); + Console.WriteLine(text); + Console.WriteLine($"success: {parseSuccess} double.TryParse(text, out {parsed.ToString("R", CultureInfo.InvariantCulture)})"); + Console.WriteLine($"success: {readSuccess} double.TryParse(text, out {read.ToString("R", CultureInfo.InvariantCulture)})"); + } + + [TestCaseSource(nameof(HappyPaths))] + public void ReadSuccess(DoubleData data) + { + var culture = data.Culture; + var style = data.Styles; + foreach (var format in PadFormats) + { + var text = string.Format(format, data.Text); + var pos = format.IndexOf('{'); + var start = pos; + double expected = double.Parse(data.Text, style, culture); + var actual = DoubleReader.Read(text, ref pos, style, culture); + Assert.AreEqual(expected, actual); + var expectedEnd = start + data.Text.Length; + Assert.AreEqual(expectedEnd, pos); + } + } + + [TestCaseSource(nameof(Errors))] + public void ReadError(DoubleData data) + { + var culture = data.Culture; + var style = data.Styles; + var text = data.Text; + foreach (var format in PadFormats) + { + var ns = string.Format(format, text); + var pos = format.IndexOf('{'); + var start = pos; + Exception parseException = null; + try + { + double.Parse(text, style, culture); + } + catch (Exception e) + { + parseException = e; + } + + Exception readException = null; + try + { + DoubleReader.Read(ns, ref pos, style, culture); + } + catch (Exception e) + { + readException = e; + } + + Assert.AreEqual(start, pos); + Assert.NotNull(parseException); + Assert.NotNull(readException); + } + } + + [Test] + public void ReadException() + { + int endPos; + var text = "abcdef"; + var culture = CultureInfo.InvariantCulture; + var pos = 3; + var e = Assert.Throws(() => DoubleReader.Read(text, ref pos, NumberStyles.Float, culture)); + var expected = "Expected to find a double starting at index 3\r\n" + + "String: abcdef\r\n" + + " ^"; + Assert.AreEqual(expected, e.Message); + } + + [TestCaseSource(nameof(HappyPaths))] + public void TryReadSuccess(DoubleData data) + { + var culture = data.Culture; + var style = data.Styles; + var pos = 0; + double expected; + Assert.IsTrue(double.TryParse(data.Text, style, culture, out expected)); + double actual; + Assert.IsTrue(DoubleReader.TryRead(data.Text, ref pos, style, culture, out actual)); + Assert.AreEqual(expected, actual); + var expectedEnd = data.Text.Length; + Assert.AreEqual(expectedEnd, pos); + } + + [TestCaseSource(nameof(HappyPaths))] + public void TryReadPaddedSuccess(DoubleData data) + { + var culture = data.Culture; + var style = data.Styles; + foreach (var format in PadFormats) + { + var text = string.Format(format, data.Text); + var pos = format.IndexOf('{'); + var start = pos; + double expected; + Assert.IsTrue(double.TryParse(data.Text, style, culture, out expected)); + double actual; + Assert.IsTrue(DoubleReader.TryRead(text, ref pos, style, culture, out actual)); + Assert.AreEqual(expected, actual); + var expectedEnd = start + data.Text.Length; + Assert.AreEqual(expectedEnd, pos); + } + } + + [TestCaseSource(nameof(Errors))] + public void TryReadErrorPadded(DoubleData data) + { + var culture = data.Culture; + var style = data.Styles; + var s = data.Text; + foreach (var format in PadFormats) + { + var ns = string.Format(format, s); + var pos = format.IndexOf('{'); + var start = pos; + double expected; + Assert.IsFalse(double.TryParse(s, style, culture, out expected)); + double actual; + Assert.IsFalse(DoubleReader.TryRead(ns, ref pos, style, culture, out actual)); + Assert.AreEqual(expected, actual); + Assert.AreEqual(start, pos); + } + } + + [TestCaseSource(nameof(Errors))] + public void TryReadError(DoubleData data) + { + var culture = data.Culture; + var style = data.Styles; + var pos = 0; + double expected; + Assert.IsFalse(double.TryParse(data.Text, style, culture, out expected)); + double actual; + Assert.IsFalse(DoubleReader.TryRead(data.Text, ref pos, style, culture, out actual)); + Assert.AreEqual(expected, actual); + Assert.AreEqual(0, pos); + } + + #region TestData + + private static readonly CultureInfo en = CultureInfo.GetCultureInfo("en-US"); + private static readonly CultureInfo sv = CultureInfo.GetCultureInfo("sv-SE"); + + private static readonly IReadOnlyList HappyPaths = new[] + { + CreateParseData("0", NumberStyles.Float, en), + CreateParseData("0.", NumberStyles.Float, en), + CreateParseData(".0", NumberStyles.Float, en), + CreateParseData("0.0", NumberStyles.Float, en), + CreateParseData("1.2", NumberStyles.Float, en), + CreateParseData("0.012", NumberStyles.Float, en), + CreateParseData("0.0012", NumberStyles.Float, en), + CreateParseData("0.001", NumberStyles.Float, en), + CreateParseData("1", NumberStyles.Float, en), + CreateParseData(" 1", NumberStyles.Float, en), + CreateParseData("-1", NumberStyles.Float, en), + CreateParseData("+1", NumberStyles.Float, en), + CreateParseData(".1", NumberStyles.Float, en), + CreateParseData("-.1", NumberStyles.Float, en), + CreateParseData("1.", NumberStyles.Float, en), + CreateParseData("-1.", NumberStyles.Float, en), + CreateParseData("12,345.67", NumberStyles.Float | NumberStyles.AllowThousands, en), + CreateParseData("-12,345.67", NumberStyles.Float | NumberStyles.AllowThousands, en), + CreateParseData("+1.2", NumberStyles.Float, en), + CreateParseData("+1,2", NumberStyles.Float, sv), + CreateParseData("+1.2e3", NumberStyles.Float, en), + CreateParseData("-1.2E3", NumberStyles.Float, en), + CreateParseData("+1.2e-3", NumberStyles.Float, en), + CreateParseData("+1.2E-3", NumberStyles.Float, en), + CreateParseData("-1.2e+3", NumberStyles.Float, en),//1,,2,3,4,5,,,.00 + CreateParseData("1,,2,3,4,5,,,.00", NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData("12345678910123456789", NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData("1.2345678910123456789", NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData("1234567891012345678.9", NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData(new string('1', 307), NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData(new string('1', 308), NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData(new string('1', 309), NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData("0." + new string('0', 15)+"1", NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData("0." + new string('0', 16)+"1", NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData("0." + new string('0', 299)+"1", NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData("0." + new string('0', 300)+"1", NumberStyles.Float|NumberStyles.AllowThousands, en), + CreateParseData(-12345.678910, NumberStyles.Float, en, "e"), + CreateParseData(12345.678910, NumberStyles.Float, en, "E"), + CreateParseData(12345.678910, NumberStyles.Float, en, "E5"), + CreateParseData(-12345.678910, NumberStyles.Float, en, "f"), + CreateParseData(12345.678910, NumberStyles.Float, en, "F"), + CreateParseData(12345.678912, NumberStyles.Float, en, "F20"), + CreateParseData(12345.678910, NumberStyles.Float, en, "G"), + CreateParseData(-12345.678910, NumberStyles.Float, en, "g"), + CreateParseData(12345.678910, NumberStyles.Float, en, "g5"), + CreateParseData(12345.678910, NumberStyles.Float |NumberStyles.AllowThousands, en, "n"), + CreateParseData(12345.678910, NumberStyles.Float |NumberStyles.AllowThousands, en, "N"), + CreateParseData(12345.678910, NumberStyles.Float |NumberStyles.AllowThousands, en, "N5"), + CreateParseData(12345.678910, NumberStyles.Float, en, "R"), + CreateParseData(-12345.678910, NumberStyles.Float, en, "r"), + CreateParseData(-Math.PI, NumberStyles.Float, en, "f15"), + CreateParseData(-Math.PI, NumberStyles.Float, en, "f16"), + CreateParseData(-Math.PI, NumberStyles.Float, en, "f17"), + CreateParseData(Math.PI, NumberStyles.Float, en, "f25"), + CreateParseData("3.141592653589793238", NumberStyles.Float, en), + CreateParseData("-3.141592653589793238", NumberStyles.Float, en), + CreateParseData("0.017453292519943295", NumberStyles.Float, en), + CreateParseData(-Math.PI, NumberStyles.Float, en, "r"), + CreateParseData(sv.NumberFormat.NaNSymbol, NumberStyles.Float, sv), + CreateParseData(sv.NumberFormat.PositiveInfinitySymbol, NumberStyles.Float, sv), + CreateParseData(sv.NumberFormat.NegativeInfinitySymbol, NumberStyles.Float, sv), + }; + + private static DoubleData CreateParseData(double value, + NumberStyles styles, + IFormatProvider culture, + string format) + { + return new DoubleData(value.ToString(format, culture), styles, culture); + } + + private static readonly IReadOnlyList Errors = new[] + { + CreateParseData("e1", NumberStyles.Float, en), + CreateParseData(" 1", NumberStyles.None, en), + CreateParseData("-1", NumberStyles.None, en), + CreateParseData(".1", NumberStyles.None, en), + CreateParseData(",.1", NumberStyles.Float, en), + CreateParseData(new string('1', 311), NumberStyles.Float, en), + //Add("1.", NumberStyles.Float | NumberStyles.AllowHexSpecifier, en), + CreateParseData(".", NumberStyles.Float, en), + //Add("+1,2", NumberStyles.Float, en), + //Add("+1.2", NumberStyles.Float, sv), + CreateParseData("+1.2e3", NumberStyles.None | NumberStyles.AllowDecimalPoint | NumberStyles.AllowLeadingSign, en), + }; + + private static DoubleData CreateParseData(string text, + NumberStyles styles, + CultureInfo culture) + { + return new DoubleData(text, styles, culture); + } + + public class DoubleData + { + public readonly string Text; + public readonly NumberStyles Styles; + public readonly IFormatProvider Culture; + + public DoubleData(string text, + NumberStyles styles, + IFormatProvider culture) + { + this.Text = text; + this.Styles = styles; + this.Culture = culture; + } + + public override string ToString() + { + return $"Text: {this.Text}, Styles: {this.Styles}, Culture: {this.Culture}"; + } + } + + #endregion TestData + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/Extensions/ParseExt.cs b/Gu.Units.Tests/Internals/Parsing/Extensions/ParseExt.cs new file mode 100644 index 00000000..3b2e9138 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/Extensions/ParseExt.cs @@ -0,0 +1,54 @@ +namespace Gu.Units.Tests.Internals.Parsing.Sources +{ + using System; + using System.Globalization; + + public static class ParseExt + { + public static object Parse(this ISuccessData data, string text) + { + var parseMethod = data.Type.GetMethod( + nameof(Length.Parse), + new[] { typeof(string) }); + return parseMethod.Invoke(null, new object[] { text }); + } + + public static object Parse(this ISuccessData data, string text, + CultureInfo cultureInfo) + { + var name = nameof(Length.Parse); + var parseMethod = data.Type.GetMethod( + name, + new[] { typeof(string), typeof(IFormatProvider) }); + return parseMethod.Invoke(null, new object[] { text, cultureInfo }); + } + + public static bool TryParse(this ISuccessData data, string text, + out object actual) + { + var tryParseMethod = data.Type.GetMethod( + nameof(Length.TryParse), + new[] { typeof(string), data.Type.MakeByRefType() }); + actual = null; + var parameters = new[] { text, actual }; + var success = (bool)tryParseMethod.Invoke(null, parameters); + actual = parameters[1]; + return success; + } + + public static bool TryParse(this ISuccessData data, string text, + CultureInfo cultureInfo, + out object actual) + { + var parseMethod = data.Type.GetMethod( + nameof(Length.TryParse), + new[] { typeof(string), typeof(IFormatProvider), data.Type.MakeByRefType() }); + actual = null; + var parameters = new object[] { text, cultureInfo, actual }; + var success = (bool)parseMethod.Invoke(null, parameters); + actual = parameters[2]; + return success; + } + + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/Extensions/ThreadExt.cs b/Gu.Units.Tests/Internals/Parsing/Extensions/ThreadExt.cs new file mode 100644 index 00000000..78b36e88 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/Extensions/ThreadExt.cs @@ -0,0 +1,32 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + using System.Globalization; + using System.Threading; + + public static class ThreadExt + { + public static IDisposable UsingTempCulture(this Thread thread, CultureInfo culture) + { + return new TempCulture(thread, culture); + } + + private class TempCulture : IDisposable + { + private readonly Thread _thread; + private readonly CultureInfo _current; + + public TempCulture(Thread thread, CultureInfo culture) + { + this._thread = thread; + this._current = thread.CurrentCulture; + this._thread.CurrentCulture = culture; + } + + public void Dispose() + { + this._thread.CurrentCulture = this._current; + } + } + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/IntReaderTests.cs b/Gu.Units.Tests/Internals/Parsing/IntReaderTests.cs new file mode 100644 index 00000000..4f97fcd0 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/IntReaderTests.cs @@ -0,0 +1,78 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + using System.Collections.Generic; + using NUnit.Framework; + + public class IntReaderTests + { + [TestCaseSource(nameof(HappyPaths))] + public void ParseSuccess(SuccessData data) + { + int pos = data.Start; + var actual = IntReader.ReadInt32(data.Text, ref pos); + Assert.AreEqual(actual, data.Expected); + Assert.AreEqual(pos, data.ExpectedEnd); + } + + [TestCaseSource(nameof(Errors))] + public void ParseError(ErrorData data) + { + int pos = data.Start; + Assert.Throws(() => IntReader.ReadInt32(data.Text, ref pos)); + Assert.AreEqual(pos, data.ExpectedEnd); + } + + [TestCaseSource(nameof(HappyPaths))] + public void TryParseSuccess(SuccessData data) + { + int pos = data.Start; + int actual; + var success = IntReader.TryReadInt32(data.Text, ref pos, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + Assert.AreEqual(data.ExpectedEnd, pos); + } + + [TestCaseSource(nameof(Errors))] + public void TryParseError(ErrorData data) + { + int pos = data.Start; + int actual; + var success = IntReader.TryReadInt32(data.Text, ref pos, out actual); + Assert.AreEqual(false, success); + Assert.AreEqual(data.Expected, actual); + Assert.AreEqual(data.ExpectedEnd, pos); + } + + private static readonly IReadOnlyList> HappyPaths = new[] + { + SuccessData.Create("1", 0, 1, 1), + SuccessData.Create("01", 0, 1, 2), + SuccessData.Create("12", 0, 12, 2), + SuccessData.Create(" 12", 1, 12, 3), + SuccessData.Create(" 12 ", 1, 12, 3), + SuccessData.Create("-1", 0, -1, 2), + SuccessData.Create("-12", 0, -12, 3), + SuccessData.Create("12345", 0, 12345, 5), + SuccessData.Create("67890", 0, 67890, 5), + SuccessData.Create(int.MaxValue.ToString(), 0, int.MaxValue, int.MaxValue.ToString().Length), + SuccessData.Create(" " + int.MaxValue, 1, int.MaxValue, int.MaxValue.ToString().Length + 1), + SuccessData.Create(" " + int.MaxValue + " ", 1, int.MaxValue, int.MaxValue.ToString().Length + 1), + SuccessData.Create(" " + int.MaxValue + "9", 1, int.MaxValue, int.MaxValue.ToString().Length + 1), + SuccessData.Create(int.MinValue.ToString(), 0, int.MinValue, int.MinValue.ToString().Length), + SuccessData.Create(" " + int.MinValue, 1, int.MinValue, int.MinValue.ToString().Length + 1), + SuccessData.Create(" " + int.MinValue + " ", 1, int.MinValue, int.MinValue.ToString().Length + 1), + SuccessData.Create(" " + int.MinValue + "9", 1, int.MinValue, int.MinValue.ToString().Length + 1), + }; + + private static readonly IReadOnlyList> Errors = new[] + { + ErrorData.Create("abc", 0), + ErrorData.Create("abc", 1), + ErrorData.Create("abc", 2), + ErrorData.Create(((long)int.MinValue-1).ToString(), 0), // less than int.min + ErrorData.Create(((long)int.MaxValue+1).ToString(), 0), // greater than int.max + }; + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/ParseLengthTests.cs b/Gu.Units.Tests/Internals/Parsing/ParseLengthTests.cs new file mode 100644 index 00000000..66190f93 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/ParseLengthTests.cs @@ -0,0 +1,146 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Threading; + using NUnit.Framework; + + public class ParseLengthTests + { + [TestCaseSource(nameof(HappyPaths))] + public void ParseLengthSuccess(SuccessData data) + { + var numberStyles = NumberStyles.Float; + + var length = QuantityParser.Parse(data.Text, Length.From, numberStyles, data.CultureInfo); + Assert.AreEqual(data.Expected, length); + + length = Length.Parse(data.Text, numberStyles, data.CultureInfo); + Assert.AreEqual(data.Expected, length); + + length = Length.Parse(data.Text, data.CultureInfo); + Assert.AreEqual(data.Expected, length); + + using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo)) + { + length = Length.Parse(data.Text); + Assert.AreEqual(data.Expected, length); + } + } + + [TestCaseSource(nameof(Errors))] + public void ParseLengthThrows(ErrorData data) + { + var numberStyles = NumberStyles.Float; + var ex = Assert.Throws(() => QuantityParser.Parse(data.Text, Length.From, numberStyles, data.CultureInfo)); + Assert.AreEqual(data.ExpectedMessage, ex.Message); + + ex = Assert.Throws(() => Length.Parse(data.Text, numberStyles, data.CultureInfo)); + Assert.AreEqual(data.ExpectedMessage, ex.Message); + + ex = Assert.Throws(() => Length.Parse(data.Text, data.CultureInfo)); + Assert.AreEqual(data.ExpectedMessage, ex.Message); + + using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo)) + { + ex = Assert.Throws(() => Length.Parse(data.Text)); + Assert.AreEqual(data.ExpectedMessage, ex.Message); + } + } + + [TestCaseSource(nameof(HappyPaths))] + public void TryParseLengthSuccess(SuccessData data) + { + Length actual; + var success = QuantityParser.TryParse( + data.Text, + Length.From, + NumberStyles.Float, + data.CultureInfo, + out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + + success = Length.TryParse(data.Text, NumberStyles.Float, data.CultureInfo, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + + success = Length.TryParse(data.Text, data.CultureInfo, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + + using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo)) + { + success = Length.TryParse(data.Text, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + } + } + + [TestCaseSource(nameof(Errors))] + public void TryParseLengthFails(ErrorData data) + { + Length length; + var success = QuantityParser.TryParse( + data.Text, + Length.From, + NumberStyles.Float, + data.CultureInfo, + out length); + Assert.AreEqual(false, success); + Assert.AreEqual(data.Expected, length); + + success = Length.TryParse(data.Text, NumberStyles.Float, data.CultureInfo, out length); + Assert.AreEqual(false, success); + Assert.AreEqual(data.Expected, length); + + success = Length.TryParse(data.Text, data.CultureInfo, out length); + Assert.AreEqual(false, success); + Assert.AreEqual(data.Expected, length); + + using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo)) + { + success = Length.TryParse(data.Text, out length); + Assert.AreEqual(false, success); + Assert.AreEqual(data.Expected, length); + } + } + + private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; // keeping this here for copy pasting + private static readonly CultureInfo en = CultureInfo.GetCultureInfo("en-US"); + private static readonly CultureInfo sv = CultureInfo.GetCultureInfo("sv-SE"); + + private static readonly IReadOnlyList> HappyPaths = new SuccessData[] + { + SuccessData.Create("1m", en, Length.FromMetres(1)), + SuccessData.Create("1m", sv, Length.FromMetres(1)), + SuccessData.Create("-1m", en, Length.FromMetres(-1)), + SuccessData.Create("-1m", sv, Length.FromMetres(-1)), + SuccessData.Create("1.2m", en,Length.FromMetres( 1.2)), + SuccessData.Create(" 1.2m", en,Length.FromMetres( 1.2)), + SuccessData.Create("1.2 m", en,Length.FromMetres( 1.2)), + SuccessData.Create("1.2m ", en,Length.FromMetres( 1.2)), + SuccessData.Create(" 1.2 m ", en,Length.FromMetres( 1.2)), + SuccessData.Create("1,2m", sv,Length.FromMetres( 1.2)), + SuccessData.Create("1e3m", en,Length.FromMetres( 1e3)), + SuccessData.Create("1E3m",en,Length.FromMetres( 1e3)), + SuccessData.Create("1e+3m",en,Length.FromMetres( 1e+3)), + SuccessData.Create("1E+3m",en,Length.FromMetres( 1E+3)), + SuccessData.Create("1.2e-3m", en,Length.FromMetres( 1.2e-3)), + SuccessData.Create("1.2E-3m", en, Length.FromMetres(1.2e-3)), + SuccessData.Create(" 1m",en, Length.FromMetres(1)), + SuccessData.Create("1 m",en,Length.FromMetres( 1)), + SuccessData.Create("1m ",en,Length.FromMetres( 1)), + SuccessData.Create("1.2mm",en, Length.FromMillimetres(1.2)), + SuccessData.Create("1.2cm",en,Length.FromCentimetres(1.2)), + }; + + private static readonly IReadOnlyList> Errors = new ErrorData[] + { + ErrorData.Create("1.2m", sv, "Could not parse the unit value from: 1.2m"), + ErrorData.Create("1.2", en, "Could not parse the unit value from: 1.2"), + ErrorData.Create("1,2m", en, "Could not parse the unit value from: 1,2m"), + }; + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/ParseRoundtripTests.cs b/Gu.Units.Tests/Internals/Parsing/ParseRoundtripTests.cs new file mode 100644 index 00000000..dfc5693d --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/ParseRoundtripTests.cs @@ -0,0 +1,94 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Threading; + using NUnit.Framework; + using Sources; + + public class ParseRoundtripTests + { + [TestCaseSource(nameof(HappyPaths))] + public void ParseRoundtrip(ISuccessData data) + { + using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo)) + { + var actual = data.Parse(data.Text); + Assert.AreEqual(data.Expected, actual); + var toString = actual.ToString(); + var roundtripped = data.Parse(toString); + Assert.AreEqual(data.Expected, roundtripped); + } + } + + [TestCaseSource(nameof(HappyPaths))] + public void ParseRoundtripWithCulture(ISuccessData data) + { + var actual = data.Parse(data.Text, data.CultureInfo); + Assert.AreEqual(data.Expected, actual); + var toString = ((IFormattable)actual).ToString(null, data.CultureInfo); + var roundtripped = data.Parse(toString, data.CultureInfo); + Assert.AreEqual(data.Expected, roundtripped); + } + + [TestCaseSource(nameof(HappyPaths))] + public void TryParseRoundtrip(ISuccessData data) + { + using (Thread.CurrentThread.UsingTempCulture(data.CultureInfo)) + { + object actual; + var success = data.TryParse(data.Text, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + var toString = actual.ToString(); + success = data.TryParse(toString, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + } + } + + [TestCaseSource(nameof(HappyPaths))] + public void TryParseRoundtripWithCulture(ISuccessData data) + { + object actual; + var success = data.TryParse(data.Text, data.CultureInfo, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + var toString = ((IFormattable)actual).ToString(null, data.CultureInfo); + success = data.TryParse(data.Text, data.CultureInfo, out actual); + Assert.AreEqual(data.Expected, actual); + Assert.AreEqual(true, success); + } + + private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; // keeping this here for copy pasting + private static readonly CultureInfo en = CultureInfo.GetCultureInfo("en-US"); + private static readonly CultureInfo sv = CultureInfo.GetCultureInfo("sv-SE"); + + private static readonly IReadOnlyList HappyPaths = new ISuccessData[] + { + SuccessData.Create("1.2m^2", en, Area.FromSquareMetres(1.2)), + SuccessData.Create("1.2m²", en, Area.FromSquareMetres(1.2)), + SuccessData.Create("1,2m²", sv, Area.FromSquareMetres(1.2)), + SuccessData.Create("1.2s", en, Time.FromSeconds(1.2)), + SuccessData.Create("1.2h", en, Time.FromHours(1.2)), + SuccessData.Create("1.2ms", en, Time.FromMilliseconds(1.2)), + SuccessData.Create("1.2kg", en, Mass.FromKilograms(1.2)), + SuccessData.Create("1.2g", en, Mass.FromGrams(1.2)), + SuccessData.Create("1.2m³", en, Volume.FromCubicMetres(1.2)), + SuccessData.Create("1.2m^3", en, Volume.FromCubicMetres(1.2)), + SuccessData.Create("1.2m/s", en, Speed.FromMetresPerSecond(1.2)), + SuccessData.Create("1.2m⋅s⁻¹", en, Speed.FromMetresPerSecond(1.2)), + SuccessData.Create("1.2m*s⁻¹", en, Speed.FromMetresPerSecond(1.2)), + SuccessData.Create("1.2m¹⋅s⁻¹", en, Speed.FromMetresPerSecond(1.2)), + SuccessData.Create("1.2m^1⋅s⁻¹", en, Speed.FromMetresPerSecond(1.2)), + SuccessData.Create("1.2m^1⋅s^-1", en, Speed.FromMetresPerSecond(1.2)), + SuccessData.Create("1.2m^1/s^2", en, Acceleration.FromMetresPerSecondSquared(1.2)), + SuccessData.Create("1.2m/s^2", en, Acceleration.FromMetresPerSecondSquared(1.2)), + SuccessData.Create("1.2 m/s^2", en, Acceleration.FromMetresPerSecondSquared(1.2)), + SuccessData.Create("1.2 m / s^2", en, Acceleration.FromMetresPerSecondSquared(1.2)), + SuccessData.Create("1.2 m / s²", en, Acceleration.FromMetresPerSecondSquared(1.2)), + SuccessData.Create("1.2 mm / s²", en, Acceleration.FromMillimetresPerSecondSquared(1.2)), + }; + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/PowerReaderTests.cs b/Gu.Units.Tests/Internals/Parsing/PowerReaderTests.cs new file mode 100644 index 00000000..beb0328c --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/PowerReaderTests.cs @@ -0,0 +1,48 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System.Collections.Generic; + using NUnit.Framework; + + public class PowerReaderTests + { + [TestCaseSource(nameof(HappyPaths))] + public void ReadSuccess(SuccessData data) + { + var end = data.Start; + var actual = PowerReader.Read(data.Text, ref end); + Assert.AreEqual(data.Expected, actual); + Assert.AreEqual(data.ExpectedEnd, end); + } + + [TestCaseSource(nameof(HappyPaths))] + public void TryReadSuccess(SuccessData data) + { + var end = data.Start; + int actual; + var success = PowerReader.TryRead(data.Text, ref end, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + Assert.AreEqual(data.ExpectedEnd, end); + } + + private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; // keeping this here for copy pasting + private static readonly IReadOnlyList> HappyPaths = new[] + { + SuccessData.Create("mm", 2, 1, 2), + SuccessData.Create("mm^2", 2, 2, 4), + SuccessData.Create("mm^-2", 2, -2, 5), + SuccessData.Create("mm⁰", 2, 0, 3), + SuccessData.Create("mm²", 2, 2, 3), + SuccessData.Create("mm⁺²", 2, 2, 4), + SuccessData.Create("mm⁺¹²", 2, 12, 5), + SuccessData.Create("mm¹²", 2, 12, 4), + SuccessData.Create("mm⁻¹²", 2, -12, 5), + SuccessData.Create("mm", 2, 1, 2), + }; + + private static readonly IReadOnlyList> ErrorSource = new[] + { + ErrorData.Create("mm^--2", 0), + }; + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/SkipperTests.cs b/Gu.Units.Tests/Internals/Parsing/SkipperTests.cs new file mode 100644 index 00000000..747a6572 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/SkipperTests.cs @@ -0,0 +1,31 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using NUnit.Framework; + + public class SkipperTests + { + [TestCase("abc", "abc", 0, 3)] + [TestCase("abc", "a", 0, 1)] + [TestCase(" abc", "a", 1, 2)] + [TestCase(" abc", "ab", 1, 3)] + [TestCase(" abc", "abc", 1, 4)] + public void TrySkipSuccess(string text, string toSkip, int pos, int expectedPos) + { + var success = Skipper.TrySkip(text, ref pos, toSkip); + Assert.AreEqual(true, success); + Assert.AreEqual(expectedPos, pos); + } + + [TestCase("abc", null, 0)] + [TestCase(" abc", "", 1)] + [TestCase("abc", "b", 0)] + [TestCase("abc", "bc", 0)] + public void TrySkipError(string text, string toSkip, int pos) + { + var expectedPos = pos; + var success = Skipper.TrySkip(text, ref pos, toSkip); + Assert.AreEqual(false, success); + Assert.AreEqual(expectedPos, pos); + } + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData.cs b/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData.cs new file mode 100644 index 00000000..29c651c0 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData.cs @@ -0,0 +1,49 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System.Collections.Generic; + using System.Globalization; + + public static class ErrorData + { + public static ErrorData Create(string text, + int start, + T expected, + string expectedMessage) + { + return new ErrorData(text, start, expected, start, expectedMessage); + } + + public static ErrorData Create(string text, + int start, + string expectedMessage) + { + return new ErrorData(text, start, default(T), start, expectedMessage); + } + + public static ErrorData Create(string text, int start) + { + return new ErrorData(text, start, default(T), start, null); + } + + public static ErrorData Create(string text, CultureInfo cultureInfo) + { + return new ErrorData(text, cultureInfo, 0, default(T), 0, null); + } + + public static ErrorData Create(string text, CultureInfo cultureInfo, string expectedMessage) + { + return new ErrorData(text, cultureInfo, 0, default(T), 0, expectedMessage); + } + + public static ErrorData Create(string text, CultureInfo cultureInfo, int start) + { + return new ErrorData(text, cultureInfo, start, default(T), start, null); + } + + internal static ErrorData> CreateForSymbol(string text) + { + IReadOnlyList expected = new SymbolAndPower[0]; + return new ErrorData>(text, 0, expected, 0, null); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData{T}.cs b/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData{T}.cs new file mode 100644 index 00000000..cd140273 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/Sources/ErrorData{T}.cs @@ -0,0 +1,32 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System.Globalization; + + public class ErrorData : SuccessData, IErrorData + { + public ErrorData(string text, + int start, + T expected, + int expectedEnd, + string expectedMessage) + : base(text, start, expected, expectedEnd) + { + ExpectedMessage = expectedMessage; + } + + public ErrorData(string text, + CultureInfo cultureInfo, + int start, + T expected, + int expectedEnd, + string expectedMessage) + : base(text, cultureInfo, start, expected, expectedEnd) + { + ExpectedMessage = expectedMessage; + } + + object IErrorData.Expected => Expected; + + public string ExpectedMessage { get; } + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/IErrorData.cs b/Gu.Units.Tests/Internals/Parsing/Sources/IErrorData.cs new file mode 100644 index 00000000..d845ec8d --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/Sources/IErrorData.cs @@ -0,0 +1,19 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + + public interface IErrorData + { + Type Type { get; } + + object Expected { get; } + + string ExpectedMessage { get; } + + string Text { get; } + + int Start { get; } + + int ExpectedEnd { get; } + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/ISuccessData.cs b/Gu.Units.Tests/Internals/Parsing/Sources/ISuccessData.cs new file mode 100644 index 00000000..5c3d18ce --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/Sources/ISuccessData.cs @@ -0,0 +1,20 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + using System.Globalization; + + public interface ISuccessData + { + Type Type { get; } + + object Expected { get; } + + int ExpectedEnd { get; } + + int Start { get; } + + string Text { get; } + + CultureInfo CultureInfo { get; } + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData.cs b/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData.cs new file mode 100644 index 00000000..8fc1c88b --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData.cs @@ -0,0 +1,40 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System.Collections.Generic; + using System.Globalization; + + public static class SuccessData + { + public static SuccessData Create(string text, + int start, + T expected, + int expectedEnd) + { + return new SuccessData(text, start, expected, expectedEnd); + } + + public static SuccessData Create(string text, T expected) + { + return new SuccessData(text, CultureInfo.InvariantCulture, 0, expected, text.Length); + } + + public static SuccessData Create(string text, CultureInfo cultureInfo, T expected) + { + return new SuccessData(text, cultureInfo, 0, expected, text.Length); + } + + public static SuccessData Create(string text, + CultureInfo cultureInfo, + int start, + T expected, + int expectedEnd) + { + return new SuccessData(text, cultureInfo, start, expected, expectedEnd); + } + + internal static SuccessData> Create(string text, params SymbolAndPower[] expected) + { + return new SuccessData>(text, 0, expected, text.Length); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData{T}.cs b/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData{T}.cs new file mode 100644 index 00000000..a3d4ef4e --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/Sources/SuccessData{T}.cs @@ -0,0 +1,77 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + using System.Collections.Generic; + using System.Globalization; + + public class SuccessData : ISuccessData + { + public SuccessData(string text, int start, T expected, int expectedEnd) + : this(text, CultureInfo.InvariantCulture, start, expected, expectedEnd) + { + } + + public SuccessData(string text, + CultureInfo cultureInfo, + int start, + T expected, + int expectedEnd) + { + Text = text; + CultureInfo = cultureInfo; + Start = start; + Expected = expected; + ExpectedEnd = expectedEnd; + } + + public string Text { get; } + + public CultureInfo CultureInfo { get; } + + public int Start { get; } + + public T Expected { get; } + + public Type Type => typeof (T); + + object ISuccessData.Expected => Expected; + + public int ExpectedEnd { get; } + + public override string ToString() + { + if (CultureInfo == null) + { + return $"Text: {Text}, Start: {Start}, Expected {ToString(typeof(T))}: {ToString(Expected)}, ExpectedEnd: {ExpectedEnd}"; + } + + return $"Text: {Text}, Culture: {CultureInfo.Name} Start: {Start}, Expected {ToString(typeof(T))}: {ToString(Expected)}, ExpectedEnd: {ExpectedEnd}"; + } + + private static string ToString(Type type) + { + if (type == typeof(IReadOnlyList)) + { + return string.Empty; + } + + return $"({type.Name})"; + } + + private static string ToString(T expected) + { + if (expected == null) + { + return "null"; + } + + var saps = expected as IEnumerable; + if (saps != null) + { + return $"{{{string.Join(", ", saps)}}}"; + } + + return expected.ToString(); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserSetTests.cs b/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserSetTests.cs new file mode 100644 index 00000000..8d664826 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserSetTests.cs @@ -0,0 +1,59 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System.Collections; + using System.Collections.Generic; + using NUnit.Framework; + + public class SymbolAndPowerParserSetTests + { + [TestCaseSource(nameof(HappyPaths))] + public void TryReadSuccess(ISuccessData data) + { + var pos = data.Start; + IReadOnlyList actual; + var success = SymbolAndPowerReader.TryRead(data.Text, ref pos, out actual); + //Console.WriteLine("expected: {0}", data.ToString(data.Tokens)); + //Console.WriteLine("actual: {0}", data.ToString(actual)); + Assert.AreEqual(true, success); + Assert.AreEqual(data.ExpectedEnd, pos); + CollectionAssert.AreEqual((IEnumerable)data.Expected, actual); + } + + [TestCaseSource(nameof(Errors))] + public void TryTokenizeError(IErrorData data) + { + var pos = data.Start; + IReadOnlyList actual; + var success = SymbolAndPowerReader.TryRead(data.Text, ref pos, out actual); + Assert.AreEqual(false, success); + Assert.AreEqual(data.Start, pos); + CollectionAssert.AreEqual(null, actual); + } + + private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; + + internal static readonly IReadOnlyList>> HappyPaths = new[] + { + SuccessData.Create("m", new SymbolAndPower("m", 1)), + SuccessData.Create(" m ", new SymbolAndPower("m", 1)), + SuccessData.Create("m^2", new SymbolAndPower("m", 2)), + SuccessData.Create(" m ^ 2", new SymbolAndPower("m", 2)), + SuccessData.Create(" m ^ -2", new SymbolAndPower("m", -2)), + SuccessData.Create("m^1/s^2", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)), + SuccessData.Create("m¹/s²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)), + SuccessData.Create("m⁺¹/s²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)), + SuccessData.Create("m⁺¹/s²*g", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2), new SymbolAndPower("g", -1)), + SuccessData.Create("m¹⋅s⁻²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2)), + SuccessData.Create("m⁻¹⋅s⁻²", new SymbolAndPower("m", -1), new SymbolAndPower("s", -2)), + }; + + internal static readonly IReadOnlyList>> Errors = new[] + { + ErrorData.CreateForSymbol("m⁻¹/s⁻²"), + ErrorData.CreateForSymbol("m⁻⁻¹"), + ErrorData.CreateForSymbol("m⁺⁻¹"), + ErrorData.CreateForSymbol("m+⁻¹"), + ErrorData.CreateForSymbol("m^¹/s⁻²"), + }; + } +} diff --git a/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserTests.cs b/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserTests.cs new file mode 100644 index 00000000..697f9404 --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/SymbolAndPowerParserTests.cs @@ -0,0 +1,79 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using System; + using System.Collections.Generic; + using NUnit.Framework; + + public class SymbolAndPowerParserTests + { + [TestCaseSource(nameof(HappyPaths))] + public void ParseSuccess(ISuccessData data) + { + var pos = data.Start; + var actual = SymbolAndPowerReader.Read(data.Text, ref pos); + Assert.AreEqual(data.Expected, actual); + Assert.AreEqual(data.ExpectedEnd, pos); + } + + [TestCaseSource(nameof(Errors))] + public void ParseError(IErrorData data) + { + var pos = data.Start; + Assert.Throws(() => SymbolAndPowerReader.Read(data.Text, ref pos)); + Assert.AreEqual(data.ExpectedEnd, pos); + } + + [TestCaseSource(nameof(HappyPaths))] + public void TryParseSuccess(ISuccessData data) + { + var pos = data.Start; + SymbolAndPower actual; + var success = SymbolAndPowerReader.TryRead(data.Text, ref pos, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(data.Expected, actual); + Assert.AreEqual(data.ExpectedEnd, pos); + } + + [TestCaseSource(nameof(Errors))] + public void TryParseError(IErrorData data) + { + var pos = data.Start; + SymbolAndPower sap; + var success = SymbolAndPowerReader.TryRead(data.Text, ref pos, out sap); + Assert.AreEqual(false, success); + Assert.AreEqual(default(SymbolAndPower), sap); + Assert.AreEqual(data.ExpectedEnd, pos); + } + + private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; + + private static readonly IReadOnlyList> HappyPaths = new[] + { + SuccessData.Create("m", 0, new SymbolAndPower("m", 1), 1), + SuccessData.Create(" m", 1, new SymbolAndPower("m", 1), 2), + SuccessData.Create("m^1", 0, new SymbolAndPower("m", 1), 3), + SuccessData.Create(" m ^ 1", 1, new SymbolAndPower("m", 1), 6), + SuccessData.Create("m⁻¹", 0, new SymbolAndPower("m", -1), 3), + SuccessData.Create("m^-1", 0, new SymbolAndPower("m", -1), 4), + SuccessData.Create("m¹", 0, new SymbolAndPower("m", 1), 2), + SuccessData.Create("m^2", 0, new SymbolAndPower("m", 2), 3), + SuccessData.Create("m^-2", 0, new SymbolAndPower("m", -2), 4), + SuccessData.Create("m²", 0, new SymbolAndPower("m", 2), 2), + SuccessData.Create("m⁻²", 0, new SymbolAndPower("m", -2), 3), + SuccessData.Create("m¹", 0, new SymbolAndPower("m", 1), 2), + SuccessData.Create("m³", 0, new SymbolAndPower("m", 3), 2), + SuccessData.Create("°", 0, new SymbolAndPower("°", 1), 1) + }; + + private static readonly IReadOnlyList> Errors = new[] + { + ErrorData.Create("m¹²", 0), + ErrorData.Create("m⁻¹²", 0), + ErrorData.Create("m⁻⁻2", 0), + ErrorData.Create("m^12", 0), + ErrorData.Create("m^-12", 0), + ErrorData.Create("m^--2", 0), + //ErrorData.Create("m-", 0), + }; + } +} \ No newline at end of file diff --git a/Gu.Units.Tests/Internals/Parsing/UnitParserTests.cs b/Gu.Units.Tests/Internals/Parsing/UnitParserTests.cs new file mode 100644 index 00000000..ee7fd34a --- /dev/null +++ b/Gu.Units.Tests/Internals/Parsing/UnitParserTests.cs @@ -0,0 +1,70 @@ +namespace Gu.Units.Tests.Internals.Parsing +{ + using NUnit.Framework; + + public class UnitParserTests + { + private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; // keeping this here for copy pasting + + [TestCase("m")] + public void ParseMetres(string s) + { + var actual = LengthUnit.Parse(s); + var expected = LengthUnit.Metres; + Assert.AreEqual(expected, actual); + + actual = UnitParser.Parse(s); + Assert.AreEqual(expected, actual); + + var success = LengthUnit.TryParse(s, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(expected, actual); + + success = UnitParser.TryParse(s, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(expected, actual); + } + + [TestCase("m^2")] + [TestCase("m²")] + [TestCase("m⁺²")] + public void ParseSquareMetres(string s) + { + var actual = AreaUnit.Parse(s); + var expected = AreaUnit.SquareMetres; + Assert.AreEqual(expected, actual); + + actual = UnitParser.Parse(s); + Assert.AreEqual(expected, actual); + + var success = AreaUnit.TryParse(s, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(expected, actual); + + success = UnitParser.TryParse(s, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(expected, actual); + } + + [TestCase("mm^2")] + [TestCase("mm²")] + [TestCase("mm⁺²")] + public void ParseSquareMillimetres(string s) + { + var actual = AreaUnit.Parse(s); + var squareMillimetres = AreaUnit.SquareMillimetres; + Assert.AreEqual(squareMillimetres, actual); + + actual = UnitParser.Parse(s); + Assert.AreEqual(squareMillimetres, actual); + + var success = AreaUnit.TryParse(s, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(squareMillimetres, actual); + + success = UnitParser.TryParse(s, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(squareMillimetres, actual); + } + } +} diff --git a/Gu.Units.Tests/QuantityTests.cs b/Gu.Units.Tests/LengthTests.cs similarity index 67% rename from Gu.Units.Tests/QuantityTests.cs rename to Gu.Units.Tests/LengthTests.cs index 0687e6b3..8be45df0 100644 --- a/Gu.Units.Tests/QuantityTests.cs +++ b/Gu.Units.Tests/LengthTests.cs @@ -1,11 +1,14 @@ namespace Gu.Units.Tests { + using System.Globalization; + using System.Threading; + using Internals.Parsing; using NUnit.Framework; /// /// Just testing for length assuming the other generated units will work. /// - public class QuantityTests + public class LengthTests { [Test] public void Equality() @@ -18,9 +21,9 @@ public void Equality() Assert.IsFalse(metres1 != metres2); } - [TestCase(1, 2)] - [TestCase(1, 1)] - [TestCase(2, 1)] + [TestCase(1.0, 2.0)] + [TestCase(1.0, 1.0)] + [TestCase(2.0, 1.0)] public void Compare(double d1, double d2) { var metres1 = Length.FromMetres(d1); @@ -38,7 +41,7 @@ public void Addition() var l2 = Length.FromCentimetres(1); var sums = new[] { - l1 + l2, + l1 + l2, l2 + l1 }; var expected = Length.FromCentimetres(101); @@ -66,7 +69,7 @@ public void Multiplication() var l = Length.FromMetres(1); var prods = new[] { - l*2, + l*2, 2*l }; var expected = Length.FromMetres(2); @@ -86,5 +89,25 @@ public void Division() Assert.IsInstanceOf(divided); Assert.AreEqual(expected, divided); } + + [Test] + public new void ToString() + { + var length = Length.FromMetres(1.2); + + using (Thread.CurrentThread.UsingTempCulture(CultureInfo.InvariantCulture)) + { + Assert.AreEqual("1.2\u00A0m", length.ToString()); + Assert.AreEqual("120\u00A0cm", length.ToString(LengthUnit.Centimetres)); + Assert.AreEqual(" 1200.0 mm ", length.ToString(" F1 mm ")); + Assert.AreEqual(" F1 {unit: null}", length.ToString(" F1 ")); + Assert.AreEqual("12.0\u00A0dm", length.ToString("F1", LengthUnit.Decimetres)); + } + + var sv = CultureInfo.GetCultureInfo("sv-SE"); + Assert.AreEqual("1,2\u00A0m", length.ToString(sv)); + Assert.AreEqual(" 1200,0 mm ", length.ToString(" F1 mm ", sv)); + Assert.AreEqual("1200,0\u00A0mm", length.ToString("F1", LengthUnit.Millimetres, sv)); + } } } diff --git a/Gu.Units.Tests/ParserTests.cs b/Gu.Units.Tests/ParserTests.cs deleted file mode 100644 index df5c9012..00000000 --- a/Gu.Units.Tests/ParserTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -namespace Gu.Units.Tests -{ - using System; - using System.Globalization; - using System.Text.RegularExpressions; - using System.Threading; - - using Gu.Units.Tests.Sources; - - using NUnit.Framework; - - public class ParserTests - { - [TestCase("1m", new[] { "sv-se", "en-us" }, 1)] - [TestCase("-1m", new[] { "sv-se", "en-us" }, -1)] - [TestCase("1.2m", new[] { "en-us" }, 1.2)] - [TestCase("1.2m", new[] { "en-us" }, 1.2)] - [TestCase("1,2m", new[] { "sv-se" }, 1.2)] - [TestCase("-1m", new[] { "sv-se", "en-us" }, -1)] - [TestCase("1e3m", new[] { "sv-se", "en-us" }, 1e3)] - [TestCase("1E3m", new[] { "sv-se", "en-us" }, 1e3)] - [TestCase("1e+3m", new[] { "sv-se", "en-us" }, 1e+3)] - [TestCase("1E+3m", new[] { "sv-se", "en-us" }, 1E+3)] - [TestCase("1.2e-3m", new[] { "en-us" }, 1.2e-3)] - [TestCase("1.2E-3m", new[] { "en-us" }, 1.2e-3)] - [TestCase(" 1m", new[] { "sv-se", "en-us" }, 1)] - [TestCase("1 m", new[] { "sv-se", "en-us" }, 1)] - [TestCase("1m ", new[] { "sv-se", "en-us" }, 1)] - [TestCase("1mm", new[] { "sv-se", "en-us" }, 1e-3)] - [TestCase("1cm", new[] { "sv-se", "en-us" }, 1e-2)] - public void ParseLength(string s, string[] cultures, double expected) - { - foreach (var culture in cultures) - { - var cultureInfo = CultureInfo.GetCultureInfo(culture); - var length = Parser.Parse(s, Length.From, NumberStyles.Float, cultureInfo); - Assert.AreEqual(expected, length.Metres); - } - } - - [TestCaseSource(typeof(ParseProvider))] - public void Roundtrip(ParseProvider.ParseData data) - { - Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; - var actual = data.ParseMethod(data.StringValue); - var expected = data.Quantity; - Assert.AreEqual(expected, actual); - var s = actual.ToString(); - var roundtripped = data.ParseMethod(s); - Assert.AreEqual(expected, roundtripped); - } - - [TestCase("mm^2")] - [TestCase("mm²")] - public void AreaUnit_Parse(string s) - { - var actual = AreaUnit.Parse(s); - Assert.AreEqual(AreaUnit.SquareMillimetres, actual); - } - - [TestCaseSource(typeof(TokenSource))] - public void Tokenize(TokenSource.TokenData data) - { - var text = data.Text; - if (data.Tokens == null) - { - Assert.Throws(() => Parser.TokenizeUnit(text)); - } - else - { - var actual = Parser.TokenizeUnit(text); - Console.WriteLine("expected: {0}", data.ToString(data.Tokens)); - Console.WriteLine("actual: {0}", data.ToString(actual)); - CollectionAssert.AreEqual(data.Tokens, actual); - } - } - - [TestCase("1.0cm", "sv-se")] - [TestCase("1,0cm", "en-us")] - public void Exceptions(string s, string culture) - { - var cultureInfo = CultureInfo.GetCultureInfo(culture); - Assert.Throws(() => Parser.Parse(s, Length.From, NumberStyles.Float, cultureInfo)); - } - } -} diff --git a/Gu.Units.Tests/ParserTests.cs.orig b/Gu.Units.Tests/ParserTests.cs.orig deleted file mode 100644 index 3b3973df..00000000 --- a/Gu.Units.Tests/ParserTests.cs.orig +++ /dev/null @@ -1,129 +0,0 @@ -namespace Gu.Units.Tests -{ - using System.Globalization; - using System.Text.RegularExpressions; - using NUnit.Framework; - - public class ParserTests - { -<<<<<<< HEAD - [TestCase("1m", 1)] - [TestCase("-1m", -1)] - [TestCase("1.2m", 1.2)] - [TestCase("1,2m", 1.2)] - [TestCase("-1m", -1)] - [TestCase("1e3m", 1e3)] - [TestCase("-1e3m", -1e3)] - [TestCase("1e+3m", 1e+3)] - [TestCase("1E+3m", 1E+3)] - [TestCase("-1e-3m", -1e-3)] - [TestCase(" 1m", 1)] - [TestCase("1m ", 1)] - [TestCase("1 m", 1)] - [TestCase(" 1 m", 1)] - [TestCase("1 m ", 1)] - [TestCase(" 1 m ", 1)] - [TestCase(" 1 m ", 1)] - [TestCase("1m ", 1)] - [TestCase("1mm", 1e-3)] - [TestCase("1cm", 1e-2)] - public void ParseLength(string s, double expected) -======= - [TestCase("1m", new[] { "sv-se", "en-us" }, 1)] - [TestCase("1.2m", new[] { "en-us" }, 1.2)] - [TestCase("1,2m", new[] { "sv-se" }, 1.2)] - [TestCase("-1m", new[] { "sv-se", "en-us" }, -1)] - [TestCase("1e3m", new[] { "sv-se", "en-us" }, 1e3)] - [TestCase("1E3m", new[] { "sv-se", "en-us" }, 1e3)] - [TestCase("1e+3m", new[] { "sv-se", "en-us" }, 1e+3)] - [TestCase("1E+3m", new[] { "sv-se", "en-us" }, 1E+3)] - [TestCase("1.2e-3m", new[] { "en-us" }, 1.2e-3)] - [TestCase("1.2E-3m", new[] { "en-us" }, 1.2e-3)] - [TestCase(" 1m", new[] { "sv-se", "en-us" }, 1)] - [TestCase("1 m", new[] { "sv-se", "en-us" }, 1)] - [TestCase("1m ", new[] { "sv-se", "en-us" }, 1)] - [TestCase("1mm", new[] { "sv-se", "en-us" }, 1e-3)] - [TestCase("1cm", new[] { "sv-se", "en-us" }, 1e-2)] - public void ParseLength(string s, string[] cultures, double expected) ->>>>>>> Added test for double pattern - { - foreach (var culture in cultures) - { - var cultureInfo = CultureInfo.GetCultureInfo(culture); - var length = Parser.Parse(s, Length.From, cultureInfo); - Assert.AreEqual(expected, length.Meters); - } - } - - [TestCase("1", 1)] - [TestCase(".1", .1)] - [TestCase("1.2", 1.2)] - [TestCase("1.2E+3", 1.2E+3)] - [TestCase("1.2e+3", 1.2E+3)] - [TestCase("1.2E3", 1.2E3)] - [TestCase("1.2e3", 1.2E3)] - [TestCase("1.2E-3", 1.2E-3)] - [TestCase("1.2e-3", 1.2E-3)] - public void DoublePattern(string s, double expected) - { - Assert.IsTrue(Regex.IsMatch(s, Parser.DoublePattern)); - Assert.AreEqual(expected, double.Parse(s, CultureInfo.InvariantCulture)); - } - - [TestCase("1m", 1)] - public void ParseMeters(string s, double expected) - { - var length = UnitParser.Parse(s, Length.From); - Assert.AreEqual(expected, length.Meters); - } - - [TestCase("1cm", 1)] - public void ParseCentimeters(string s, double expected) - { - var length = UnitParser.Parse(s, Length.From); - Assert.AreEqual(expected, length.Centimeters); - } - - [TestCase("1mm", 1)] - public void ParseMillimeters(string s, double expected) - { - var length = UnitParser.Parse(s, Length.From); - Assert.AreEqual(expected, length.Millimeters); - } - - [TestCase("1h", 1)] - public void ParseHours(string s, double expected) - { - var time = UnitParser.Parse(s, Time.From); - Assert.AreEqual(expected, time.Hours); - } - - [TestCase("1s", 1)] - public void ParseSeconds(string s, double expected) - { - var time = UnitParser.Parse(s, Time.From); - Assert.AreEqual(expected, time.Seconds); - } - - [TestCase("1ms", 1)] - public void ParseMilliseconds(string s, double expected) - { - var time = UnitParser.Parse(s, Time.From); - Assert.AreEqual(expected, time.MilliSeconds); - } - - [TestCase("1kN", 1)] - public void ParseKilonewtons(string s, double expected) - { - var force = UnitParser.Parse(s, Force.From); - Assert.AreEqual(expected, force.KiloNewtons); - } - - [TestCase("1N", 1)] - public void ParseNewtons(string s, double expected) - { - var force = UnitParser.Parse(s, Force.From); - Assert.AreEqual(expected, force.Newtons); - } - } -} diff --git a/Gu.Units.Tests/Samples/Samples.cs b/Gu.Units.Tests/Samples/Samples.cs index 35d566c3..c957ae15 100644 --- a/Gu.Units.Tests/Samples/Samples.cs +++ b/Gu.Units.Tests/Samples/Samples.cs @@ -39,10 +39,11 @@ public void ConversionSample() [TestCase("1.2m^1/s¹")] public void ParsingSample(string s) { - var speed = Speed.Parse(s,CultureInfo.InvariantCulture); - Assert.AreEqual(Speed.FromMetresPerSecond(1.2), Speed.Parse(s)); + var speed = Speed.Parse(s, CultureInfo.InvariantCulture); + Assert.AreEqual(Speed.FromMetresPerSecond(1.2), speed); + Assert.IsTrue(Speed.TryParse(s, CultureInfo.InvariantCulture, out speed)); - Assert.AreEqual(Speed.FromMetresPerSecond(1.2), Speed.Parse(s)); + Assert.AreEqual(Speed.FromMetresPerSecond(1.2), speed); } } } diff --git a/Gu.Units.Tests/Sandbox/SandboxTests.cs b/Gu.Units.Tests/Sandbox/SandboxTests.cs deleted file mode 100644 index 8fd4b086..00000000 --- a/Gu.Units.Tests/Sandbox/SandboxTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -namespace Gu.Units.Tests.Sandbox -{ - using System; - using System.Diagnostics; - - using NUnit.Framework; - - public class SandboxTests - { - [Test, Explicit] - public void PerformanceDoublesThenLengths() - { - var sw = Stopwatch.StartNew(); - var n = 10000000; - double sum1 = 0; - for (double i = 0; i < n; i++) - { - sum1 += i; - } - Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum1.GetType().Name, sw.ElapsedMilliseconds); - sw.Restart(); - var sum2 = new Length(1, LengthUnit.Metres); - for (var i = 0; i < n; i++) - { - sum2 += new Length(i, LengthUnit.Metres); - } - Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum2.GetType().Name, sw.ElapsedMilliseconds); - //Summing 1E+007 Doubles took: 11 ms - //Summing 1E+007 Lengths took: 8 ms - } - - [Test, Explicit] - public void PerformanceIntsThenLengths() - { - var sw = Stopwatch.StartNew(); - var n = 10000000; - int sum1 = 0; - for (int i = 0; i < n; i++) - { - sum1 += i; - } - Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum1.GetType().Name, sw.ElapsedMilliseconds); - sw.Restart(); - var sum2 = new Length(1, LengthUnit.Metres); - for (var i = 0; i < n; i++) - { - sum2 += new Length(i, LengthUnit.Metres); - } - Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum2.GetType().Name, sw.ElapsedMilliseconds); - //Summing 1E+007 Int32s took: 5 ms - //Summing 1E+007 Lengths took: 8 ms - } - - [Test, Explicit] - public void PerformanceLengthsThenDoubles() - { - var sw = Stopwatch.StartNew(); - var n = 10000000; - var sum2 = new Length(1, LengthUnit.Metres); - for (var i = 0; i < n; i++) - { - sum2 += new Length(i, LengthUnit.Metres); - } - Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum2.GetType().Name, sw.ElapsedMilliseconds); - sw.Restart(); - double sum1 = 0; - for (double i = 0; i < n; i++) - { - sum1 += i; - } - Console.WriteLine("Summing {0:E0} {1}s took: {2} ms", n, sum1.GetType().Name, sw.ElapsedMilliseconds); - //Summing 1E+007 Lengths took: 8 ms - //Summing 1E+007 Doubles took: 11 ms - } - } -} diff --git a/Gu.Units.Tests/Sources/ConversionProvider.cs b/Gu.Units.Tests/Sources/ConversionProvider.cs index bd31857e..34401f4b 100644 --- a/Gu.Units.Tests/Sources/ConversionProvider.cs +++ b/Gu.Units.Tests/Sources/ConversionProvider.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using System.Threading; + using Internals.Parsing; public class ConversionProvider : List> { @@ -54,12 +55,14 @@ public class Conversion : IConversion public Conversion(string from, string to, Func parser) { - Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-US"); - this._parser = parser; - this.From = @from; - FromQuantity = Parse(from); - this.To = to; - ToQuantity = Parse(to); + using (Thread.CurrentThread.UsingTempCulture(CultureInfo.InvariantCulture)) + { + this._parser = parser; + this.From = @from; + FromQuantity = Parse(from); + this.To = to; + ToQuantity = Parse(to); + } } public T Parse(string s) diff --git a/Gu.Units.Tests/Sources/TokenSource.cs b/Gu.Units.Tests/Sources/TokenSource.cs deleted file mode 100644 index d174996f..00000000 --- a/Gu.Units.Tests/Sources/TokenSource.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace Gu.Units.Tests.Sources -{ - using System.Collections.Generic; - - public class TokenSource : List - { - private const string Superscripts = "⋅⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; - public TokenSource() - { - Add(new TokenData("m", new SymbolAndPower("m", 1))); - Add(new TokenData(" m ", new SymbolAndPower("m", 1))); - Add(new TokenData("m^2", new SymbolAndPower("m", 2))); - Add(new TokenData(" m ^ 2", new SymbolAndPower("m", 2))); - Add(new TokenData(" m ^ -2", new SymbolAndPower("m", -2))); - Add(new TokenData("m^1/s^2", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2))); - Add(new TokenData("m¹/s²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2))); - Add(new TokenData("m⁺¹/s²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2))); - Add(new TokenData("m⁺¹/s²*g", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2), new SymbolAndPower("g", -1))); - Add(new TokenData("m¹⋅s⁻²", new SymbolAndPower("m", 1), new SymbolAndPower("s", -2))); - Add(new TokenData("m⁻¹⋅s⁻²", new SymbolAndPower("m", -1), new SymbolAndPower("s", -2))); - Add(new TokenData("m⁻¹/s⁻²", null)); - Add(new TokenData("m⁻⁻¹/s⁻²", null)); - Add(new TokenData("m⁺⁻¹/s⁻²", null)); - Add(new TokenData("m+⁻¹/s⁻²", null)); - Add(new TokenData("m^¹/s⁻²", null)); - } - - public class TokenData - { - public readonly string Text; - public readonly IReadOnlyList Tokens; - - public TokenData(string text, params SymbolAndPower[] tokens) - { - Text = text; - Tokens = tokens; - } - - public override string ToString() - { - var tokens = "Exception"; - if (Tokens != null) - { - tokens = ToString(Tokens); - } - return string.Format("Text: {0}, Tokens: {1}", Text, Tokens); - } - - public string ToString(IEnumerable tokens) - { - return string.Join(", ", tokens); - } - } - } -} \ No newline at end of file diff --git a/Gu.Units.Tests/SymbolAndPowerTests.cs b/Gu.Units.Tests/SymbolAndPowerTests.cs deleted file mode 100644 index 1b19b803..00000000 --- a/Gu.Units.Tests/SymbolAndPowerTests.cs +++ /dev/null @@ -1,52 +0,0 @@ -namespace Gu.Units.Tests -{ - using System; - using NUnit.Framework; - - public class SymbolAndPowerTests - { - private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; - - internal static readonly Sign[] Signs = { Sign.Positive, Sign.Negative }; - - [TestCase("m", "m", 1)] - [TestCase(" m", "m", 1)] - [TestCase("m^1", "m", 1)] - [TestCase(" m ^ 1", "m", 1)] - [TestCase("m⁻¹", "m", -1)] - [TestCase("m^-1", "m", -1)] - [TestCase("m¹", "m", 1)] - [TestCase("m^2", "m", 2)] - [TestCase("m^-2", "m", -2)] - [TestCase("m²", "m", 2)] - [TestCase("m¹", "m", 1)] - [TestCase("m³", "m", 3)] - [TestCase("m⁹", "m", 9)] - [TestCase("kg⁹", "kg", 9)] - [TestCase("°", "°", 1)] - public void SimpleSymbol(string s, string symbol, int power) - { - foreach (var si in Signs) - { - Sign sign = si; - int pos = 0; - SymbolAndPower sap; - if (power < 0 && sign == Sign.Negative) - { - Assert.Throws(() => SymbolAndPower.Read(s, ref pos, ref sign)); - return; - } - sap = SymbolAndPower.Read(s, ref pos, ref sign); - Assert.AreEqual(symbol, sap.Symbol); - if (si == Sign.Negative) - { - Assert.AreEqual(-1 * power, sap.Power); - } - else - { - Assert.AreEqual(power, sap.Power); - } - } - } - } -} \ No newline at end of file diff --git a/Gu.Units.Tests/Helpers/DummyUnit.cs b/Gu.Units.Tests/TestHelpers/DummyUnit.cs similarity index 89% rename from Gu.Units.Tests/Helpers/DummyUnit.cs rename to Gu.Units.Tests/TestHelpers/DummyUnit.cs index eb03dfdd..bdf0883b 100644 --- a/Gu.Units.Tests/Helpers/DummyUnit.cs +++ b/Gu.Units.Tests/TestHelpers/DummyUnit.cs @@ -3,6 +3,9 @@ namespace Gu.Units.Tests public class DummyUnit : IUnit { public string Symbol { get; private set; } + + public IUnit SiUnit { get; } + public double ToSiUnit(double value) { return 10 * value; diff --git a/Gu.Units.Tests/UnitParserTests.cs.orig b/Gu.Units.Tests/UnitParserTests.cs.orig deleted file mode 100644 index 64c71cfe..00000000 --- a/Gu.Units.Tests/UnitParserTests.cs.orig +++ /dev/null @@ -1,98 +0,0 @@ -namespace Gu.Units.Tests -{ - using NUnit.Framework; - - public class UnitParserTests - { - [TestCase("1m", 1)] -<<<<<<< HEAD - [TestCase("-1m", -1)] - [TestCase("1e3m", 1e3)] - [TestCase("-1e3m", -1e3)] - [TestCase("1e+3m", 1e+3)] - [TestCase("1e-3m", 1e-3)] - [TestCase("-1e-3m", -1e-3)] -======= - [TestCase("1.2m", 1.2)] - [TestCase("1,2m", 1.2)] - [TestCase("-1m", -1)] - [TestCase("1e3m", 1e3)] - [TestCase("1E3m", 1e3)] - [TestCase("1e+3m", 1e+3)] - [TestCase("1E+3m", 1E+3)] - [TestCase("1.2e-3m", 1.2e-3)] - [TestCase("1.2E-3m", 1.2e-3)] ->>>>>>> Changed conversion signature in IUnit - [TestCase(" 1m", 1)] - [TestCase("1m ", 1)] - [TestCase("1 m", 1)] - [TestCase(" 1 m", 1)] - [TestCase("1 m ", 1)] - [TestCase(" 1 m ", 1)] - [TestCase(" 1 m ", 1)] - [TestCase("1m ", 1)] - [TestCase("1mm", 1e-3)] - [TestCase("1cm", 1e-2)] - public void ParseLength(string s, double expected) - { - var length = UnitParser.Parse(s, Length.From); - Assert.AreEqual(expected, length.Meters); - } - - [TestCase("1m", 1)] - public void ParseMeters(string s, double expected) - { - var length = UnitParser.Parse(s, Length.From); - Assert.AreEqual(expected, length.Meters); - } - - [TestCase("1cm", 1)] - public void ParseCentimeters(string s, double expected) - { - var length = UnitParser.Parse(s, Length.From); - Assert.AreEqual(expected, length.Centimeters); - } - - [TestCase("1mm", 1)] - public void ParseMillimeters(string s, double expected) - { - var length = UnitParser.Parse(s, Length.From); - Assert.AreEqual(expected, length.Millimeters); - } - - [TestCase("1h", 1)] - public void ParseHours(string s, double expected) - { - var time = UnitParser.Parse(s, Time.From); - Assert.AreEqual(expected, time.Hours); - } - - [TestCase("1s", 1)] - public void ParseSeconds(string s, double expected) - { - var time = UnitParser.Parse(s, Time.From); - Assert.AreEqual(expected, time.Seconds); - } - - [TestCase("1ms", 1)] - public void ParseMilliseconds(string s, double expected) - { - var time = UnitParser.Parse(s, Time.From); - Assert.AreEqual(expected, time.MilliSeconds); - } - - [TestCase("1kN", 1)] - public void ParseKilonewtons(string s, double expected) - { - var force = UnitParser.Parse(s, Force.From); - Assert.AreEqual(expected, force.KiloNewtons); - } - - [TestCase("1N", 1)] - public void ParseNewtons(string s, double expected) - { - var force = UnitParser.Parse(s, Force.From); - Assert.AreEqual(expected, force.Newtons); - } - } -} diff --git a/Gu.Units.Tests/UnitTests.cs b/Gu.Units.Tests/UnitTests.cs index c6475b31..cd5af042 100644 --- a/Gu.Units.Tests/UnitTests.cs +++ b/Gu.Units.Tests/UnitTests.cs @@ -12,8 +12,7 @@ public class UnitTests public void Roundtrip(IUnit unit) { var s = unit.ToString(); - var parseMethod = unit.GetType() - .GetMethod("Parse", BindingFlags.Static | BindingFlags.Public); + var parseMethod = unit.GetType().GetMethod("Parse", BindingFlags.Static | BindingFlags.Public); var roundtripped = (IUnit)parseMethod.Invoke(null, new object[] { s }); Assert.AreEqual(unit, roundtripped); } diff --git a/Gu.Units.Wpf.Demo/App.xaml b/Gu.Units.Wpf.Demo/App.xaml index c74bc2cb..ab0bc63e 100644 --- a/Gu.Units.Wpf.Demo/App.xaml +++ b/Gu.Units.Wpf.Demo/App.xaml @@ -4,6 +4,29 @@ xmlns:local="clr-namespace:Gu.Units.Wpf.Demo" StartupUri="MainWindow.xaml"> - + + + + + + + + diff --git a/Gu.Units.Wpf.Demo/DoubleControl.cs b/Gu.Units.Wpf.Demo/DoubleControl.cs new file mode 100644 index 00000000..3ac29653 --- /dev/null +++ b/Gu.Units.Wpf.Demo/DoubleControl.cs @@ -0,0 +1,22 @@ +namespace Gu.Units.Wpf.Demo +{ + using System.Windows; + using System.Windows.Controls; + using System.Windows.Data; + + // Dummy control for testing binding of doubles. + public class DoubleControl : Control + { + public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( + "Value", + typeof(double), + typeof(DoubleControl), + new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault) {DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged}); + + public double Value + { + get { return (double)GetValue(ValueProperty); } + set { SetValue(ValueProperty, value); } + } + } +} diff --git a/Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj b/Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj index 3d525787..193033af 100644 --- a/Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj +++ b/Gu.Units.Wpf.Demo/Gu.Units.Wpf.Demo.csproj @@ -48,7 +48,24 @@ MSBuild:Compile Designer + + + InputOptionsView.xaml + + + StringFormatView.xaml + + + SymbolOptions.xaml + + + UnitsOnlyView.xaml + + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -61,6 +78,18 @@ MainWindow.xaml Code + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + diff --git a/Gu.Units.Wpf.Demo/InputOptionsView.xaml b/Gu.Units.Wpf.Demo/InputOptionsView.xaml new file mode 100644 index 00000000..6ce651dd --- /dev/null +++ b/Gu.Units.Wpf.Demo/InputOptionsView.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Gu.Units.Wpf.Demo/InputOptionsView.xaml.cs b/Gu.Units.Wpf.Demo/InputOptionsView.xaml.cs new file mode 100644 index 00000000..190f7120 --- /dev/null +++ b/Gu.Units.Wpf.Demo/InputOptionsView.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Gu.Units.Wpf.Demo +{ + /// + /// Interaction logic for InputOptionsView.xaml + /// + public partial class InputOptionsView : UserControl + { + public InputOptionsView() + { + InitializeComponent(); + } + } +} diff --git a/Gu.Units.Wpf.Demo/MainWindow.xaml b/Gu.Units.Wpf.Demo/MainWindow.xaml index 8b1ab3a1..29bf8764 100644 --- a/Gu.Units.Wpf.Demo/MainWindow.xaml +++ b/Gu.Units.Wpf.Demo/MainWindow.xaml @@ -5,30 +5,27 @@ xmlns:local="clr-namespace:Gu.Units.Wpf.Demo" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:units="http://Gu.com/Units" + xmlns:units1="clr-namespace:Gu.Units;assembly=Gu.Units" Title="MainWindow" Width="525" Height="350" + DataContext="{x:Static local:ViewModel.Instance}" mc:Ignorable="d"> - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/Gu.Units.Wpf.Demo/StringFormatView.xaml b/Gu.Units.Wpf.Demo/StringFormatView.xaml new file mode 100644 index 00000000..ddb5862a --- /dev/null +++ b/Gu.Units.Wpf.Demo/StringFormatView.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Gu.Units.Wpf.Demo/StringFormatView.xaml.cs b/Gu.Units.Wpf.Demo/StringFormatView.xaml.cs new file mode 100644 index 00000000..9ce1a57d --- /dev/null +++ b/Gu.Units.Wpf.Demo/StringFormatView.xaml.cs @@ -0,0 +1,15 @@ +namespace Gu.Units.Wpf.Demo +{ + using System.Windows.Controls; + + /// + /// Interaction logic for StringFormatView.xaml + /// + public partial class StringFormatView : UserControl + { + public StringFormatView() + { + InitializeComponent(); + } + } +} diff --git a/Gu.Units.Wpf.Demo/SymbolOptions.xaml b/Gu.Units.Wpf.Demo/SymbolOptions.xaml new file mode 100644 index 00000000..7d44d8a9 --- /dev/null +++ b/Gu.Units.Wpf.Demo/SymbolOptions.xaml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Gu.Units.Wpf.Demo/SymbolOptions.xaml.cs b/Gu.Units.Wpf.Demo/SymbolOptions.xaml.cs new file mode 100644 index 00000000..e56cd2cb --- /dev/null +++ b/Gu.Units.Wpf.Demo/SymbolOptions.xaml.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; + +namespace Gu.Units.Wpf.Demo +{ + /// + /// Interaction logic for SymbolOptions.xaml + /// + public partial class SymbolOptions : UserControl + { + public SymbolOptions() + { + InitializeComponent(); + } + } +} diff --git a/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml b/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml new file mode 100644 index 00000000..93dabcf8 --- /dev/null +++ b/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml.cs b/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml.cs new file mode 100644 index 00000000..fd65ee07 --- /dev/null +++ b/Gu.Units.Wpf.Demo/UnitsOnlyView.xaml.cs @@ -0,0 +1,15 @@ +namespace Gu.Units.Wpf.Demo +{ + using System.Windows.Controls; + + /// + /// Interaction logic for UnitsOnlyView.xaml + /// + public partial class UnitsOnlyView : UserControl + { + public UnitsOnlyView() + { + InitializeComponent(); + } + } +} diff --git a/Gu.Units.Wpf.Demo/ViewModel.cs b/Gu.Units.Wpf.Demo/ViewModel.cs index 7d169057..88391611 100644 --- a/Gu.Units.Wpf.Demo/ViewModel.cs +++ b/Gu.Units.Wpf.Demo/ViewModel.cs @@ -6,7 +6,15 @@ namespace Gu.Units.Wpf.Demo public class ViewModel : INotifyPropertyChanged { - private Length _length; + private Length _length = Length.FromMillimetres(1234.567); + private Speed speed = Speed.FromMetresPerSecond(1.2); + private Pressure pressure = Pressure.FromMegapascals(1.23); + + public static readonly ViewModel Instance = new ViewModel(); + + private ViewModel() + { + } public event PropertyChangedEventHandler PropertyChanged; @@ -22,6 +30,30 @@ public Length Length } } + public Speed Speed + { + get { return this.speed; } + set + { + if (value.Equals(this.speed)) + return; + this.speed = value; + OnPropertyChanged(); + } + } + + public Pressure Pressure + { + get { return this.pressure; } + set + { + if (value.Equals(this.pressure)) + return; + this.pressure = value; + OnPropertyChanged(); + } + } + [NotifyPropertyChangedInvocator] protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { diff --git a/Gu.Units.Wpf.Tests/Class1.cs b/Gu.Units.Wpf.Tests/Class1.cs deleted file mode 100644 index 8e69b133..00000000 --- a/Gu.Units.Wpf.Tests/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Gu.Units.Wpf.Tests -{ - public class Class1 - { - } -} diff --git a/Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj b/Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj index 40311725..d77186a3 100644 --- a/Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj +++ b/Gu.Units.Wpf.Tests/Gu.Units.Wpf.Tests.csproj @@ -30,11 +30,22 @@ 4 + + + + + - + + + + + + + + + + {ea4bce51-a58d-4fa7-bba4-6f0056d753ca} + Gu.Units.Wpf + + + {f1f8b138-becc-4475-a7ab-b3019338bc7b} + Gu.Units + + - - - - ..\packages\NUnit\lib\net20\nunit.framework.dll - True - True - - - - + - - ..\packages\NUnit\lib\net40\nunit.framework.dll + + ..\packages\Moq\lib\net35\Moq.dll True True - + - - ..\packages\NUnit\lib\net45\nunit.framework.dll + + ..\packages\Moq\lib\net40\Moq.dll True True - + - - ..\packages\NUnit\lib\portable-net45+win8+wp8+wpa81+Xamarin.Mac+MonoAndroid10+MonoTouch10+Xamarin.iOS10\nunit.framework.dll + + ..\packages\Moq\lib\sl5\Moq.Silverlight.dll True True + + + ..\packages\NUnit\lib\nunit.framework.dll + True + True + + \ No newline at end of file diff --git a/Gu.Units.Wpf.Tests/LengthConverterTests.convert.cs b/Gu.Units.Wpf.Tests/LengthConverterTests.convert.cs new file mode 100644 index 00000000..96c47cda --- /dev/null +++ b/Gu.Units.Wpf.Tests/LengthConverterTests.convert.cs @@ -0,0 +1,137 @@ +namespace Gu.Units.Wpf.Tests +{ + using System; + using System.Globalization; + using NUnit.Framework; + + public partial class LengthConverterTests + { + [RequiresSTA] + public class Convert + { + [TestCase(typeof(string), 1.2)] + [TestCase(typeof(object), 1.2)] + [TestCase(typeof(double), 1.2)] + public void WithExplicitUnit(Type targetType, object expected) + { + var converter = new LengthConverter + { + Unit = LengthUnit.Centimetres, + }; + + var length = Length.FromMillimetres(12); + var actual = converter.Convert(length, targetType, null, null); + Assert.AreEqual(expected, actual); + } + + private const string Superscripts = "⁺⁻⁰¹²³⁴⁵⁶⁷⁸⁹"; + private const char MultiplyDot = '⋅'; + + [TestCase(typeof(string), SymbolFormat.SignedHatPowers, "1.2\u00A0m*s^-1")] + [TestCase(typeof(object), SymbolFormat.FractionHatPowers, "1.2\u00A0m/s")] + [TestCase(typeof(object), SymbolFormat.SignedSuperScript, "1.2\u00A0m⋅s⁻¹")] + [TestCase(typeof(object), SymbolFormat.FractionSuperScript, "1.2\u00A0m/s")] + public void WithUnitAndSymbolFormat(Type targetType, SymbolFormat format, object expected) + { + var converter = new SpeedConverter + { + Unit = SpeedUnit.MetresPerSecond, + UnitInput = UnitInput.SymbolRequired, + SymbolFormat = format + }; + + var length = Speed.FromMetresPerSecond(1.2); + var actual = converter.Convert(length, targetType, null, CultureInfo.InvariantCulture); + Assert.AreEqual(expected, actual); + } + + + [TestCase(typeof(string), UnitInput.ScalarOnly, 1.2)] + [TestCase(typeof(string), UnitInput.SymbolAllowed, 1.2)] + [TestCase(typeof(string), UnitInput.SymbolRequired, "1.2\u00A0m/s")] + public void WithUnitAndUnitInput(Type targetType, UnitInput inputOptions, object expected) + { + var converter = new SpeedConverter + { + Unit = SpeedUnit.MetresPerSecond, + UnitInput = inputOptions + }; + + var length = Speed.FromMetresPerSecond(1.2); + var actual = converter.Convert(length, targetType, null, CultureInfo.InvariantCulture); + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(string), "F1 m", "en-us", "1.2 m")] + [TestCase(typeof(object), "F1 m", "en-us", "1.2 m")] + [TestCase(typeof(double), "F1 m", "en-us", 1.234)] + public void WithStringFormat(Type targetType, string stringFormat, string culture, object expected) + { + var converter = new LengthConverter + { + StringFormat = stringFormat + }; + + var length = Length.FromMillimetres(1234); + var actual = converter.Convert(length, targetType, null, CultureInfo.GetCultureInfo(culture)); + Assert.AreEqual(expected, actual); + } + + [TestCase(typeof(string), "F1 m")] + [TestCase(typeof(object), "F1 m")] + public void WithBindingStringFormat(Type targetType, string stringFormat) + { + var converter = new LengthConverter(); + var providerMock = new ServiceProviderMock + { + BindingStringFormat = stringFormat + }; + + converter.ProvideValue(providerMock.Object); + var length = Length.FromMillimetres(1234); + var actual = converter.Convert(length, targetType, null, null); + Assert.AreEqual(length, actual); + } + + [Test] + public void WithBindingStringFormatAndExplicitStringFormat() + { + var converter = new LengthConverter + { + StringFormat = "F1 mm" + }; + + var providerMock = new ServiceProviderMock + { + BindingStringFormat = "F2 cm" + }; + + converter.ProvideValue(providerMock.Object); + var length = Length.FromMillimetres(1234); + Gu.Units.Wpf.Is.DesignMode = true; + var ex = Assert.Throws(()=> converter.Convert(length, typeof(string), null, null)); + var expected = "Both Binding.StringFormat and StringFormat are set."; + Assert.AreEqual(expected, ex.Message); + + Gu.Units.Wpf.Is.DesignMode = false; + var convert = converter.Convert(length, typeof (string), null, null); + Assert.AreEqual(expected, convert); + } + + [TestCase(typeof(string), "")] + [TestCase(typeof(object), null)] + [TestCase(typeof(double), null)] + public void Null(Type targetType, object expected) + { + var converter = new LengthConverter + { + Unit = LengthUnit.Centimetres, + UnitInput = UnitInput.ScalarOnly + }; + + var actual = converter.Convert(null, targetType, null, null); + Assert.AreEqual(expected, actual); + } + } + } +} diff --git a/Gu.Units.Wpf.Tests/LengthConverterTests.convertback.cs b/Gu.Units.Wpf.Tests/LengthConverterTests.convertback.cs new file mode 100644 index 00000000..fed7f8a9 --- /dev/null +++ b/Gu.Units.Wpf.Tests/LengthConverterTests.convertback.cs @@ -0,0 +1,53 @@ +namespace Gu.Units.Wpf.Tests +{ + using System.Globalization; + using NUnit.Framework; + + [TestFixture] + public partial class LengthConverterTests + { + public class ConvertBack + { + [TestCase("1.2")] + [TestCase(1.2)] + public void WithExplicitUnitScalarOnly(object value) + { + var converter = new LengthConverter + { + Unit = LengthUnit.Centimetres, + UnitInput = UnitInput.ScalarOnly + }; + + var actual = converter.ConvertBack(value, typeof(Length), null, CultureInfo.InvariantCulture); + var expected = Length.FromCentimetres(1.2); + Assert.AreEqual(expected, actual); + } + + [TestCase("1.2", UnitInput.ScalarOnly, true)] + [TestCase("1.2 mm", UnitInput.ScalarOnly, false)] + [TestCase("1.2", UnitInput.SymbolAllowed, true)] + [TestCase("12 mm", UnitInput.SymbolAllowed, true)] + [TestCase("1.2", UnitInput.SymbolRequired, false)] + [TestCase("12 mm", UnitInput.SymbolRequired, true)] + public void WithSymbolSettings(string value, UnitInput unitInput, bool expectSuccess) + { + var converter = new LengthConverter + { + Unit = LengthUnit.Centimetres, + UnitInput = unitInput + }; + + var actual = converter.ConvertBack(value, typeof(Length), null, CultureInfo.InvariantCulture); + if (expectSuccess) + { + var expected = Length.FromCentimetres(1.2); + Assert.AreEqual(expected, actual); + } + else + { + Assert.AreEqual(value, actual); + } + } + } + } +} diff --git a/Gu.Units.Wpf.Tests/LengthConverterTests.cs b/Gu.Units.Wpf.Tests/LengthConverterTests.cs new file mode 100644 index 00000000..6e28b770 --- /dev/null +++ b/Gu.Units.Wpf.Tests/LengthConverterTests.cs @@ -0,0 +1,13 @@ +namespace Gu.Units.Wpf.Tests +{ + using NUnit.Framework; + + public partial class LengthConverterTests + { + //[Test] + //public void Reminders() + //{ + // Assert.Pass("Looks like R# needs this to render a proper tree."); + //} + } +} diff --git a/Gu.Units.Wpf.Tests/LengthConverterTests.setup.cs b/Gu.Units.Wpf.Tests/LengthConverterTests.setup.cs new file mode 100644 index 00000000..9c8725be --- /dev/null +++ b/Gu.Units.Wpf.Tests/LengthConverterTests.setup.cs @@ -0,0 +1,104 @@ +namespace Gu.Units.Wpf.Tests +{ + using System; + using System.Globalization; + using NUnit.Framework; + + public partial class LengthConverterTests + { + [RequiresSTA] + public class Setup + { + [TestCase("F1 mm")] + [TestCase("{F1 mm}")] + [TestCase("{0:F1 mm}")] + public void SettingStringFormatHappyPath(string stringFormat) + { + var converter = new LengthConverter + { + StringFormat = stringFormat + }; + + var convert = converter.Convert(Length.FromMillimetres(12.34), typeof(string), null, CultureInfo.InvariantCulture); + Assert.AreEqual("12.3 mm", convert); + Assert.AreEqual(LengthUnit.Millimetres, converter.Unit); + Assert.AreEqual(UnitInput.SymbolRequired, converter.UnitInput); + } + + [TestCase("unknown format")] + public void SettingStringFormatFailsIfBadFormat(string stringFormat) + { + var converter = new LengthConverter { Unit = LengthUnit.Centimetres }; + Wpf.Is.DesignMode = true; + var ex = Assert.Throws(() => converter.StringFormat = stringFormat); + var expected = $"Error parsing: '{stringFormat}'"; + Assert.AreEqual(expected, ex.Message); + + Wpf.Is.DesignMode = false; + + var convert = converter.Convert(Length.Zero, typeof(string), null, null); + Assert.AreEqual(stringFormat, convert); + } + + [TestCase("F1 cm")] + [TestCase("{F1 cm}")] + [TestCase("{0:F1 cm}")] + public void BindingStringFormatHappyPath(string stringFormat) + { + var converter = new LengthConverter(); + var providerMock = new ServiceProviderMock + { + BindingStringFormat = stringFormat + }; + + converter.ProvideValue(providerMock.Object); + var length = Length.FromMillimetres(12.3); + var convert = converter.Convert(length, typeof(string), null, CultureInfo.InvariantCulture); + Assert.AreEqual(length, convert); + Assert.AreEqual(LengthUnit.Centimetres, converter.Unit); + Assert.AreEqual(UnitInput.SymbolRequired, converter.UnitInput); + } + + [Test] + public void SettingStringFormatWithUnitWhenUnitIsSet() + { + var converter = new LengthConverter + { + Unit = LengthUnit.Centimetres + }; + + Gu.Units.Wpf.Is.DesignMode = true; + var ex = Assert.Throws(() => converter.StringFormat = "F1 mm"); + var expected = "Unit is set to 'cm' but StringFormat is 'F1 mm'"; + Assert.AreEqual(expected, ex.Message); + + Gu.Units.Wpf.Is.DesignMode = false; + var actual = converter.Convert(Length.FromMetres(1.2), typeof(string), null, CultureInfo.InvariantCulture); + Assert.AreEqual(expected, actual); + } + + [TestCase(true)] + [TestCase(false)] + public void ThrowsInDesignModeIfMissingUnit(bool isDesignMode) + { + Gu.Units.Wpf.Is.DesignMode = isDesignMode; + var converter = new LengthConverter + { + UnitInput = UnitInput.ScalarOnly + }; + + var length = Length.FromMetres(1.2); + Assert.AreEqual(null, converter.Unit); + if (isDesignMode) + { + Assert.Throws(() => converter.Convert(length, typeof(string), null, null)); + } + else + { + var actual = converter.Convert(length, typeof(string), null, null); + Assert.AreEqual("No unit set", actual); + } + } + } + } +} diff --git a/Gu.Units.Wpf.Tests/Reminder.cs b/Gu.Units.Wpf.Tests/Reminder.cs new file mode 100644 index 00000000..4d1bb984 --- /dev/null +++ b/Gu.Units.Wpf.Tests/Reminder.cs @@ -0,0 +1,7 @@ +namespace Gu.Units.Wpf.Tests +{ + public static class Reminder + { + public const string ToDo = "Fix so this test passes!"; + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf.Tests/ServiceProviderMock.cs b/Gu.Units.Wpf.Tests/ServiceProviderMock.cs new file mode 100644 index 00000000..78c375c6 --- /dev/null +++ b/Gu.Units.Wpf.Tests/ServiceProviderMock.cs @@ -0,0 +1,36 @@ +namespace Gu.Units.Wpf.Tests +{ + using System; + using System.Windows.Controls; + using System.Windows.Data; + using System.Windows.Markup; + using Moq; + + public class ServiceProviderMock : Mock + { + internal readonly Mock ProvideValueTargetMock = new Mock(MockBehavior.Strict); + private readonly TextBox textBox = new TextBox(); + + public ServiceProviderMock() + :base(MockBehavior.Strict) + { + Setup(x => x.GetService(typeof(IProvideValueTarget))) + .Returns(this.ProvideValueTargetMock.Object); + + this.ProvideValueTargetMock.SetupGet(x => x.TargetObject).Returns(this.textBox); + this.ProvideValueTargetMock.SetupGet(x => x.TargetProperty).Returns(TextBox.TextProperty); + } + + public string BindingStringFormat + { + set + { + var binding = new Binding("Foo") + { + StringFormat = value + }; + BindingOperations.SetBinding(this.textBox, TextBox.TextProperty, binding); + } + } + } +} diff --git a/Gu.Units.Wpf.Tests/StringFormatParserTests.cs b/Gu.Units.Wpf.Tests/StringFormatParserTests.cs new file mode 100644 index 00000000..997b90de --- /dev/null +++ b/Gu.Units.Wpf.Tests/StringFormatParserTests.cs @@ -0,0 +1,21 @@ +namespace Gu.Units.Wpf.Tests +{ + using NUnit.Framework; + + public class StringFormatParserTests + { + [TestCase("{0:F1 mm}")] + [TestCase("F1 mm")] + public void TryParse(string format) + { + QuantityFormat actual; + var success = StringFormatParser.TryParse(format, out actual); + Assert.AreEqual(true, success); + Assert.AreEqual(null, actual.PrePadding); + Assert.AreEqual("F1", actual.ValueFormat); + Assert.AreEqual(" ", actual.Padding); + Assert.AreEqual("mm", actual.SymbolFormat); + Assert.AreEqual(null, actual.PostPadding); + } + } +} diff --git a/Gu.Units.Wpf.Tests/paket.references b/Gu.Units.Wpf.Tests/paket.references index 77bef386..5694ee4c 100644 --- a/Gu.Units.Wpf.Tests/paket.references +++ b/Gu.Units.Wpf.Tests/paket.references @@ -1 +1,2 @@ -NUnit \ No newline at end of file +NUnit +Moq \ No newline at end of file diff --git a/Gu.Units.Wpf/AccelerationConverter.generated.cs b/Gu.Units.Wpf/AccelerationConverter.generated.cs new file mode 100644 index 00000000..22efbc30 --- /dev/null +++ b/Gu.Units.Wpf/AccelerationConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Acceleration))] +[assembly: TypeForwardedTo(typeof(AccelerationUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class AccelerationConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private AccelerationUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public AccelerationConverter() + { + } + + public AccelerationConverter([TypeConverter(typeof(AccelerationUnitTypeConverter))]AccelerationUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(AccelerationUnitTypeConverter))] + public AccelerationUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Acceleration) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Acceleration)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var acceleration = (Acceleration)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return acceleration; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return acceleration.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return acceleration.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return acceleration.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Acceleration) || targetType == typeof(Acceleration?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Acceleration((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Acceleration(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Acceleration(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Acceleration result; + if (Acceleration.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/AngleConverter.generated.cs b/Gu.Units.Wpf/AngleConverter.generated.cs new file mode 100644 index 00000000..526f3dee --- /dev/null +++ b/Gu.Units.Wpf/AngleConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Angle))] +[assembly: TypeForwardedTo(typeof(AngleUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class AngleConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private AngleUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public AngleConverter() + { + } + + public AngleConverter([TypeConverter(typeof(AngleUnitTypeConverter))]AngleUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(AngleUnitTypeConverter))] + public AngleUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Angle) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Angle)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var angle = (Angle)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return angle; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return angle.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return angle.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return angle.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Angle) || targetType == typeof(Angle?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Angle((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Angle(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Angle(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Angle result; + if (Angle.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/AnglePerUnitlessConverter.generated.cs b/Gu.Units.Wpf/AnglePerUnitlessConverter.generated.cs new file mode 100644 index 00000000..f5474774 --- /dev/null +++ b/Gu.Units.Wpf/AnglePerUnitlessConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(AnglePerUnitless))] +[assembly: TypeForwardedTo(typeof(AnglePerUnitlessUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class AnglePerUnitlessConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private AnglePerUnitlessUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public AnglePerUnitlessConverter() + { + } + + public AnglePerUnitlessConverter([TypeConverter(typeof(AnglePerUnitlessUnitTypeConverter))]AnglePerUnitlessUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(AnglePerUnitlessUnitTypeConverter))] + public AnglePerUnitlessUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is AnglePerUnitless) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(AnglePerUnitless)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var anglePerUnitless = (AnglePerUnitless)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return anglePerUnitless; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return anglePerUnitless.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return anglePerUnitless.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return anglePerUnitless.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(AnglePerUnitless) || targetType == typeof(AnglePerUnitless?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new AnglePerUnitless((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new AnglePerUnitless(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new AnglePerUnitless(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + AnglePerUnitless result; + if (AnglePerUnitless.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/AngularAccelerationConverter.generated.cs b/Gu.Units.Wpf/AngularAccelerationConverter.generated.cs new file mode 100644 index 00000000..1cb553ba --- /dev/null +++ b/Gu.Units.Wpf/AngularAccelerationConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(AngularAcceleration))] +[assembly: TypeForwardedTo(typeof(AngularAccelerationUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class AngularAccelerationConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private AngularAccelerationUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public AngularAccelerationConverter() + { + } + + public AngularAccelerationConverter([TypeConverter(typeof(AngularAccelerationUnitTypeConverter))]AngularAccelerationUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(AngularAccelerationUnitTypeConverter))] + public AngularAccelerationUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is AngularAcceleration) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(AngularAcceleration)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var angularAcceleration = (AngularAcceleration)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return angularAcceleration; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return angularAcceleration.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return angularAcceleration.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return angularAcceleration.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(AngularAcceleration) || targetType == typeof(AngularAcceleration?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new AngularAcceleration((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new AngularAcceleration(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new AngularAcceleration(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + AngularAcceleration result; + if (AngularAcceleration.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/AngularJerkConverter.generated.cs b/Gu.Units.Wpf/AngularJerkConverter.generated.cs new file mode 100644 index 00000000..9d6eded2 --- /dev/null +++ b/Gu.Units.Wpf/AngularJerkConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(AngularJerk))] +[assembly: TypeForwardedTo(typeof(AngularJerkUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class AngularJerkConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private AngularJerkUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public AngularJerkConverter() + { + } + + public AngularJerkConverter([TypeConverter(typeof(AngularJerkUnitTypeConverter))]AngularJerkUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(AngularJerkUnitTypeConverter))] + public AngularJerkUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is AngularJerk) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(AngularJerk)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var angularJerk = (AngularJerk)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return angularJerk; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return angularJerk.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return angularJerk.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return angularJerk.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(AngularJerk) || targetType == typeof(AngularJerk?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new AngularJerk((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new AngularJerk(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new AngularJerk(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + AngularJerk result; + if (AngularJerk.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/AngularSpeedConverter.generated.cs b/Gu.Units.Wpf/AngularSpeedConverter.generated.cs new file mode 100644 index 00000000..53a0d2c0 --- /dev/null +++ b/Gu.Units.Wpf/AngularSpeedConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(AngularSpeed))] +[assembly: TypeForwardedTo(typeof(AngularSpeedUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class AngularSpeedConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private AngularSpeedUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public AngularSpeedConverter() + { + } + + public AngularSpeedConverter([TypeConverter(typeof(AngularSpeedUnitTypeConverter))]AngularSpeedUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(AngularSpeedUnitTypeConverter))] + public AngularSpeedUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is AngularSpeed) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(AngularSpeed)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var angularSpeed = (AngularSpeed)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return angularSpeed; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return angularSpeed.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return angularSpeed.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return angularSpeed.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(AngularSpeed) || targetType == typeof(AngularSpeed?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new AngularSpeed((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new AngularSpeed(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new AngularSpeed(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + AngularSpeed result; + if (AngularSpeed.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/AreaConverter.generated.cs b/Gu.Units.Wpf/AreaConverter.generated.cs new file mode 100644 index 00000000..8b1009c6 --- /dev/null +++ b/Gu.Units.Wpf/AreaConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Area))] +[assembly: TypeForwardedTo(typeof(AreaUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class AreaConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private AreaUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public AreaConverter() + { + } + + public AreaConverter([TypeConverter(typeof(AreaUnitTypeConverter))]AreaUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(AreaUnitTypeConverter))] + public AreaUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Area) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Area)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var area = (Area)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return area; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return area.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return area.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return area.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Area) || targetType == typeof(Area?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Area((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Area(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Area(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Area result; + if (Area.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/CapacitanceConverter.generated.cs b/Gu.Units.Wpf/CapacitanceConverter.generated.cs new file mode 100644 index 00000000..5dff64f9 --- /dev/null +++ b/Gu.Units.Wpf/CapacitanceConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Capacitance))] +[assembly: TypeForwardedTo(typeof(CapacitanceUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class CapacitanceConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private CapacitanceUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public CapacitanceConverter() + { + } + + public CapacitanceConverter([TypeConverter(typeof(CapacitanceUnitTypeConverter))]CapacitanceUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(CapacitanceUnitTypeConverter))] + public CapacitanceUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Capacitance) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Capacitance)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var capacitance = (Capacitance)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return capacitance; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return capacitance.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return capacitance.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return capacitance.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Capacitance) || targetType == typeof(Capacitance?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Capacitance((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Capacitance(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Capacitance(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Capacitance result; + if (Capacitance.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/CurrentConverter.generated.cs b/Gu.Units.Wpf/CurrentConverter.generated.cs new file mode 100644 index 00000000..ca2586ba --- /dev/null +++ b/Gu.Units.Wpf/CurrentConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Current))] +[assembly: TypeForwardedTo(typeof(CurrentUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class CurrentConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private CurrentUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public CurrentConverter() + { + } + + public CurrentConverter([TypeConverter(typeof(CurrentUnitTypeConverter))]CurrentUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(CurrentUnitTypeConverter))] + public CurrentUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Current) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Current)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var current = (Current)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return current; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return current.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return current.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return current.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Current) || targetType == typeof(Current?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Current((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Current(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Current(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Current result; + if (Current.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/DensityConverter.generated.cs b/Gu.Units.Wpf/DensityConverter.generated.cs new file mode 100644 index 00000000..f1d685cc --- /dev/null +++ b/Gu.Units.Wpf/DensityConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Density))] +[assembly: TypeForwardedTo(typeof(DensityUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class DensityConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private DensityUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public DensityConverter() + { + } + + public DensityConverter([TypeConverter(typeof(DensityUnitTypeConverter))]DensityUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(DensityUnitTypeConverter))] + public DensityUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Density) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Density)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var density = (Density)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return density; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return density.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return density.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return density.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Density) || targetType == typeof(Density?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Density((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Density(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Density(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Density result; + if (Density.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/ElectricChargeConverter.generated.cs b/Gu.Units.Wpf/ElectricChargeConverter.generated.cs new file mode 100644 index 00000000..8dfe8f00 --- /dev/null +++ b/Gu.Units.Wpf/ElectricChargeConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(ElectricCharge))] +[assembly: TypeForwardedTo(typeof(ElectricChargeUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class ElectricChargeConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private ElectricChargeUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public ElectricChargeConverter() + { + } + + public ElectricChargeConverter([TypeConverter(typeof(ElectricChargeUnitTypeConverter))]ElectricChargeUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(ElectricChargeUnitTypeConverter))] + public ElectricChargeUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is ElectricCharge) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(ElectricCharge)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var electricCharge = (ElectricCharge)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return electricCharge; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return electricCharge.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return electricCharge.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return electricCharge.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(ElectricCharge) || targetType == typeof(ElectricCharge?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new ElectricCharge((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new ElectricCharge(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new ElectricCharge(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + ElectricCharge result; + if (ElectricCharge.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/EnergyConverter.generated.cs b/Gu.Units.Wpf/EnergyConverter.generated.cs new file mode 100644 index 00000000..7213e199 --- /dev/null +++ b/Gu.Units.Wpf/EnergyConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Energy))] +[assembly: TypeForwardedTo(typeof(EnergyUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class EnergyConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private EnergyUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public EnergyConverter() + { + } + + public EnergyConverter([TypeConverter(typeof(EnergyUnitTypeConverter))]EnergyUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(EnergyUnitTypeConverter))] + public EnergyUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Energy) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Energy)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var energy = (Energy)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return energy; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return energy.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return energy.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return energy.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Energy) || targetType == typeof(Energy?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Energy((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Energy(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Energy(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Energy result; + if (Energy.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/FlexibilityConverter.generated.cs b/Gu.Units.Wpf/FlexibilityConverter.generated.cs new file mode 100644 index 00000000..62c3ef7d --- /dev/null +++ b/Gu.Units.Wpf/FlexibilityConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Flexibility))] +[assembly: TypeForwardedTo(typeof(FlexibilityUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class FlexibilityConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private FlexibilityUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public FlexibilityConverter() + { + } + + public FlexibilityConverter([TypeConverter(typeof(FlexibilityUnitTypeConverter))]FlexibilityUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(FlexibilityUnitTypeConverter))] + public FlexibilityUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Flexibility) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Flexibility)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var flexibility = (Flexibility)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return flexibility; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return flexibility.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return flexibility.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return flexibility.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Flexibility) || targetType == typeof(Flexibility?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Flexibility((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Flexibility(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Flexibility(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Flexibility result; + if (Flexibility.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/ForceConverter.generated.cs b/Gu.Units.Wpf/ForceConverter.generated.cs new file mode 100644 index 00000000..4a2d4122 --- /dev/null +++ b/Gu.Units.Wpf/ForceConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Force))] +[assembly: TypeForwardedTo(typeof(ForceUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class ForceConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private ForceUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public ForceConverter() + { + } + + public ForceConverter([TypeConverter(typeof(ForceUnitTypeConverter))]ForceUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(ForceUnitTypeConverter))] + public ForceUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Force) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Force)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var force = (Force)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return force; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return force.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return force.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return force.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Force) || targetType == typeof(Force?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Force((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Force(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Force(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Force result; + if (Force.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/ForcePerUnitlessConverter.generated.cs b/Gu.Units.Wpf/ForcePerUnitlessConverter.generated.cs new file mode 100644 index 00000000..70404d56 --- /dev/null +++ b/Gu.Units.Wpf/ForcePerUnitlessConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(ForcePerUnitless))] +[assembly: TypeForwardedTo(typeof(ForcePerUnitlessUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class ForcePerUnitlessConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private ForcePerUnitlessUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public ForcePerUnitlessConverter() + { + } + + public ForcePerUnitlessConverter([TypeConverter(typeof(ForcePerUnitlessUnitTypeConverter))]ForcePerUnitlessUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(ForcePerUnitlessUnitTypeConverter))] + public ForcePerUnitlessUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is ForcePerUnitless) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(ForcePerUnitless)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var forcePerUnitless = (ForcePerUnitless)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return forcePerUnitless; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return forcePerUnitless.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return forcePerUnitless.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return forcePerUnitless.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(ForcePerUnitless) || targetType == typeof(ForcePerUnitless?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new ForcePerUnitless((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new ForcePerUnitless(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new ForcePerUnitless(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + ForcePerUnitless result; + if (ForcePerUnitless.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/FrequencyConverter.generated.cs b/Gu.Units.Wpf/FrequencyConverter.generated.cs new file mode 100644 index 00000000..996bd6a6 --- /dev/null +++ b/Gu.Units.Wpf/FrequencyConverter.generated.cs @@ -0,0 +1,320 @@ +using System.Runtime.CompilerServices; +using Gu.Units; + +[assembly: TypeForwardedTo(typeof(Frequency))] +[assembly: TypeForwardedTo(typeof(FrequencyUnit))] + +namespace Gu.Units.Wpf +{ + using System; + using System.ComponentModel; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + using System.Windows.Markup; + + [MarkupExtensionReturnType(typeof(IValueConverter))] + public class FrequencyConverter : MarkupExtension, IValueConverter + { + private static readonly string StringFormatNotSet = "Not Set"; + private FrequencyUnit? unit; + private IProvideValueTarget provideValueTarget; + private string stringFormat = StringFormatNotSet; + private QuantityFormat quantityFormat; + private string bindingStringFormat = StringFormatNotSet; + private string errorText; + + public FrequencyConverter() + { + } + + public FrequencyConverter([TypeConverter(typeof(FrequencyUnitTypeConverter))]FrequencyUnit unit) + { + Unit = unit; + } + + [ConstructorArgument("unit"), TypeConverter(typeof(FrequencyUnitTypeConverter))] + public FrequencyUnit? Unit + { + get { return this.unit; } + set + { + if (value == null) + { + var message = $"{nameof(Unit)} cannot be null"; + throw new ArgumentException(message, nameof(value)); + } + + this.unit = value.Value; + } + } + + public SymbolFormat? SymbolFormat { get; set; } + + public UnitInput? UnitInput { get; set; } + + public string StringFormat + { + get { return this.stringFormat; } + set + { + this.stringFormat = value; + OnStringFormatChanged(); + } + } + + public override object ProvideValue(IServiceProvider serviceProvider) + { + // the binding does not have stringformat set at this point + // caching IProvideValueTarget to resolve later. + this.provideValueTarget = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget)); + return this; + } + + public object Convert(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!IsValidConvertTargetType(targetType) && Is.DesignMode) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new ArgumentException(message, nameof(targetType)); + } + + if (value != null && !(value is Frequency) && Is.DesignMode) + { + var message = $"{GetType().Name} only supports converting from {typeof(Frequency)}"; + throw new ArgumentException(message, nameof(value)); + } + + if (this.bindingStringFormat == StringFormatNotSet) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return "No unit set"; + } + + if (this.errorText != null) + { + return this.errorText; + } + + if (value == null) + { + return targetType == typeof(string) + ? string.Empty + : null; + } + + var frequency = (Frequency)value; + + var format = this.bindingStringFormat != null && this.bindingStringFormat != StringFormatNotSet + ? this.bindingStringFormat + : null; + if (format != null) + { + return frequency; + } + + format = this.stringFormat != null && this.stringFormat != StringFormatNotSet + ? this.stringFormat + : null; + if (format != null && + (targetType == typeof(string) || targetType == typeof(object))) + { + return frequency.ToString(this.quantityFormat, culture); + } + + + if ((SymbolFormat != null || UnitInput == Wpf.UnitInput.SymbolRequired) && + (targetType == typeof(string) || targetType == typeof(object))) + { + return frequency.ToString(Unit.Value, SymbolFormat ?? Units.SymbolFormat.FractionSuperScript, culture); + } + + if (IsValidConvertTargetType(targetType)) + { + return frequency.GetValue(this.unit.Value); + } + + return value; + } + + public object ConvertBack(object value, + Type targetType, + object parameter, + CultureInfo culture) + { + if (!(targetType == typeof(Frequency) || targetType == typeof(Frequency?))) + { + var message = $"{GetType().Name} does not support converting to {targetType.Name}"; + throw new NotSupportedException(message); + } + + if (value == null) + { + return null; + } + + if (this.StringFormat == null) + { + TryGetStringFormatFromBinding(); + } + + if (this.unit == null) + { + if (Is.DesignMode) + { + var message = $"{nameof(Unit)} cannot be null\r\n" + + $"Must be specified Explicitly or in Binding.StringFormat"; + throw new ArgumentException(message); + } + + return value; + } + + if (value is double) + { + return new Frequency((double)value, this.unit.Value); + } + + var text = value as string; + if (string.IsNullOrEmpty(text)) + { + return null; + } + + var unitInput = UnitInput ?? Wpf.UnitInput.ScalarOnly; + switch (unitInput) + { + case Wpf.UnitInput.ScalarOnly: + { + double d; + if (double.TryParse(text, NumberStyles.Float, culture, out d)) + { + return new Frequency(d, this.unit.Value); + } + + return value; // returning raw to trigger error + } + case Wpf.UnitInput.SymbolAllowed: + { + double d; + int pos = 0; + WhiteSpaceReader.TryRead(text, ref pos); + if (DoubleReader.TryRead(text, ref pos, NumberStyles.Float, culture, out d)) + { + WhiteSpaceReader.TryRead(text, ref pos); + if (pos == text.Length) + { + return new Frequency(d, this.unit.Value); + } + } + + goto case Wpf.UnitInput.SymbolRequired; + } + case Wpf.UnitInput.SymbolRequired: + { + Frequency result; + if (Frequency.TryParse(text, NumberStyles.Float, culture, out result)) + { + return result; + } + + return text; + } + default: + throw new ArgumentOutOfRangeException(); + } + } + + private void TryGetStringFormatFromBinding() + { + var target = this.provideValueTarget?.TargetObject as DependencyObject; + Binding binding = null; + if (target != null) + { + var targetProperty = this.provideValueTarget.TargetProperty as DependencyProperty; + if (targetProperty != null) + { + binding = BindingOperations.GetBinding(target, targetProperty); + } + } + + binding = binding ?? this.provideValueTarget?.TargetObject as Binding; + this.bindingStringFormat = binding?.StringFormat; + if (this.bindingStringFormat != null) + { + OnStringFormatChanged(); + } + } + + private void OnStringFormatChanged() + { + if (this.bindingStringFormat != StringFormatNotSet && + this.stringFormat != StringFormatNotSet && + this.bindingStringFormat != this.stringFormat) + { + this.errorText += $"Both {nameof(Binding)}.{nameof(Binding.StringFormat)} and {nameof(StringFormat)} are set."; + + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + var format = this.stringFormat == StringFormatNotSet + ? this.bindingStringFormat + : this.stringFormat; + if (StringFormatParser.TryParse(format, out this.quantityFormat)) + { + if (UnitInput == null && this.quantityFormat.SymbolFormat != null) + { + UnitInput = Wpf.UnitInput.SymbolRequired; + } + + if (this.unit == null) + { + this.unit = this.quantityFormat.Unit; + } + + else if (this.unit != this.quantityFormat.Unit) + { + this.errorText += $"Unit is set to '{Unit}' but {nameof(StringFormat)} is '{format}'"; + if (Is.DesignMode) + { + throw new InvalidOperationException(this.errorText); + } + } + + return; + } + + this.errorText = this.quantityFormat.ErrorText; + if (Is.DesignMode) + { + throw new ArgumentException($"Error parsing: '{this.errorText}'"); + } + + this.stringFormat = null; + } + + private bool IsValidConvertTargetType(Type targetType) + { + return targetType == typeof(string) || + targetType == typeof(double) || + targetType == typeof(object); + } + } +} \ No newline at end of file diff --git a/Gu.Units.Wpf/Gu.Units.Wpf.csproj b/Gu.Units.Wpf/Gu.Units.Wpf.csproj index 4513ce99..f2244771 100644 --- a/Gu.Units.Wpf/Gu.Units.Wpf.csproj +++ b/Gu.Units.Wpf/Gu.Units.Wpf.csproj @@ -35,10 +35,119 @@ + + - + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + + + QuantityValueConverters.txt4 + @@ -46,6 +155,10 @@ Gu.Units + + + +