[PT Run][New Plugin] Unit Converter (#9800) (#11406)

This commit is contained in:
ThiefZero 2021-06-09 10:46:19 +01:00 committed by Jaime Bernardo
parent 27e609f6eb
commit fc9d7e4f1b
23 changed files with 1071 additions and 7 deletions

View File

@ -49,6 +49,7 @@ AModifier
AMPROPERTY
AMPROPSETID
anges
angularsen
ansicolor
ANull
AOC
@ -1497,6 +1498,7 @@ ppv
pragma
prc
precomp
Prefixer
Preinstalled
preload
PREMULTIPLIED
@ -1745,6 +1747,7 @@ shldisp
shlobj
shlwapi
shobjidl
shortsplit
SHORTCUTATLEAST
shortcutcontrol
Shortcutguide
@ -2028,6 +2031,8 @@ uninstantiated
Uniq
uniquifier
Uniquifies
unitconvert
unitconverter
unittests
unk
unknwn

View File

@ -147,6 +147,7 @@ steps:
configuration: '$(BuildConfiguration)'
testSelector: 'testAssemblies'
testAssemblyVer2: |
**\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.dll
**\Microsoft.Plugin.Folder.UnitTests.dll
**\Microsoft.Plugin.Program.UnitTests.dll
**\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest.dll

View File

@ -123,10 +123,6 @@ build:
- 'modules\launcher\Plugins\Calculator\Microsoft.PowerToys.Run.Plugin.Calculator.dll'
- 'modules\launcher\Plugins\Calculator\ManagedTelemetry.dll'
- 'modules\launcher\Plugins\UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.dll'
- 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator\Microsoft.PowerToys.Run.Plugin.Calculator.dll'
- 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator\Wox.Infrastructure.dll'
- 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator\Wox.Plugin.dll'
- 'modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.Calculator\Telemetry.dll'
- 'modules\launcher\Plugins\Microsoft.Plugin.Folder\Microsoft.Plugin.Folder.dll'
- 'modules\launcher\Plugins\Microsoft.Plugin.Folder\ManagedTelemetry.dll'
- 'modules\launcher\Plugins\Microsoft.Plugin.Indexer\Microsoft.Plugin.Indexer.dll'

View File

@ -145,14 +145,18 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Launcher", "src\m
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\modules\launcher\PowerLauncher\PowerLauncher.csproj", "{F97E5003-F263-4D4A-A964-0F1F3C82DEF2}"
ProjectSection(ProjectDependencies) = postProject
{FD8EB419-FF9C-4D88-BB6F-BF6CED37747B} = {FD8EB419-FF9C-4D88-BB6F-BF6CED37747B}
{03276A39-D4E9-417C-8FFD-200B0EE5E871} = {03276A39-D4E9-417C-8FFD-200B0EE5E871}
{4D971245-7A70-41D5-BAA0-DDB5684CAF51} = {4D971245-7A70-41D5-BAA0-DDB5684CAF51}
{FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4}
{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}
{59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E}
{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}
{0351ADA4-0C32-4652-9BA0-41F7B602372B} = {0351ADA4-0C32-4652-9BA0-41F7B602372B}
{787B8AA6-CA93-4C84-96FE-DF31110AD1C4} = {787B8AA6-CA93-4C84-96FE-DF31110AD1C4}
{F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {F8B870EB-D5F5-45BA-9CF7-A5C459818820}
{74F1B9ED-F59C-4FE7-B473-7B453E30837E} = {74F1B9ED-F59C-4FE7-B473-7B453E30837E}
{4BABF3FE-3451-42FD-873F-3C332E18DCEF} = {4BABF3FE-3451-42FD-873F-3C332E18DCEF}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{E775CC2C-24CB-48D6-9C3A-BE4CCE0DB17A}"
@ -325,8 +329,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "KeyboardManagerEditorTest",
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "espresso", "espresso", "{127F38E0-40AA-4594-B955-5616BF206882}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.csproj", "{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EspressoModuleInterface", "src\modules\espresso\EspressoModuleInterface\EspressoModuleInterface.vcxproj", "{5E7360A8-D048-4ED3-8F09-0BFD64C5529A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Community.PowerToys.Run.Plugin.UnitConverter.UnitTest", "src\modules\launcher\Plugins\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest\Community.PowerToys.Run.Plugin.UnitConverter.UnitTest.csproj", "{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Espresso", "src\modules\espresso\Espresso\Espresso.csproj", "{D940E07F-532C-4FF3-883F-790DA014F19A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shortcutguide", "shortcutguide", "{106CBECA-0701-4FC3-838C-9DF816A19AE2}"
@ -675,6 +683,14 @@ Global
{D940E07F-532C-4FF3-883F-790DA014F19A}.Debug|x64.Build.0 = Debug|x64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.ActiveCfg = Release|x64
{D940E07F-532C-4FF3-883F-790DA014F19A}.Release|x64.Build.0 = Release|x64
{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.ActiveCfg = Debug|x64
{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Debug|x64.Build.0 = Debug|x64
{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.ActiveCfg = Release|x64
{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}.Release|x64.Build.0 = Release|x64
{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.ActiveCfg = Debug|x64
{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Debug|x64.Build.0 = Debug|x64
{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.ActiveCfg = Release|x64
{3E424AD2-19E5-4AE6-B833-F53963EB5FC1}.Release|x64.Build.0 = Release|x64
{2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.ActiveCfg = Debug|x64
{2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Debug|x64.Build.0 = Debug|x64
{2D604C07-51FC-46BB-9EB7-75AECC7F5E81}.Release|x64.ActiveCfg = Release|x64
@ -783,6 +799,8 @@ Global
{62173D9A-6724-4C00-A1C8-FB646480A9EC} = {38BDB927-829B-4C65-9CD9-93FB05D66D65}
{127F38E0-40AA-4594-B955-5616BF206882} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{5E7360A8-D048-4ED3-8F09-0BFD64C5529A} = {127F38E0-40AA-4594-B955-5616BF206882}
{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{3E424AD2-19E5-4AE6-B833-F53963EB5FC1} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
{D940E07F-532C-4FF3-883F-790DA014F19A} = {127F38E0-40AA-4594-B955-5616BF206882}
{106CBECA-0701-4FC3-838C-9DF816A19AE2} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
{2D604C07-51FC-46BB-9EB7-75AECC7F5E81} = {106CBECA-0701-4FC3-838C-9DF816A19AE2}

View File

@ -0,0 +1,31 @@
# Unit Converter Plugin
The Unit Convert plugin as the name suggests is used to perform unit conversion on the user entered query.
This plugin uses a package called [UnitsNet](https://github.com/angularsen/UnitsNet).
![Image of Calculator plugin](/doc/images/launcher/plugins/community.unitconverter.png)
### Currently Supported Units
- [Acceleration](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AccelerationUnit.g.cs)
- [Angle](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AngleUnit.g.cs)
- [Area](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/AreaUnit.g.cs)
- [Duration](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/DurationUnit.g.cs)
- [Energy](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/EnergyUnit.g.cs)
- [Information](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/InformationUnit.g.cs)
- [Length](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/LengthUnit.g.cs)
- [Mass](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/MassUnit.g.cs)
- [Power](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/PowerUnit.g.cs)
- [Pressure](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/PressureUnit.g.cs)
- [Speed](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/SpeedUnit.g.cs)
- [Temperature](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/TemperatureUnit.g.cs)
- [Volume](https://github.com/angularsen/UnitsNet/blob/master/UnitsNet/GeneratedCode/Units/VolumeUnit.g.cs)
These are the ones that are currently enabled (though UnitsNet supports many more). They are defined in [`Main.cs`](/src/modules/launcher/Plugins/Community.PowerToys.Run.UnitConverter/Main.cs).
### [`InputInterpreter`](/src/modules/launcher/Plugins/Community.PowerToys.Run.UnitConverter/InputInterpreter.cs)
- Class which manipulates user input such that it may be interpreted correctly and thus converted.
- Uses a regex amongst other things to do this.
### [`UnitHandler`](/src/modules/launcher/Plugins/Community.PowerToys.Run.UnitConverter/UnitHandler.cs)
- Class that does the actual conversion.
- Supports abbreviations in user input (single, double, or none).

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@ -276,6 +276,10 @@
<Directory Id="UriImagesFolder" Name="Images" />
<Directory Id="UriLanguagesFolder" Name="Languages" />
</Directory>
<Directory Id="UnitConverterPluginFolder" Name="UnitConverter">
<Directory Id="UnitConverterImagesFolder" Name="Images" />
<Directory Id="UnitConverterLanguagesFolder" Name="Languages" />
</Directory>
<Directory Id="VSCodeWorkspacesPluginFolder" Name="VSCodeWorkspace">
<Directory Id="VSCodeWorkspaceImagesFolder" Name="Images" />
<Directory Id="VSCodeWorkspaceLanguagesFolder" Name="Languages" />
@ -907,7 +911,7 @@
<Fragment>
<!-- Resource directories should be added only if the installer is built on the build farm -->
<?ifdef env.IsPipeline?>
<?foreach ParentDirectory in LauncherInstallFolder;FancyZonesInstallFolder;ImageResizerInstallFolder;ColorPickerInstallFolder;FileExplorerPreviewInstallFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder?>
<?foreach ParentDirectory in LauncherInstallFolder;FancyZonesInstallFolder;ImageResizerInstallFolder;ColorPickerInstallFolder;FileExplorerPreviewInstallFolder;CalculatorPluginFolder;FolderPluginFolder;ProgramPluginFolder;ShellPluginFolder;IndexerPluginFolder;UnitConverterPluginFolder;UriPluginFolder;WindowWalkerPluginFolder;RegistryPluginFolder;VSCodeWorkspacesPluginFolder;ServicePluginFolder;SystemPluginFolder?>
<DirectoryRef Id="$(var.ParentDirectory)">
<!-- Resource file directories -->
<?foreach Language in $(var.LocLanguageList)?>
@ -1015,7 +1019,7 @@
<Component Id="launcherInstallComponent" Directory="LauncherInstallFolder" Guid="5E688DB4-C522-4268-BA54-ED1CDFFE9DB6">
<File Source="$(var.BinX64Dir)modules\Launcher\Microsoft.Launcher.dll" />
<?foreach File in concrt140_app.dll;ICSharpCode.SharpZipLib.dll;JetBrains.Annotations.dll;Mages.Core.dll;Microsoft.Search.Interop.dll;Mono.Cecil.dll;Mono.Cecil.Mdb.dll;Mono.Cecil.Pdb.dll;Mono.Cecil.Rocks.dll;msvcp140_1_app.dll;msvcp140_2_app.dll;msvcp140_app.dll;Newtonsoft.Json.dll;NLog.dll;NLog.Extensions.Logging.dll;PowerLauncher.deps.json;PowerLauncher.dll;PowerLauncher.exe;Microsoft.Xaml.Behaviors.dll;System.Text.Json.dll;PowerLauncher.runtimeconfig.json;System.Data.OleDb.dll;vcamp140_app.dll;vccorlib140_app.dll;vcomp140_app.dll;vcruntime140_1_app.dll;vcruntime140_app.dll;Wox.Infrastructure.dll;Wox.Plugin.dll;PowerToysInterop.dll;ManagedTelemetry.dll;PowerLauncher.Telemetry.dll;Microsoft.Extensions.Configuration.Abstractions.dll;Microsoft.Extensions.Configuration.Binder.dll;Microsoft.Extensions.Configuration.dll;Microsoft.Extensions.DependencyInjection.Abstractions.dll;Microsoft.Extensions.DependencyInjection.dll;Microsoft.Extensions.Logging.Abstractions.dll;Microsoft.Extensions.Logging.dll;Microsoft.Extensions.Options.dll;Microsoft.Extensions.Primitives.dll;ControlzEx.dll;ManagedCommon.dll;System.IO.Abstractions.dll;Microsoft.PowerToys.Common.UI.dll;System.ServiceProcess.ServiceController.dll;Microsoft.Toolkit.Uwp.Notifications.dll;ModernWpf.Controls.dll;ModernWpf.dll;System.Runtime.CompilerServices.Unsafe.dll;System.Text.Encodings.Web.dll?>
<?foreach File in concrt140_app.dll;ICSharpCode.SharpZipLib.dll;JetBrains.Annotations.dll;Mages.Core.dll;Microsoft.Search.Interop.dll;Mono.Cecil.dll;Mono.Cecil.Mdb.dll;Mono.Cecil.Pdb.dll;Mono.Cecil.Rocks.dll;msvcp140_1_app.dll;msvcp140_2_app.dll;msvcp140_app.dll;Newtonsoft.Json.dll;NLog.dll;NLog.Extensions.Logging.dll;PowerLauncher.deps.json;PowerLauncher.dll;PowerLauncher.exe;Microsoft.Xaml.Behaviors.dll;System.Text.Json.dll;PowerLauncher.runtimeconfig.json;System.Data.OleDb.dll;UnitsNet.dll;vcamp140_app.dll;vccorlib140_app.dll;vcomp140_app.dll;vcruntime140_1_app.dll;vcruntime140_app.dll;Wox.Infrastructure.dll;Wox.Plugin.dll;PowerToysInterop.dll;ManagedTelemetry.dll;PowerLauncher.Telemetry.dll;Microsoft.Extensions.Configuration.Abstractions.dll;Microsoft.Extensions.Configuration.Binder.dll;Microsoft.Extensions.Configuration.dll;Microsoft.Extensions.DependencyInjection.Abstractions.dll;Microsoft.Extensions.DependencyInjection.dll;Microsoft.Extensions.Logging.Abstractions.dll;Microsoft.Extensions.Logging.dll;Microsoft.Extensions.Options.dll;Microsoft.Extensions.Primitives.dll;ControlzEx.dll;ManagedCommon.dll;System.IO.Abstractions.dll;Microsoft.PowerToys.Common.UI.dll;System.ServiceProcess.ServiceController.dll;Microsoft.Toolkit.Uwp.Notifications.dll;ModernWpf.Controls.dll;ModernWpf.dll;System.Runtime.CompilerServices.Unsafe.dll;System.Text.Encodings.Web.dll?>
<File Id="File_$(var.File)" Source="$(var.BinX64Dir)modules\launcher\$(var.File)" />
<?endforeach?>
<File Source="$(var.BinX64Dir)Settings\Microsoft.PowerToys.Settings.UI.Lib.dll" />
@ -1090,6 +1094,17 @@
<?endforeach?>
</Component>
<!-- UnitConverter Plugin -->
<Component Id="UnitConverterComponent" Directory="UnitConverterPluginFolder" Guid="D4F429E3-C619-49D6-9416-88A757D18E02">
<?foreach File in plugin.json;Community.PowerToys.Run.Plugin.UnitConverter.deps.json;Community.PowerToys.Run.Plugin.UnitConverter.dll?>
<File Id="UnitConverter_$(var.File)" Source="$(var.BinX64Dir)modules\launcher\Plugins\Community.UnitConverter\$(var.File)" />
<?endforeach?>
</Component>
<Component Id="UnitConverterImagesComponent" Directory="UnitConverterImagesFolder" Guid="16ABD217-0898-47B2-89D9-AF1ABF00F543">
<File Id="UnitConverterLight" Source="$(var.BinX64Dir)modules\launcher\Plugins\Community.UnitConverter\Images\unitconverter.light.png" />
<File Id="UnitConverterDark" Source="$(var.BinX64Dir)modules\launcher\Plugins\Community.UnitConverter\Images\unitconverter.dark.png" />
</Component>
<!-- Uri Plugin -->
<Component Id="UriComponent" Directory="UriPluginFolder" Guid="C7DC8F88-554C-4375-9510-9435399B5D3D">
<?foreach File in plugin.json;Microsoft.Plugin.Uri.deps.json;Microsoft.Plugin.Uri.dll;ManagedTelemetry.dll?>

View File

@ -0,0 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<IsPackable>false</IsPackable>
<Platforms>x64</Platforms>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\..\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\..\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Community.PowerToys.Run.Plugin.UnitConverter\Community.PowerToys.Run.Plugin.UnitConverter.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,67 @@
using System.Globalization;
using NUnit.Framework;
using Wox.Plugin;
namespace Community.PowerToys.Run.Plugin.UnitConverter.UnitTest
{
[TestFixture]
public class InputInterpreterTests
{
[TestCase(new string[] { "1,5'" }, new string[] { "1,5", "'" })]
[TestCase(new string[] { "1.5'" }, new string[] { "1.5", "'" })]
[TestCase(new string[] { "1'" }, new string[] { "1", "'" })]
[TestCase(new string[] { "1'5\"" }, new string[] { "1", "'", "5", "\"" })]
[TestCase(new string[] { "5\"" }, new string[] { "5", "\"" })]
[TestCase(new string[] { "1'5" }, new string[] { "1", "'", "5" })]
public void RegexSplitsInput(string[] input, string[] expectedResult)
{
string[] shortsplit = InputInterpreter.RegexSplitter(input);
Assert.AreEqual(expectedResult, shortsplit);
}
[TestCase(new string[] { "1cm", "to", "mm" }, new string[] { "1", "cm", "to", "mm" })]
public void InsertsSpaces(string[] input, string[] expectedResult)
{
InputInterpreter.InputSpaceInserter(ref input);
Assert.AreEqual(expectedResult, input);
}
[TestCase(new string[] { "1'", "in", "cm" }, new string[] { "1", "foot", "in", "cm" })]
[TestCase(new string[] { "1\"", "in", "cm" }, new string[] { "1", "inch", "in", "cm" })]
[TestCase(new string[] { "1'6", "in", "cm" }, new string[] { "1.5", "foot", "in", "cm" })]
[TestCase(new string[] { "1'6\"", "in", "cm" }, new string[] { "1.5", "foot", "in", "cm" })]
public void HandlesShorthandFeetInchNotation(string[] input, string[] expectedResult)
{
InputInterpreter.ShorthandFeetInchHandler(ref input, CultureInfo.InvariantCulture);
Assert.AreEqual(expectedResult, input);
}
[TestCase(new string[] { "5", "CeLsIuS", "in", "faHrenheiT" }, new string[] { "5", "DegreeCelsius", "in", "DegreeFahrenheit" })]
[TestCase(new string[] { "5", "f", "in", "celsius" }, new string[] { "5", "°f", "in", "DegreeCelsius" })]
[TestCase(new string[] { "5", "c", "in", "f" }, new string[] { "5", "°c", "in", "°f" })]
[TestCase(new string[] { "5", "f", "in", "c" }, new string[] { "5", "°f", "in", "°c" })]
public void PrefixesDegrees(string[] input, string[] expectedResult)
{
InputInterpreter.DegreePrefixer(ref input);
Assert.AreEqual(expectedResult, input);
}
[TestCase("a f in c")]
[TestCase("12 f in")]
public void ParseInvalidQueries(string queryString)
{
Query query = new Query(queryString);
var result = InputInterpreter.Parse(query);
Assert.AreEqual(null, result);
}
[TestCase("12 f in c", 12)]
[TestCase("10m to cm", 10)]
public void ParseValidQueries(string queryString, double result)
{
Query query = new Query(queryString);
var convertModel = InputInterpreter.Parse(query);
Assert.AreEqual(result, convertModel.Value);
}
}
}

View File

@ -0,0 +1,42 @@
using System.Globalization;
using System.Linq;
using NUnit.Framework;
namespace Community.PowerToys.Run.Plugin.UnitConverter.UnitTest
{
[TestFixture]
public class UnitHandlerTests
{
[Test]
public void HandleTemperature()
{
var convertModel = new ConvertModel(1, "DegreeCelsius", "DegreeFahrenheit");
double result = UnitHandler.ConvertInput(convertModel, UnitsNet.QuantityType.Temperature);
Assert.AreEqual(33.79999999999999d, result);
}
[Test]
public void HandleLength()
{
var convertModel = new ConvertModel(1, "meter", "centimeter");
double result = UnitHandler.ConvertInput(convertModel, UnitsNet.QuantityType.Length);
Assert.AreEqual(100, result);
}
[Test]
public void HandlesByteCapitals()
{
var convertModel = new ConvertModel(1, "kB", "kb");
double result = UnitHandler.ConvertInput(convertModel, UnitsNet.QuantityType.Information);
Assert.AreEqual(8, result);
}
[Test]
public void HandleInvalidModel()
{
var convertModel = new ConvertModel(1, "aa", "bb");
var results = UnitHandler.Convert(convertModel);
Assert.AreEqual(0, results.Count());
}
}
}

View File

@ -0,0 +1,99 @@
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<Import Project="..\..\..\..\Version.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectGuid>{BB23A474-5058-4F75-8FA3-5FE3DE53CDF4}</ProjectGuid>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Community.PowerToys.Run.Plugin.UnitConverter</RootNamespace>
<AssemblyName>Community.PowerToys.Run.Plugin.UnitConverter</AssemblyName>
<Version>$(Version).0</Version>
<useWPF>true</useWPF>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Platforms>x64</Platforms>
<NeutralLanguage>en-US</NeutralLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Community.UnitConverter\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<WarningLevel>4</WarningLevel>
<Optimize>false</Optimize>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutputPath>..\..\..\..\..\x64\Release\modules\launcher\Plugins\Community.UnitConverter\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>7.3</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Include="..\..\..\..\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\..\..\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Wox.Infrastructure\Wox.Infrastructure.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Wox.Plugin\Wox.Plugin.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="UnitsNet" Version="4.76.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Update="Images\unitconverter.light.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Images\unitconverter.dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Community.PowerToys.Run.Plugin.UnitConverter
{
public class ConvertModel
{
public double Value { get; set; }
public string FromUnit { get; set; }
public string ToUnit { get; set; }
public ConvertModel()
{
}
public ConvertModel(double value, string fromUnit, string toUnit)
{
Value = value;
FromUnit = fromUnit;
ToUnit = toUnit;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,181 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using UnitsNet;
using Wox.Plugin;
namespace Community.PowerToys.Run.Plugin.UnitConverter
{
public static class InputInterpreter
{
private static string pattern = @"(?<=\d)(?![,.])(?=\D)|(?<=\D)(?<![,.])(?=\d)";
public static string[] RegexSplitter(string[] split)
{
return Regex.Split(split[0], pattern);
}
/// <summary>
/// Separates input like: "1ft in cm" to "1 ft in cm"
/// </summary>
public static void InputSpaceInserter(ref string[] split)
{
if (split.Length != 3)
{
return;
}
string[] parseInputWithoutSpace = Regex.Split(split[0], pattern);
if (parseInputWithoutSpace.Length > 1)
{
string[] firstEntryRemoved = split.Skip(1).ToArray();
string[] newSplit = new string[] { parseInputWithoutSpace[0], parseInputWithoutSpace[1] };
split = newSplit.Concat(firstEntryRemoved).ToArray();
}
}
/// <summary>
/// Replaces a split input array with shorthand feet/inch notation (1', 1'2" etc) to 'x foot in cm'.
/// </summary>
public static void ShorthandFeetInchHandler(ref string[] split, CultureInfo culture)
{
if (!split[0].Contains('\'') && !split[0].Contains('\"'))
{
return;
}
// catches 1' || 1" || 1'2 || 1'2" in cm
// by converting it to "x foot in cm"
if (split.Length == 3)
{
string[] shortsplit = RegexSplitter(split);
switch (shortsplit.Length)
{
case 2:
// ex: 1' & 1"
if (shortsplit[1] == "\'")
{
string[] newInput = new string[] { shortsplit[0], "foot", split[1], split[2] };
split = newInput;
}
else if (shortsplit[1] == "\"")
{
string[] newInput = new string[] { shortsplit[0], "inch", split[1], split[2] };
split = newInput;
}
break;
case 3:
case 4:
// ex: 1'2 and 1'2"
if (shortsplit[1] == "\'")
{
bool isFeet = double.TryParse(shortsplit[0], NumberStyles.AllowDecimalPoint, culture, out double feet);
bool isInches = double.TryParse(shortsplit[2], NumberStyles.AllowDecimalPoint, culture, out double inches);
if (!isFeet || !isInches)
{
// atleast one could not be parsed correctly
break;
}
string convertedTotalInFeet = Length.FromFeetInches(feet, inches).Feet.ToString(culture);
string[] newInput = new string[] { convertedTotalInFeet, "foot", split[1], split[2] };
split = newInput;
}
break;
default:
break;
}
}
}
/// <summary>
/// Adds degree prefixes to degree units for shorthand notation. E.g. '10 c in fahrenheit' becomes '10 °c in DegreeFahrenheit'.
/// </summary>
public static void DegreePrefixer(ref string[] split)
{
switch (split[1].ToLower())
{
case "celsius":
split[1] = "DegreeCelsius";
break;
case "fahrenheit":
split[1] = "DegreeFahrenheit";
break;
case "c":
split[1] = "°c";
break;
case "f":
split[1] = "°f";
break;
default:
break;
}
switch (split[3].ToLower())
{
case "celsius":
split[3] = "DegreeCelsius";
break;
case "fahrenheit":
split[3] = "DegreeFahrenheit";
break;
case "c":
split[3] = "°c";
break;
case "f":
split[3] = "°f";
break;
default:
break;
}
}
public static ConvertModel Parse(Query query)
{
string[] split = query.Search.Split(' ');
InputInterpreter.ShorthandFeetInchHandler(ref split, CultureInfo.CurrentCulture);
InputInterpreter.InputSpaceInserter(ref split);
if (split.Length != 4)
{
// deny any other queries than:
// 10 ft in cm
// 10 ft to cm
return null;
}
InputInterpreter.DegreePrefixer(ref split);
if (!double.TryParse(split[0], out double value))
{
return null;
}
return new ConvertModel()
{
Value = value,
FromUnit = split[1],
ToUnit = split[3],
};
}
}
}

View File

@ -0,0 +1,180 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Input;
using ManagedCommon;
using UnitsNet;
using Wox.Plugin;
namespace Community.PowerToys.Run.Plugin.UnitConverter
{
public class Main : IPlugin, IPluginI18n, IContextMenu, IDisposable
{
public string Name => Properties.Resources.plugin_name;
public string Description => Properties.Resources.plugin_description;
private PluginInitContext _context;
private static string _icon_path;
private bool _disposed;
public void Init(PluginInitContext context)
{
if (context == null)
{
throw new ArgumentNullException(paramName: nameof(context));
}
_context = context;
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
}
public List<Result> Query(Query query)
{
if (query == null)
{
throw new ArgumentNullException(paramName: nameof(query));
}
// Parse
ConvertModel convertModel = InputInterpreter.Parse(query);
if (convertModel == null)
{
return new List<Result>();
}
// Convert
return UnitHandler.Convert(convertModel)
.Select(x => GetResult(x))
.ToList();
}
private Result GetResult(UnitConversionResult result)
{
return new Result
{
ContextData = result,
Title = string.Format("{0} {1}", result.ConvertedValue, result.UnitName),
IcoPath = _icon_path,
Score = 300,
SubTitle = string.Format(Properties.Resources.copy_to_clipboard, result.QuantityType),
Action = c =>
{
var ret = false;
var thread = new Thread(() =>
{
try
{
Clipboard.SetText(result.ConvertedValue.ToString());
ret = true;
}
catch (ExternalException)
{
MessageBox.Show(Properties.Resources.copy_failed);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return ret;
},
};
}
private ContextMenuResult CreateContextMenuEntry(UnitConversionResult result)
{
return new ContextMenuResult
{
PluginName = Name,
Title = Properties.Resources.context_menu_copy,
Glyph = "\xE8C8",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.Enter,
Action = _ =>
{
bool ret = false;
var thread = new Thread(() =>
{
try
{
Clipboard.SetText(result.ConvertedValue.ToString());
ret = true;
}
catch (ExternalException)
{
MessageBox.Show(Properties.Resources.copy_failed);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return ret;
},
};
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
if (!(selectedResult?.ContextData is UnitConversionResult))
{
return new List<ContextMenuResult>();
}
List<ContextMenuResult> contextResults = new List<ContextMenuResult>();
UnitConversionResult result = selectedResult.ContextData as UnitConversionResult;
contextResults.Add(CreateContextMenuEntry(result));
return contextResults;
}
public string GetTranslatedPluginTitle()
{
return Properties.Resources.plugin_name;
}
public string GetTranslatedPluginDescription()
{
return Properties.Resources.plugin_description;
}
private void OnThemeChanged(Theme _, Theme newTheme)
{
UpdateIconPath(newTheme);
}
private static void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
_icon_path = "Images/unitconverter.light.png";
}
else
{
_icon_path = "Images/unitconverter.dark.png";
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.API.ThemeChanged -= OnThemeChanged;
_disposed = true;
}
}
}
}
}

View File

@ -0,0 +1,108 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Community.PowerToys.Run.Plugin.UnitConverter.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Community.PowerToys.Run.Plugin.UnitConverter.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Copy (Enter).
/// </summary>
public static string context_menu_copy {
get {
return ResourceManager.GetString("context_menu_copy", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy failed.
/// </summary>
public static string copy_failed {
get {
return ResourceManager.GetString("copy_failed", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copy {0} to clipboard.
/// </summary>
public static string copy_to_clipboard {
get {
return ResourceManager.GetString("copy_to_clipboard", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Provides unit conversion (e.g. 10 ft in m)..
/// </summary>
public static string plugin_description {
get {
return ResourceManager.GetString("plugin_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unit Converter.
/// </summary>
public static string plugin_name {
get {
return ResourceManager.GetString("plugin_name", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="context_menu_copy" xml:space="preserve">
<value>Copy (Enter)</value>
</data>
<data name="copy_failed" xml:space="preserve">
<value>Copy failed</value>
</data>
<data name="copy_to_clipboard" xml:space="preserve">
<value>Copy {0} to clipboard</value>
</data>
<data name="plugin_description" xml:space="preserve">
<value>Provides unit conversion (e.g. 10 ft in m).</value>
</data>
<data name="plugin_name" xml:space="preserve">
<value>Unit Converter</value>
</data>
</root>

View File

@ -0,0 +1,20 @@
using UnitsNet;
namespace Community.PowerToys.Run.Plugin.UnitConverter
{
public class UnitConversionResult
{
public double ConvertedValue { get; }
public string UnitName { get; }
public QuantityType QuantityType { get; }
public UnitConversionResult(double convertedValue, string unitName, QuantityType quantityType)
{
ConvertedValue = convertedValue;
UnitName = unitName;
QuantityType = quantityType;
}
}
}

View File

@ -0,0 +1,90 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UnitsNet;
namespace Community.PowerToys.Run.Plugin.UnitConverter
{
public static class UnitHandler
{
private static readonly int _roundingFractionalDigits = 4;
private static readonly QuantityType[] _included = new QuantityType[]
{
QuantityType.Acceleration,
QuantityType.Angle,
QuantityType.Area,
QuantityType.Duration,
QuantityType.Energy,
QuantityType.Information,
QuantityType.Length,
QuantityType.Mass,
QuantityType.Power,
QuantityType.Pressure,
QuantityType.Speed,
QuantityType.Temperature,
QuantityType.Volume,
};
/// <summary>
/// Given string representation of unit, converts it to the enum.
/// </summary>
/// <returns>Corresponding enum or null.</returns>
private static Enum GetUnitEnum(string unit, QuantityInfo unitInfo)
{
UnitInfo first = Array.Find(unitInfo.UnitInfos, info => info.Name.ToLower() == unit.ToLower());
if (first != null)
{
return first.Value;
}
if (UnitParser.Default.TryParse(unit, unitInfo.UnitType, out Enum enum_unit))
{
return enum_unit;
}
return null;
}
/// <summary>
/// Given parsed ConvertModel, computes result. (E.g "1 foot in cm").
/// </summary>
/// <returns>The converted value as a double.</returns>
public static double ConvertInput(ConvertModel convertModel, QuantityType quantityType)
{
QuantityInfo unitInfo = Quantity.GetInfo(quantityType);
var fromUnit = GetUnitEnum(convertModel.FromUnit, unitInfo);
var toUnit = GetUnitEnum(convertModel.ToUnit, unitInfo);
if (fromUnit != null && toUnit != null)
{
return UnitsNet.UnitConverter.Convert(convertModel.Value, fromUnit, toUnit);
}
return double.NaN;
}
/// <summary>
/// Given ConvertModel returns collection of possible results.
/// </summary>
/// <returns>The converted value as a double.</returns>
public static IEnumerable<UnitConversionResult> Convert(ConvertModel convertModel)
{
var results = new List<UnitConversionResult>();
foreach (QuantityType quantityType in _included)
{
double convertedValue = UnitHandler.ConvertInput(convertModel, quantityType);
if (!double.IsNaN(convertedValue))
{
UnitConversionResult result = new UnitConversionResult(Math.Round(convertedValue, _roundingFractionalDigits), convertModel.ToUnit, quantityType);
results.Add(result);
}
}
return results;
}
}
}

View File

@ -0,0 +1,13 @@
{
"ID": "aa0ee9daff654fb7be452c2d77c471b9",
"ActionKeyword": "%%",
"IsGlobal": false,
"Name": "Unit Converter",
"Author": "ThiefZero",
"Version": "0.0.1",
"Language": "csharp",
"Website": "https://github.com/ThiefZero",
"IcoPathDark": "Images\\unitconverter.dark.png",
"IcoPathLight": "Images\\unitconverter.light.png",
"ExecuteFileName": "Community.PowerToys.Run.Plugin.UnitConverter.dll"
}

View File

@ -74,7 +74,6 @@ namespace Microsoft.PowerToys.Run.Plugin.Calculator
UpdateIconPath(Context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)

View File

@ -113,6 +113,7 @@
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="UnitsNet" Version="4.76.0" />
</ItemGroup>
<ItemGroup>