mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-01 09:56:32 +01:00
Compare commits
7 Commits
shawn/APIm
...
leilzh/ima
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e67ae1bd8 | ||
|
|
f58f4dc5ad | ||
|
|
e4dda98b6e | ||
|
|
b61231b3dc | ||
|
|
aac813cb71 | ||
|
|
02fa0daea5 | ||
|
|
dcfb93b26d |
@@ -131,6 +131,8 @@
|
||||
|
||||
"PowerToys.ImageResizer.exe",
|
||||
"PowerToys.ImageResizer.dll",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerCLI.exe",
|
||||
"WinUI3Apps\\PowerToys.ImageResizerCLI.dll",
|
||||
"PowerToys.ImageResizerExt.dll",
|
||||
"PowerToys.ImageResizerContextMenu.dll",
|
||||
"ImageResizerContextMenuPackage.msix",
|
||||
|
||||
@@ -442,6 +442,10 @@
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
<Project Path="src/modules/imageresizer/ImageResizerCLI/ImageResizerCLI.csproj">
|
||||
<Platform Solution="*|ARM64" Project="ARM64" />
|
||||
<Platform Solution="*|x64" Project="x64" />
|
||||
</Project>
|
||||
</Folder>
|
||||
<Folder Name="/modules/imageresizer/Tests/">
|
||||
<Project Path="src/modules/imageresizer/tests/ImageResizer.UnitTests.csproj">
|
||||
|
||||
93
doc/devdocs/cli-conventions.md
Normal file
93
doc/devdocs/cli-conventions.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# CLI Conventions
|
||||
|
||||
This document describes the conventions for implementing command-line interfaces (CLI) in PowerToys modules.
|
||||
|
||||
## Library
|
||||
|
||||
Use the **System.CommandLine** library for CLI argument parsing. This is already defined in `Directory.Packages.props`:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||
```
|
||||
|
||||
Add the reference to your project:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
```
|
||||
|
||||
## Option Naming and Definition
|
||||
|
||||
- Use `--kebab-case` for long form (e.g., `--shrink-only`).
|
||||
- Use single `-x` for short form (e.g., `-s`, `-w`).
|
||||
- Define aliases as static readonly arrays: `["--silent", "-s"]`.
|
||||
- Create options using `Option<T>` with descriptive help text.
|
||||
- Add validators for options that require range or format checking.
|
||||
|
||||
## RootCommand Setup
|
||||
|
||||
- Create a `RootCommand` with a brief description.
|
||||
- Add all options and arguments to the command.
|
||||
|
||||
## Parsing
|
||||
|
||||
- Use `Parser(rootCommand).Parse(args)` to parse CLI arguments.
|
||||
- Extract option values using `parseResult.GetValueForOption()`.
|
||||
- Note: Use `Parser` directly; `RootCommand.Parse()` may not be available with the pinned System.CommandLine version.
|
||||
|
||||
### Parse/Validation Errors
|
||||
|
||||
- On parse/validation errors, print error messages and usage, then exit with non-zero code.
|
||||
|
||||
## Examples
|
||||
|
||||
Reference implementations:
|
||||
- Awake: `src/modules/Awake/Awake/Program.cs`
|
||||
- ImageResizer: `src/modules/imageresizer/ui/Cli/`
|
||||
|
||||
## Help Output
|
||||
|
||||
- Provide a `PrintUsage()` method for custom help formatting if needed.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Consistency**: Follow existing module patterns.
|
||||
2. **Documentation**: Always provide help text for each option.
|
||||
3. **Validation**: Validate input and provide clear error messages.
|
||||
4. **Atomicity**: Make one logical change per PR; avoid drive-by refactors.
|
||||
5. **Build/Test Discipline**: Build and test synchronously, one terminal per operation.
|
||||
6. **Style**: Follow repo analyzers (`.editorconfig`, StyleCop) and formatting rules.
|
||||
|
||||
## Logging Requirements
|
||||
|
||||
- Use `ManagedCommon.Logger` for consistent logging.
|
||||
- Initialize logging early in `Main()`.
|
||||
- Use dual output (console + log file) for errors and warnings to ensure visibility.
|
||||
- Reference: `src/modules/imageresizer/ui/Cli/CliLogger.cs`
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Exit Codes
|
||||
|
||||
- `0`: Success
|
||||
- `1`: General error (parsing, validation, runtime)
|
||||
- `2`: Invalid arguments (optional)
|
||||
|
||||
### Exception Handling
|
||||
|
||||
- Always wrap `Main()` in try-catch for unhandled exceptions.
|
||||
- Log exceptions before exiting with non-zero code.
|
||||
- Display user-friendly error messages to stderr.
|
||||
- Preserve detailed stack traces in log files only.
|
||||
|
||||
## Testing Requirements
|
||||
|
||||
- Include tests for argument parsing, validation, and edge cases.
|
||||
- Place CLI tests in module-specific test projects (e.g., `src/modules/[module]/tests/*CliTests.cs`).
|
||||
|
||||
## Signing and Deployment
|
||||
|
||||
- CLI executables are signed automatically in CI/CD.
|
||||
- **New CLI tools**: Add your executable and dll to `.pipelines/ESRPSigning_core.json` in the signing list.
|
||||
- CLI executables are deployed alongside their parent module (e.g., `C:\Program Files\PowerToys\modules\[ModuleName]\`).
|
||||
- Use self-contained deployment (import `Common.SelfContained.props`).
|
||||
@@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\Common.SelfContained.props" />
|
||||
<Import Project="..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>PowerToys.ImageResizerCLI</AssemblyTitle>
|
||||
<AssemblyDescription>PowerToys Image Resizer Command Line Interface</AssemblyDescription>
|
||||
<Description>PowerToys Image Resizer CLI</Description>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<OutputPath>..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps\</OutputPath>
|
||||
<AssemblyName>PowerToys.ImageResizerCLI</AssemblyName>
|
||||
<NoWarn>$(NoWarn);SA1500;SA1402;CA1852</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ui\ImageResizerUI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Force using WindowsDesktop runtime to ensure consistent dll versions with other projects -->
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.WindowsDesktop.App.WPF" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
50
src/modules/imageresizer/ImageResizerCLI/Program.cs
Normal file
50
src/modules/imageresizer/ImageResizerCLI/Program.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
using ImageResizer.Cli;
|
||||
using ManagedCommon;
|
||||
|
||||
namespace ImageResizerCLI;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
string appLanguage = LanguageHelper.LoadLanguage();
|
||||
if (!string.IsNullOrEmpty(appLanguage))
|
||||
{
|
||||
System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(appLanguage);
|
||||
}
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
// Ignore invalid culture and fall back to default.
|
||||
}
|
||||
|
||||
Console.InputEncoding = Encoding.Unicode;
|
||||
|
||||
// Initialize logger to file (same as other modules)
|
||||
CliLogger.Initialize("\\ImageResizer\\Logs");
|
||||
CliLogger.Info($"ImageResizerCLI started with {args.Length} argument(s)");
|
||||
|
||||
try
|
||||
{
|
||||
var executor = new ImageResizerCliExecutor();
|
||||
return executor.Run(args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
CliLogger.Error($"Unhandled exception: {ex.Message}");
|
||||
CliLogger.Error($"Stack trace: {ex.StackTrace}");
|
||||
Console.Error.WriteLine($"Fatal error: {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
320
src/modules/imageresizer/tests/Cli/CliSettingsApplierTests.cs
Normal file
320
src/modules/imageresizer/tests/Cli/CliSettingsApplierTests.cs
Normal file
@@ -0,0 +1,320 @@
|
||||
// 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.
|
||||
|
||||
using ImageResizer.Cli;
|
||||
using ImageResizer.Models;
|
||||
using ImageResizer.Properties;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ImageResizer.Tests.Cli
|
||||
{
|
||||
[TestClass]
|
||||
public class CliSettingsApplierTests
|
||||
{
|
||||
private Settings CreateDefaultSettings()
|
||||
{
|
||||
var settings = new Settings();
|
||||
settings.Sizes.Add(new ResizeSize(0, "Small", ResizeFit.Fit, 854, 480, ResizeUnit.Pixel));
|
||||
settings.Sizes.Add(new ResizeSize(1, "Medium", ResizeFit.Fit, 1366, 768, ResizeUnit.Pixel));
|
||||
settings.Sizes.Add(new ResizeSize(2, "Large", ResizeFit.Fit, 1920, 1080, ResizeUnit.Pixel));
|
||||
return settings;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithCustomWidth_SetsCustomSizeWidth()
|
||||
{
|
||||
var options = new CliOptions { Width = 800 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(800.0, settings.CustomSize.Width);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithCustomHeight_SetsCustomSizeHeight()
|
||||
{
|
||||
var options = new CliOptions { Height = 600 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(600.0, settings.CustomSize.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithCustomSize_SelectsCustomSizeIndex()
|
||||
{
|
||||
var options = new CliOptions { Width = 800, Height = 600 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
// Custom size index should be settings.Sizes.Count
|
||||
Assert.AreEqual(settings.Sizes.Count, settings.SelectedSizeIndex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithZeroWidth_SetsZeroForAutoCalculation()
|
||||
{
|
||||
var options = new CliOptions { Width = 0, Height = 600 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(0.0, settings.CustomSize.Width);
|
||||
Assert.AreEqual(600.0, settings.CustomSize.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithZeroHeight_SetsZeroForAutoCalculation()
|
||||
{
|
||||
var options = new CliOptions { Width = 800, Height = 0 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(800.0, settings.CustomSize.Width);
|
||||
Assert.AreEqual(0.0, settings.CustomSize.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithNullWidthAndHeight_DoesNotModifyCustomSize()
|
||||
{
|
||||
var options = new CliOptions { Width = null, Height = null };
|
||||
var settings = CreateDefaultSettings();
|
||||
var originalWidth = settings.CustomSize.Width;
|
||||
var originalHeight = settings.CustomSize.Height;
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
// When both null, should not modify CustomSize (keeps default 1024x640)
|
||||
Assert.AreEqual(originalWidth, settings.CustomSize.Width);
|
||||
Assert.AreEqual(originalHeight, settings.CustomSize.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithUnit_SetsCustomSizeUnit()
|
||||
{
|
||||
var options = new CliOptions { Width = 100, Unit = ResizeUnit.Percent };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(ResizeUnit.Percent, settings.CustomSize.Unit);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithFit_SetsCustomSizeFit()
|
||||
{
|
||||
var options = new CliOptions { Width = 800, Fit = ResizeFit.Fill };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(ResizeFit.Fill, settings.CustomSize.Fit);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithValidSizeIndex_SetsSelectedSizeIndex()
|
||||
{
|
||||
var options = new CliOptions { SizeIndex = 1 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(1, settings.SelectedSizeIndex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithInvalidSizeIndex_DoesNotChangeSelection()
|
||||
{
|
||||
var options = new CliOptions { SizeIndex = 99 };
|
||||
var settings = CreateDefaultSettings();
|
||||
var originalIndex = settings.SelectedSizeIndex;
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
// Should remain unchanged when invalid
|
||||
Assert.AreEqual(originalIndex, settings.SelectedSizeIndex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithNegativeSizeIndex_DoesNotChangeSelection()
|
||||
{
|
||||
var options = new CliOptions { SizeIndex = -1 };
|
||||
var settings = CreateDefaultSettings();
|
||||
var originalIndex = settings.SelectedSizeIndex;
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(originalIndex, settings.SelectedSizeIndex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithShrinkOnly_SetsShrinkOnly()
|
||||
{
|
||||
var options = new CliOptions { ShrinkOnly = true };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.IsTrue(settings.ShrinkOnly);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithReplace_SetsReplace()
|
||||
{
|
||||
var options = new CliOptions { Replace = true };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.IsTrue(settings.Replace);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithIgnoreOrientation_SetsIgnoreOrientation()
|
||||
{
|
||||
var options = new CliOptions { IgnoreOrientation = true };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.IsTrue(settings.IgnoreOrientation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithRemoveMetadata_SetsRemoveMetadata()
|
||||
{
|
||||
var options = new CliOptions { RemoveMetadata = true };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.IsTrue(settings.RemoveMetadata);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithJpegQualityLevel_SetsJpegQualityLevel()
|
||||
{
|
||||
var options = new CliOptions { JpegQualityLevel = 85 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(85, settings.JpegQualityLevel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithKeepDateModified_SetsKeepDateModified()
|
||||
{
|
||||
var options = new CliOptions { KeepDateModified = true };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.IsTrue(settings.KeepDateModified);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithFileName_SetsFileName()
|
||||
{
|
||||
var options = new CliOptions { FileName = "%1 (%2)" };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual("%1 (%2)", settings.FileName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithEmptyFileName_DoesNotChangeFileName()
|
||||
{
|
||||
var options = new CliOptions { FileName = string.Empty };
|
||||
var settings = CreateDefaultSettings();
|
||||
var originalFileName = settings.FileName;
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(originalFileName, settings.FileName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithMultipleOptions_AppliesAllOptions()
|
||||
{
|
||||
var options = new CliOptions
|
||||
{
|
||||
Width = 800,
|
||||
Height = 600,
|
||||
Unit = ResizeUnit.Percent,
|
||||
Fit = ResizeFit.Fill,
|
||||
ShrinkOnly = true,
|
||||
Replace = true,
|
||||
IgnoreOrientation = true,
|
||||
RemoveMetadata = true,
|
||||
JpegQualityLevel = 90,
|
||||
KeepDateModified = true,
|
||||
FileName = "test_%2",
|
||||
};
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(800.0, settings.CustomSize.Width);
|
||||
Assert.AreEqual(600.0, settings.CustomSize.Height);
|
||||
Assert.AreEqual(ResizeUnit.Percent, settings.CustomSize.Unit);
|
||||
Assert.AreEqual(ResizeFit.Fill, settings.CustomSize.Fit);
|
||||
Assert.IsTrue(settings.ShrinkOnly);
|
||||
Assert.IsTrue(settings.Replace);
|
||||
Assert.IsTrue(settings.IgnoreOrientation);
|
||||
Assert.IsTrue(settings.RemoveMetadata);
|
||||
Assert.AreEqual(90, settings.JpegQualityLevel);
|
||||
Assert.IsTrue(settings.KeepDateModified);
|
||||
Assert.AreEqual("test_%2", settings.FileName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_CustomSizeTakesPrecedence_OverSizeIndex()
|
||||
{
|
||||
var options = new CliOptions
|
||||
{
|
||||
Width = 800,
|
||||
Height = 600,
|
||||
SizeIndex = 1, // Should be ignored when Width/Height specified
|
||||
};
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
// Custom size should be selected, not preset
|
||||
Assert.AreEqual(settings.Sizes.Count, settings.SelectedSizeIndex);
|
||||
Assert.AreEqual(800.0, settings.CustomSize.Width);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithOnlyWidth_StillSelectsCustomSize()
|
||||
{
|
||||
var options = new CliOptions { Width = 800 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(settings.Sizes.Count, settings.SelectedSizeIndex);
|
||||
Assert.AreEqual(800.0, settings.CustomSize.Width);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Apply_WithOnlyHeight_StillSelectsCustomSize()
|
||||
{
|
||||
var options = new CliOptions { Height = 600 };
|
||||
var settings = CreateDefaultSettings();
|
||||
|
||||
CliSettingsApplier.Apply(options, settings);
|
||||
|
||||
Assert.AreEqual(settings.Sizes.Count, settings.SelectedSizeIndex);
|
||||
Assert.AreEqual(600.0, settings.CustomSize.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
268
src/modules/imageresizer/tests/Models/CliOptionsTests.cs
Normal file
268
src/modules/imageresizer/tests/Models/CliOptionsTests.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using ImageResizer.Cli.Commands;
|
||||
using ImageResizer.Models;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ImageResizer.Tests.Models
|
||||
{
|
||||
[TestClass]
|
||||
public class CliOptionsTests
|
||||
{
|
||||
private static readonly string[] _multiFileArgs = new[] { "test1.jpg", "test2.jpg", "test3.jpg" };
|
||||
private static readonly string[] _mixedOptionsArgs = new[] { "--width", "800", "test1.jpg", "--height", "600", "test2.jpg" };
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithValidWidth_SetsWidth()
|
||||
{
|
||||
var args = new[] { "--width", "800", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(800.0, options.Width);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithValidHeight_SetsHeight()
|
||||
{
|
||||
var args = new[] { "--height", "600", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(600.0, options.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithShortWidthAlias_WorksIdentically()
|
||||
{
|
||||
var longFormArgs = new[] { "--width", "800", "test.jpg" };
|
||||
var shortFormArgs = new[] { "-w", "800", "test.jpg" };
|
||||
var longForm = CliOptions.Parse(longFormArgs);
|
||||
var shortForm = CliOptions.Parse(shortFormArgs);
|
||||
|
||||
Assert.AreEqual(longForm.Width, shortForm.Width);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithShortHeightAlias_WorksIdentically()
|
||||
{
|
||||
var longFormArgs = new[] { "--height", "600", "test.jpg" };
|
||||
var shortFormArgs = new[] { "-h", "600", "test.jpg" };
|
||||
var longForm = CliOptions.Parse(longFormArgs);
|
||||
var shortForm = CliOptions.Parse(shortFormArgs);
|
||||
|
||||
Assert.AreEqual(longForm.Height, shortForm.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithValidUnit_SetsUnit()
|
||||
{
|
||||
var args = new[] { "--unit", "Percent", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(ResizeUnit.Percent, options.Unit);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithValidFit_SetsFit()
|
||||
{
|
||||
var args = new[] { "--fit", "Fill", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(ResizeFit.Fill, options.Fit);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithSizeIndex_SetsSizeIndex()
|
||||
{
|
||||
var args = new[] { "--size", "2", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(2, options.SizeIndex);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithShrinkOnly_SetsShrinkOnly()
|
||||
{
|
||||
var args = new[] { "--shrink-only", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(true, options.ShrinkOnly);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithReplace_SetsReplace()
|
||||
{
|
||||
var args = new[] { "--replace", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(true, options.Replace);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithIgnoreOrientation_SetsIgnoreOrientation()
|
||||
{
|
||||
var args = new[] { "--ignore-orientation", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(true, options.IgnoreOrientation);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithRemoveMetadata_SetsRemoveMetadata()
|
||||
{
|
||||
var args = new[] { "--remove-metadata", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(true, options.RemoveMetadata);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithValidQuality_SetsQuality()
|
||||
{
|
||||
var args = new[] { "--quality", "85", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(85, options.JpegQualityLevel);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithKeepDateModified_SetsKeepDateModified()
|
||||
{
|
||||
var args = new[] { "--keep-date-modified", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(true, options.KeepDateModified);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithFileName_SetsFileName()
|
||||
{
|
||||
var args = new[] { "--filename", "%1 (%2)", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual("%1 (%2)", options.FileName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithDestination_SetsDestinationDirectory()
|
||||
{
|
||||
var args = new[] { "--destination", "C:\\Output", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual("C:\\Output", options.DestinationDirectory);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithShortDestinationAlias_WorksIdentically()
|
||||
{
|
||||
var longFormArgs = new[] { "--destination", "C:\\Output", "test.jpg" };
|
||||
var shortFormArgs = new[] { "-d", "C:\\Output", "test.jpg" };
|
||||
var longForm = CliOptions.Parse(longFormArgs);
|
||||
var shortForm = CliOptions.Parse(shortFormArgs);
|
||||
|
||||
Assert.AreEqual(longForm.DestinationDirectory, shortForm.DestinationDirectory);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithProgressLines_SetsProgressLines()
|
||||
{
|
||||
var args = new[] { "--progress-lines", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(true, options.ProgressLines);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithAccessibleAlias_SetsProgressLines()
|
||||
{
|
||||
var args = new[] { "--accessible", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(true, options.ProgressLines);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithMultipleFiles_AddsAllFiles()
|
||||
{
|
||||
var args = _multiFileArgs;
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(3, options.Files.Count);
|
||||
CollectionAssert.Contains(options.Files.ToList(), "test1.jpg");
|
||||
CollectionAssert.Contains(options.Files.ToList(), "test2.jpg");
|
||||
CollectionAssert.Contains(options.Files.ToList(), "test3.jpg");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithMixedOptionsAndFiles_ParsesCorrectly()
|
||||
{
|
||||
var args = _mixedOptionsArgs;
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(800.0, options.Width);
|
||||
Assert.AreEqual(600.0, options.Height);
|
||||
Assert.AreEqual(2, options.Files.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithHelp_SetsShowHelp()
|
||||
{
|
||||
var args = new[] { "--help" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.IsTrue(options.ShowHelp);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithShowConfig_SetsShowConfig()
|
||||
{
|
||||
var args = new[] { "--show-config" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.IsTrue(options.ShowConfig);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithNoArguments_ReturnsEmptyOptions()
|
||||
{
|
||||
var args = Array.Empty<string>();
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.IsNotNull(options);
|
||||
Assert.AreEqual(0, options.Files.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithZeroWidth_AllowsZeroValue()
|
||||
{
|
||||
var args = new[] { "--width", "0", "--height", "600", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(0.0, options.Width);
|
||||
Assert.AreEqual(600.0, options.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_WithZeroHeight_AllowsZeroValue()
|
||||
{
|
||||
var args = new[] { "--width", "800", "--height", "0", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(800.0, options.Width);
|
||||
Assert.AreEqual(0.0, options.Height);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void Parse_CaseInsensitiveEnums_ParsesCorrectly()
|
||||
{
|
||||
var args = new[] { "--unit", "pixel", "--fit", "fit", "test.jpg" };
|
||||
var options = CliOptions.Parse(args);
|
||||
|
||||
Assert.AreEqual(ResizeUnit.Pixel, options.Unit);
|
||||
Assert.AreEqual(ResizeFit.Fit, options.Fit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,20 +25,27 @@ namespace ImageResizer.Models
|
||||
[TestMethod]
|
||||
public void FromCommandLineWorks()
|
||||
{
|
||||
// Use actual test files that exist in the test directory
|
||||
var testDir = Path.GetDirectoryName(typeof(ResizeBatchTests).Assembly.Location);
|
||||
var file1 = Path.Combine(testDir, "Test.jpg");
|
||||
var file2 = Path.Combine(testDir, "Test.png");
|
||||
var file3 = Path.Combine(testDir, "Test.gif");
|
||||
|
||||
var standardInput =
|
||||
"Image1.jpg" + EOL +
|
||||
"Image2.jpg";
|
||||
file1 + EOL +
|
||||
file2;
|
||||
var args = new[]
|
||||
{
|
||||
"/d", "OutputDir",
|
||||
"Image3.jpg",
|
||||
file3,
|
||||
};
|
||||
|
||||
var result = ResizeBatch.FromCommandLine(
|
||||
new StringReader(standardInput),
|
||||
args);
|
||||
|
||||
CollectionAssert.AreEquivalent(new List<string> { "Image1.jpg", "Image2.jpg", "Image3.jpg" }, result.Files.ToArray());
|
||||
var files = result.Files.Select(Path.GetFileName).ToArray();
|
||||
CollectionAssert.AreEquivalent(new List<string> { "Test.jpg", "Test.png", "Test.gif" }, files);
|
||||
|
||||
Assert.AreEqual("OutputDir", result.DestinationDirectory);
|
||||
}
|
||||
|
||||
28
src/modules/imageresizer/ui/Cli/CliLogger.cs
Normal file
28
src/modules/imageresizer/ui/Cli/CliLogger.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
// 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.
|
||||
|
||||
using ManagedCommon;
|
||||
|
||||
namespace ImageResizer.Cli
|
||||
{
|
||||
public static class CliLogger
|
||||
{
|
||||
private static bool _initialized;
|
||||
|
||||
public static void Initialize(string logSubFolder)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
Logger.InitializeLogger(logSubFolder);
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Info(string message) => Logger.LogInfo(message);
|
||||
|
||||
public static void Warn(string message) => Logger.LogWarning(message);
|
||||
|
||||
public static void Error(string message) => Logger.LogError(message);
|
||||
}
|
||||
}
|
||||
122
src/modules/imageresizer/ui/Cli/CliSettingsApplier.cs
Normal file
122
src/modules/imageresizer/ui/Cli/CliSettingsApplier.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
using ImageResizer.Models;
|
||||
using ImageResizer.Properties;
|
||||
|
||||
namespace ImageResizer.Cli
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies CLI options to Settings object.
|
||||
/// Separated from executor logic for Single Responsibility Principle.
|
||||
/// </summary>
|
||||
public static class CliSettingsApplier
|
||||
{
|
||||
/// <summary>
|
||||
/// Applies CLI options to the settings, overriding default values.
|
||||
/// </summary>
|
||||
/// <param name="cliOptions">The CLI options to apply.</param>
|
||||
/// <param name="settings">The settings to modify.</param>
|
||||
public static void Apply(CliOptions cliOptions, Settings settings)
|
||||
{
|
||||
// Handle complex size options first
|
||||
ApplySizeOptions(cliOptions, settings);
|
||||
|
||||
// Apply simple property mappings
|
||||
ApplySimpleOptions(cliOptions, settings);
|
||||
}
|
||||
|
||||
private static void ApplySizeOptions(CliOptions cliOptions, Settings settings)
|
||||
{
|
||||
if (cliOptions.Width.HasValue || cliOptions.Height.HasValue)
|
||||
{
|
||||
ApplyCustomSizeOptions(cliOptions, settings);
|
||||
}
|
||||
else if (cliOptions.SizeIndex.HasValue)
|
||||
{
|
||||
ApplyPresetSizeOption(cliOptions, settings);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyCustomSizeOptions(CliOptions cliOptions, Settings settings)
|
||||
{
|
||||
// Set dimensions (0 = auto-calculate for aspect ratio preservation)
|
||||
// Implementation: ResizeSize.ConvertToPixels() returns double.PositiveInfinity for 0 in Fit mode,
|
||||
// causing Math.Min(scaleX, scaleY) to preserve aspect ratio by selecting the non-zero scale.
|
||||
// For Fill/Stretch modes, 0 uses the original dimension instead.
|
||||
settings.CustomSize.Width = cliOptions.Width ?? 0;
|
||||
settings.CustomSize.Height = cliOptions.Height ?? 0;
|
||||
|
||||
// Apply optional properties
|
||||
if (cliOptions.Unit.HasValue)
|
||||
{
|
||||
settings.CustomSize.Unit = cliOptions.Unit.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.Fit.HasValue)
|
||||
{
|
||||
settings.CustomSize.Fit = cliOptions.Fit.Value;
|
||||
}
|
||||
|
||||
// Select custom size (index = Sizes.Count)
|
||||
settings.SelectedSizeIndex = settings.Sizes.Count;
|
||||
}
|
||||
|
||||
private static void ApplyPresetSizeOption(CliOptions cliOptions, Settings settings)
|
||||
{
|
||||
var index = cliOptions.SizeIndex.Value;
|
||||
|
||||
if (index >= 0 && index < settings.Sizes.Count)
|
||||
{
|
||||
settings.SelectedSizeIndex = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.Error.WriteLine(string.Format(CultureInfo.InvariantCulture, Resources.CLI_WarningInvalidSizeIndex, index));
|
||||
CliLogger.Warn($"Invalid size index: {index}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplySimpleOptions(CliOptions cliOptions, Settings settings)
|
||||
{
|
||||
if (cliOptions.ShrinkOnly.HasValue)
|
||||
{
|
||||
settings.ShrinkOnly = cliOptions.ShrinkOnly.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.Replace.HasValue)
|
||||
{
|
||||
settings.Replace = cliOptions.Replace.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.IgnoreOrientation.HasValue)
|
||||
{
|
||||
settings.IgnoreOrientation = cliOptions.IgnoreOrientation.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.RemoveMetadata.HasValue)
|
||||
{
|
||||
settings.RemoveMetadata = cliOptions.RemoveMetadata.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.JpegQualityLevel.HasValue)
|
||||
{
|
||||
settings.JpegQualityLevel = cliOptions.JpegQualityLevel.Value;
|
||||
}
|
||||
|
||||
if (cliOptions.KeepDateModified.HasValue)
|
||||
{
|
||||
settings.KeepDateModified = cliOptions.KeepDateModified.Value;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(cliOptions.FileName))
|
||||
{
|
||||
settings.FileName = cliOptions.FileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
using ImageResizer.Cli.Options;
|
||||
|
||||
namespace ImageResizer.Cli.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Root command for the ImageResizer CLI.
|
||||
/// </summary>
|
||||
public sealed class ImageResizerRootCommand : RootCommand
|
||||
{
|
||||
public ImageResizerRootCommand()
|
||||
: base("PowerToys Image Resizer - Resize images from command line")
|
||||
{
|
||||
HelpOption = new HelpOption();
|
||||
ShowConfigOption = new ShowConfigOption();
|
||||
DestinationOption = new DestinationOption();
|
||||
WidthOption = new WidthOption();
|
||||
HeightOption = new HeightOption();
|
||||
UnitOption = new UnitOption();
|
||||
FitOption = new FitOption();
|
||||
SizeOption = new SizeOption();
|
||||
ShrinkOnlyOption = new ShrinkOnlyOption();
|
||||
ReplaceOption = new ReplaceOption();
|
||||
IgnoreOrientationOption = new IgnoreOrientationOption();
|
||||
RemoveMetadataOption = new RemoveMetadataOption();
|
||||
QualityOption = new QualityOption();
|
||||
KeepDateModifiedOption = new KeepDateModifiedOption();
|
||||
FileNameOption = new FileNameOption();
|
||||
ProgressLinesOption = new ProgressLinesOption();
|
||||
FilesArgument = new FilesArgument();
|
||||
|
||||
AddOption(HelpOption);
|
||||
AddOption(ShowConfigOption);
|
||||
AddOption(DestinationOption);
|
||||
AddOption(WidthOption);
|
||||
AddOption(HeightOption);
|
||||
AddOption(UnitOption);
|
||||
AddOption(FitOption);
|
||||
AddOption(SizeOption);
|
||||
AddOption(ShrinkOnlyOption);
|
||||
AddOption(ReplaceOption);
|
||||
AddOption(IgnoreOrientationOption);
|
||||
AddOption(RemoveMetadataOption);
|
||||
AddOption(QualityOption);
|
||||
AddOption(KeepDateModifiedOption);
|
||||
AddOption(FileNameOption);
|
||||
AddOption(ProgressLinesOption);
|
||||
AddArgument(FilesArgument);
|
||||
}
|
||||
|
||||
public HelpOption HelpOption { get; }
|
||||
|
||||
public ShowConfigOption ShowConfigOption { get; }
|
||||
|
||||
public DestinationOption DestinationOption { get; }
|
||||
|
||||
public WidthOption WidthOption { get; }
|
||||
|
||||
public HeightOption HeightOption { get; }
|
||||
|
||||
public UnitOption UnitOption { get; }
|
||||
|
||||
public FitOption FitOption { get; }
|
||||
|
||||
public SizeOption SizeOption { get; }
|
||||
|
||||
public ShrinkOnlyOption ShrinkOnlyOption { get; }
|
||||
|
||||
public ReplaceOption ReplaceOption { get; }
|
||||
|
||||
public IgnoreOrientationOption IgnoreOrientationOption { get; }
|
||||
|
||||
public RemoveMetadataOption RemoveMetadataOption { get; }
|
||||
|
||||
public QualityOption QualityOption { get; }
|
||||
|
||||
public KeepDateModifiedOption KeepDateModifiedOption { get; }
|
||||
|
||||
public FileNameOption FileNameOption { get; }
|
||||
|
||||
public ProgressLinesOption ProgressLinesOption { get; }
|
||||
|
||||
public FilesArgument FilesArgument { get; }
|
||||
}
|
||||
}
|
||||
124
src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs
Normal file
124
src/modules/imageresizer/ui/Cli/ImageResizerCliExecutor.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
using ImageResizer.Models;
|
||||
using ImageResizer.Properties;
|
||||
|
||||
namespace ImageResizer.Cli
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes Image Resizer CLI operations.
|
||||
/// Instance-based design for better testability and Single Responsibility Principle.
|
||||
/// </summary>
|
||||
public class ImageResizerCliExecutor
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the CLI executor with the provided command-line arguments.
|
||||
/// </summary>
|
||||
/// <param name="args">Command-line arguments.</param>
|
||||
/// <returns>Exit code.</returns>
|
||||
public int Run(string[] args)
|
||||
{
|
||||
var cliOptions = CliOptions.Parse(args);
|
||||
|
||||
if (cliOptions.ParseErrors.Count > 0)
|
||||
{
|
||||
foreach (var error in cliOptions.ParseErrors)
|
||||
{
|
||||
Console.Error.WriteLine(error);
|
||||
CliLogger.Error($"Parse error: {error}");
|
||||
}
|
||||
|
||||
CliOptions.PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (cliOptions.ShowHelp)
|
||||
{
|
||||
CliOptions.PrintUsage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cliOptions.ShowConfig)
|
||||
{
|
||||
CliOptions.PrintConfig(Settings.Default);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (cliOptions.Files.Count == 0 && string.IsNullOrEmpty(cliOptions.PipeName))
|
||||
{
|
||||
Console.WriteLine(Resources.CLI_NoInputFiles);
|
||||
CliOptions.PrintUsage();
|
||||
return 1;
|
||||
}
|
||||
|
||||
return RunSilentMode(cliOptions);
|
||||
}
|
||||
|
||||
private int RunSilentMode(CliOptions cliOptions)
|
||||
{
|
||||
var batch = ResizeBatch.FromCliOptions(Console.In, cliOptions);
|
||||
var settings = Settings.Default;
|
||||
CliSettingsApplier.Apply(cliOptions, settings);
|
||||
|
||||
CliLogger.Info($"CLI mode: processing {batch.Files.Count} files");
|
||||
|
||||
// Use accessible line-based progress if requested or detected
|
||||
bool useLineBasedProgress = cliOptions.ProgressLines ?? false;
|
||||
int lastReportedMilestone = -1;
|
||||
|
||||
var errors = batch.Process(
|
||||
(completed, total) =>
|
||||
{
|
||||
var progress = (int)((completed / total) * 100);
|
||||
|
||||
if (useLineBasedProgress)
|
||||
{
|
||||
// Milestone-based progress (0%, 25%, 50%, 75%, 100%)
|
||||
int milestone = (progress / 25) * 25;
|
||||
if (milestone > lastReportedMilestone || completed == (int)total)
|
||||
{
|
||||
lastReportedMilestone = milestone;
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Resources.CLI_ProgressFormat, progress, completed, (int)total));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Traditional carriage return mode
|
||||
Console.Write(string.Format(CultureInfo.InvariantCulture, "\r{0}", string.Format(CultureInfo.InvariantCulture, Resources.CLI_ProgressFormat, progress, completed, (int)total)));
|
||||
}
|
||||
},
|
||||
settings,
|
||||
CancellationToken.None);
|
||||
|
||||
if (!useLineBasedProgress)
|
||||
{
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
var errorList = errors.ToList();
|
||||
if (errorList.Count > 0)
|
||||
{
|
||||
Console.Error.WriteLine(string.Format(CultureInfo.InvariantCulture, Resources.CLI_CompletedWithErrors, errorList.Count));
|
||||
CliLogger.Error($"Processing completed with {errorList.Count} error(s)");
|
||||
foreach (var error in errorList)
|
||||
{
|
||||
Console.Error.WriteLine(string.Format(CultureInfo.InvariantCulture, " {0}: {1}", error.File, error.Error));
|
||||
CliLogger.Error($" {error.File}: {error.Error}");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
CliLogger.Info("CLI batch completed successfully");
|
||||
Console.WriteLine(Resources.CLI_AllFilesProcessed);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/DestinationOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/DestinationOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class DestinationOption : Option<string>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--destination", "-d", "/d"];
|
||||
|
||||
public DestinationOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Destination)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/FileNameOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/FileNameOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class FileNameOption : Option<string>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--filename", "-n"];
|
||||
|
||||
public FileNameOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_FileName)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/modules/imageresizer/ui/Cli/Options/FilesArgument.cs
Normal file
17
src/modules/imageresizer/ui/Cli/Options/FilesArgument.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class FilesArgument : Argument<string[]>
|
||||
{
|
||||
public FilesArgument()
|
||||
: base("files", Properties.Resources.CLI_Option_Files)
|
||||
{
|
||||
Arity = ArgumentArity.ZeroOrMore;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/FitOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/FitOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class FitOption : Option<ImageResizer.Models.ResizeFit?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--fit", "-f"];
|
||||
|
||||
public FitOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Fit)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/HeightOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/HeightOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class HeightOption : Option<double?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--height", "-h"];
|
||||
|
||||
public HeightOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Height)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/HelpOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/HelpOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class HelpOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--help", "-?", "/?"];
|
||||
|
||||
public HelpOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Help)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class IgnoreOrientationOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--ignore-orientation"];
|
||||
|
||||
public IgnoreOrientationOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_IgnoreOrientation)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class KeepDateModifiedOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--keep-date-modified"];
|
||||
|
||||
public KeepDateModifiedOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_KeepDateModified)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class ProgressLinesOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--progress-lines", "--accessible"];
|
||||
|
||||
public ProgressLinesOption()
|
||||
: base(_aliases, "Use line-based progress output for screen reader accessibility (milestones: 0%, 25%, 50%, 75%, 100%)")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/modules/imageresizer/ui/Cli/Options/QualityOption.cs
Normal file
26
src/modules/imageresizer/ui/Cli/Options/QualityOption.cs
Normal 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class QualityOption : Option<int?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--quality", "-q"];
|
||||
|
||||
public QualityOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Quality)
|
||||
{
|
||||
AddValidator(result =>
|
||||
{
|
||||
var value = result.GetValueOrDefault<int?>();
|
||||
if (value.HasValue && (value.Value < 1 || value.Value > 100))
|
||||
{
|
||||
result.ErrorMessage = "JPEG quality must be between 1 and 100.";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class RemoveMetadataOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--remove-metadata"];
|
||||
|
||||
public RemoveMetadataOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_RemoveMetadata)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/ReplaceOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/ReplaceOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class ReplaceOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--replace", "-r"];
|
||||
|
||||
public ReplaceOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Replace)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/ShowConfigOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/ShowConfigOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class ShowConfigOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--show-config", "--config"];
|
||||
|
||||
public ShowConfigOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_ShowConfig)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/ShrinkOnlyOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/ShrinkOnlyOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class ShrinkOnlyOption : Option<bool>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--shrink-only"];
|
||||
|
||||
public ShrinkOnlyOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_ShrinkOnly)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/modules/imageresizer/ui/Cli/Options/SizeOption.cs
Normal file
26
src/modules/imageresizer/ui/Cli/Options/SizeOption.cs
Normal 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class SizeOption : Option<int?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--size"];
|
||||
|
||||
public SizeOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Size)
|
||||
{
|
||||
AddValidator(result =>
|
||||
{
|
||||
var value = result.GetValueOrDefault<int?>();
|
||||
if (value.HasValue && value.Value < 0)
|
||||
{
|
||||
result.ErrorMessage = "Size index must be a non-negative integer.";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/UnitOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/UnitOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class UnitOption : Option<ImageResizer.Models.ResizeUnit?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--unit", "-u"];
|
||||
|
||||
public UnitOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Unit)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
18
src/modules/imageresizer/ui/Cli/Options/WidthOption.cs
Normal file
18
src/modules/imageresizer/ui/Cli/Options/WidthOption.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
// 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.
|
||||
|
||||
using System.CommandLine;
|
||||
|
||||
namespace ImageResizer.Cli.Options
|
||||
{
|
||||
public sealed class WidthOption : Option<double?>
|
||||
{
|
||||
private static readonly string[] _aliases = ["--width", "-w"];
|
||||
|
||||
public WidthOption()
|
||||
: base(_aliases, Properties.Resources.CLI_Option_Width)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
<AssemblyName>PowerToys.ImageResizer</AssemblyName>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<NoWarn>CA1863</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -51,6 +52,7 @@
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK.AI" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" />
|
||||
<PackageReference Include="System.CommandLine" />
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="WPF-UI" />
|
||||
</ItemGroup>
|
||||
|
||||
261
src/modules/imageresizer/ui/Models/CliOptions.cs
Normal file
261
src/modules/imageresizer/ui/Models/CliOptions.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.CommandLine.Parsing;
|
||||
using System.Globalization;
|
||||
using ImageResizer.Cli.Commands;
|
||||
|
||||
#pragma warning disable SA1649 // File name should match first type name
|
||||
#pragma warning disable SA1402 // File may only contain a single type
|
||||
|
||||
namespace ImageResizer.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the command-line options for ImageResizer CLI mode.
|
||||
/// </summary>
|
||||
public class CliOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show help information.
|
||||
/// </summary>
|
||||
public bool ShowHelp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show current configuration.
|
||||
/// </summary>
|
||||
public bool ShowConfig { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the destination directory for resized images.
|
||||
/// </summary>
|
||||
public string DestinationDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the width of the resized image.
|
||||
/// </summary>
|
||||
public double? Width { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height of the resized image.
|
||||
/// </summary>
|
||||
public double? Height { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the resize unit (Pixel, Percent, Inch, Centimeter).
|
||||
/// </summary>
|
||||
public ResizeUnit? Unit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the resize fit mode (Fill, Fit, Stretch).
|
||||
/// </summary>
|
||||
public ResizeFit? Fit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the index of the preset size to use.
|
||||
/// </summary>
|
||||
public int? SizeIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to only shrink images (not enlarge).
|
||||
/// </summary>
|
||||
public bool? ShrinkOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to replace the original file.
|
||||
/// </summary>
|
||||
public bool? Replace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to ignore orientation when resizing.
|
||||
/// </summary>
|
||||
public bool? IgnoreOrientation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to remove metadata from the resized image.
|
||||
/// </summary>
|
||||
public bool? RemoveMetadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the JPEG quality level (1-100).
|
||||
/// </summary>
|
||||
public int? JpegQualityLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to keep the date modified.
|
||||
/// </summary>
|
||||
public bool? KeepDateModified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the output filename format.
|
||||
/// </summary>
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to use line-based progress output for screen reader accessibility.
|
||||
/// </summary>
|
||||
public bool? ProgressLines { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of files to process.
|
||||
/// </summary>
|
||||
public ICollection<string> Files { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the pipe name for receiving file list.
|
||||
/// </summary>
|
||||
public string PipeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets parse/validation errors produced by System.CommandLine.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> ParseErrors { get; private set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Converts a boolean value to nullable bool (true -> true, false -> null).
|
||||
/// </summary>
|
||||
private static bool? ToBoolOrNull(bool value) => value ? true : null;
|
||||
|
||||
/// <summary>
|
||||
/// Parses command-line arguments into CliOptions using System.CommandLine.
|
||||
/// </summary>
|
||||
/// <param name="args">The command-line arguments.</param>
|
||||
/// <returns>A CliOptions instance with parsed values.</returns>
|
||||
public static CliOptions Parse(string[] args)
|
||||
{
|
||||
var options = new CliOptions();
|
||||
var cmd = new ImageResizerRootCommand();
|
||||
|
||||
// Parse using System.CommandLine
|
||||
var parseResult = new Parser(cmd).Parse(args);
|
||||
|
||||
if (parseResult.Errors.Count > 0)
|
||||
{
|
||||
var errors = new List<string>(parseResult.Errors.Count);
|
||||
foreach (var error in parseResult.Errors)
|
||||
{
|
||||
errors.Add(error.Message);
|
||||
}
|
||||
|
||||
options.ParseErrors = new ReadOnlyCollection<string>(errors);
|
||||
}
|
||||
|
||||
// Extract values from parse result using strongly typed options
|
||||
options.ShowHelp = parseResult.GetValueForOption(cmd.HelpOption);
|
||||
options.ShowConfig = parseResult.GetValueForOption(cmd.ShowConfigOption);
|
||||
options.DestinationDirectory = parseResult.GetValueForOption(cmd.DestinationOption);
|
||||
options.Width = parseResult.GetValueForOption(cmd.WidthOption);
|
||||
options.Height = parseResult.GetValueForOption(cmd.HeightOption);
|
||||
options.Unit = parseResult.GetValueForOption(cmd.UnitOption);
|
||||
options.Fit = parseResult.GetValueForOption(cmd.FitOption);
|
||||
options.SizeIndex = parseResult.GetValueForOption(cmd.SizeOption);
|
||||
|
||||
// Convert bool to nullable bool (true -> true, false -> null)
|
||||
options.ShrinkOnly = ToBoolOrNull(parseResult.GetValueForOption(cmd.ShrinkOnlyOption));
|
||||
options.Replace = ToBoolOrNull(parseResult.GetValueForOption(cmd.ReplaceOption));
|
||||
options.IgnoreOrientation = ToBoolOrNull(parseResult.GetValueForOption(cmd.IgnoreOrientationOption));
|
||||
options.RemoveMetadata = ToBoolOrNull(parseResult.GetValueForOption(cmd.RemoveMetadataOption));
|
||||
options.KeepDateModified = ToBoolOrNull(parseResult.GetValueForOption(cmd.KeepDateModifiedOption));
|
||||
options.ProgressLines = ToBoolOrNull(parseResult.GetValueForOption(cmd.ProgressLinesOption));
|
||||
|
||||
options.JpegQualityLevel = parseResult.GetValueForOption(cmd.QualityOption);
|
||||
|
||||
options.FileName = parseResult.GetValueForOption(cmd.FileNameOption);
|
||||
|
||||
// Get files from arguments
|
||||
var files = parseResult.GetValueForArgument(cmd.FilesArgument);
|
||||
if (files != null)
|
||||
{
|
||||
const string pipeNamePrefix = "\\\\.\\pipe\\";
|
||||
foreach (var file in files)
|
||||
{
|
||||
// Check for pipe name (must be at the start of the path)
|
||||
if (file.StartsWith(pipeNamePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
options.PipeName = file.Substring(pipeNamePrefix.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
options.Files.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints current configuration to the console.
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to display.</param>
|
||||
public static void PrintConfig(ImageResizer.Properties.Settings settings)
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Console.WriteLine(Properties.Resources.CLI_ConfigTitle);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.CLI_ConfigGeneralSettings);
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigShrinkOnly, settings.ShrinkOnly));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigReplaceOriginal, settings.Replace));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigIgnoreOrientation, settings.IgnoreOrientation));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigRemoveMetadata, settings.RemoveMetadata));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigKeepDateModified, settings.KeepDateModified));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigJpegQuality, settings.JpegQualityLevel));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigPngInterlace, settings.PngInterlaceOption));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigTiffCompress, settings.TiffCompressOption));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigFilenameFormat, settings.FileName));
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.CLI_ConfigCustomSize);
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigWidth, settings.CustomSize.Width, settings.CustomSize.Unit));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigHeight, settings.CustomSize.Height, settings.CustomSize.Unit));
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigFitMode, settings.CustomSize.Fit));
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.CLI_ConfigPresetSizes);
|
||||
for (int i = 0; i < settings.Sizes.Count; i++)
|
||||
{
|
||||
var size = settings.Sizes[i];
|
||||
var selected = i == settings.SelectedSizeIndex ? "*" : " ";
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigPresetSizeFormat, i, selected, size.Name, size.Width, size.Height, size.Unit, size.Fit));
|
||||
}
|
||||
|
||||
if (settings.SelectedSizeIndex >= settings.Sizes.Count)
|
||||
{
|
||||
Console.WriteLine(string.Format(CultureInfo.InvariantCulture, Properties.Resources.CLI_ConfigCustomSelected, settings.CustomSize.Width, settings.CustomSize.Height, settings.CustomSize.Unit, settings.CustomSize.Fit));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints usage information to the console.
|
||||
/// </summary>
|
||||
public static void PrintUsage()
|
||||
{
|
||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||
Console.WriteLine(Properties.Resources.CLI_UsageTitle);
|
||||
Console.WriteLine();
|
||||
|
||||
var cmd = new ImageResizerRootCommand();
|
||||
|
||||
// Print usage line
|
||||
Console.WriteLine(Properties.Resources.CLI_UsageLine);
|
||||
Console.WriteLine();
|
||||
|
||||
// Print options from the command definition
|
||||
Console.WriteLine(Properties.Resources.CLI_UsageOptions);
|
||||
foreach (var option in cmd.Options)
|
||||
{
|
||||
var aliases = string.Join(", ", option.Aliases);
|
||||
var description = option.Description ?? string.Empty;
|
||||
Console.WriteLine($" {aliases,-30} {description}");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(Properties.Resources.CLI_UsageExamples);
|
||||
Console.WriteLine(Properties.Resources.CLI_UsageExampleHelp);
|
||||
Console.WriteLine(Properties.Resources.CLI_UsageExampleDimensions);
|
||||
Console.WriteLine(Properties.Resources.CLI_UsageExamplePercent);
|
||||
Console.WriteLine(Properties.Resources.CLI_UsageExamplePreset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -39,44 +40,78 @@ namespace ImageResizer.Models
|
||||
_aiSuperResolutionService = null;
|
||||
}
|
||||
|
||||
public static ResizeBatch FromCommandLine(TextReader standardInput, string[] args)
|
||||
/// <summary>
|
||||
/// Validates if a file path is a supported image format.
|
||||
/// </summary>
|
||||
/// <param name="path">The file path to validate.</param>
|
||||
/// <returns>True if the path is valid and points to a supported image file.</returns>
|
||||
private static bool IsValidImagePath(string path)
|
||||
{
|
||||
var batch = new ResizeBatch();
|
||||
const string pipeNamePrefix = "\\\\.\\pipe\\";
|
||||
string pipeName = null;
|
||||
|
||||
for (var i = 0; i < args?.Length; i++)
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
if (args[i] == "/d")
|
||||
{
|
||||
batch.DestinationDirectory = args[++i];
|
||||
continue;
|
||||
}
|
||||
else if (args[i].Contains(pipeNamePrefix))
|
||||
{
|
||||
pipeName = args[i].Substring(pipeNamePrefix.Length);
|
||||
continue;
|
||||
}
|
||||
|
||||
batch.Files.Add(args[i]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(pipeName))
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
var validExtensions = new[]
|
||||
{
|
||||
".bmp", ".dib", ".gif", ".jfif", ".jpe", ".jpeg", ".jpg",
|
||||
".jxr", ".png", ".rle", ".tif", ".tiff", ".wdp",
|
||||
};
|
||||
|
||||
return validExtensions.Contains(ext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a ResizeBatch from CliOptions.
|
||||
/// </summary>
|
||||
/// <param name="standardInput">Standard input stream for reading additional file paths.</param>
|
||||
/// <param name="options">The parsed CLI options.</param>
|
||||
/// <returns>A ResizeBatch instance.</returns>
|
||||
public static ResizeBatch FromCliOptions(TextReader standardInput, CliOptions options)
|
||||
{
|
||||
var batch = new ResizeBatch
|
||||
{
|
||||
DestinationDirectory = options.DestinationDirectory,
|
||||
};
|
||||
|
||||
foreach (var file in options.Files)
|
||||
{
|
||||
// Convert relative paths to absolute paths
|
||||
var absolutePath = Path.IsPathRooted(file) ? file : Path.GetFullPath(file);
|
||||
if (IsValidImagePath(absolutePath))
|
||||
{
|
||||
batch.Files.Add(absolutePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(options.PipeName))
|
||||
{
|
||||
// NB: We read these from stdin since there are limits on the number of args you can have
|
||||
// Only read from stdin if it's redirected (piped input), not from interactive terminal
|
||||
string file;
|
||||
if (standardInput != null)
|
||||
if (standardInput != null && (Console.IsInputRedirected || !ReferenceEquals(standardInput, Console.In)))
|
||||
{
|
||||
while ((file = standardInput.ReadLine()) != null)
|
||||
{
|
||||
batch.Files.Add(file);
|
||||
// Convert relative paths to absolute paths
|
||||
var absolutePath = Path.IsPathRooted(file) ? file : Path.GetFullPath(file);
|
||||
if (IsValidImagePath(absolutePath))
|
||||
{
|
||||
batch.Files.Add(absolutePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (NamedPipeClientStream pipeClient =
|
||||
new NamedPipeClientStream(".", pipeName, PipeDirection.In))
|
||||
new NamedPipeClientStream(".", options.PipeName, PipeDirection.In))
|
||||
{
|
||||
// Connect to the pipe or wait until the pipe is available.
|
||||
pipeClient.Connect();
|
||||
@@ -88,7 +123,10 @@ namespace ImageResizer.Models
|
||||
// Display the read text to the console
|
||||
while ((file = sr.ReadLine()) != null)
|
||||
{
|
||||
batch.Files.Add(file);
|
||||
if (IsValidImagePath(file))
|
||||
{
|
||||
batch.Files.Add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,17 +135,26 @@ namespace ImageResizer.Models
|
||||
return batch;
|
||||
}
|
||||
|
||||
public static ResizeBatch FromCommandLine(TextReader standardInput, string[] args)
|
||||
{
|
||||
var options = CliOptions.Parse(args);
|
||||
return FromCliOptions(standardInput, options);
|
||||
}
|
||||
|
||||
public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, CancellationToken cancellationToken)
|
||||
{
|
||||
// NOTE: Settings.Default is captured once before parallel processing.
|
||||
// Any changes to settings on disk during this batch will NOT be reflected until the next batch.
|
||||
// This improves performance and predictability by avoiding repeated mutex acquisition and behaviour change results in a batch.
|
||||
return Process(reportProgress, Settings.Default, cancellationToken);
|
||||
}
|
||||
|
||||
public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, Settings settings, CancellationToken cancellationToken)
|
||||
{
|
||||
double total = Files.Count;
|
||||
int completed = 0;
|
||||
var errors = new ConcurrentBag<ResizeError>();
|
||||
|
||||
// NOTE: Settings.Default is captured once before parallel processing.
|
||||
// Any changes to settings on disk during this batch will NOT be reflected until the next batch.
|
||||
// This improves performance and predictability by avoiding repeated mutex acquisition and behaviour change results in a batch.
|
||||
var settings = Settings.Default;
|
||||
|
||||
// TODO: If we ever switch to Windows.Graphics.Imaging, we can get a lot more throughput by using the async
|
||||
// APIs and a custom SynchronizationContext
|
||||
Parallel.ForEach(
|
||||
|
||||
@@ -716,5 +716,437 @@ namespace ImageResizer.Properties {
|
||||
return ResourceManager.GetString("Width", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Processing {0} files....
|
||||
/// </summary>
|
||||
public static string CLI_ProcessingFiles {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ProcessingFiles", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to [{0}%] {1}/{2} completed.
|
||||
/// </summary>
|
||||
public static string CLI_ProgressFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ProgressFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Completed with {0} error(s)..
|
||||
/// </summary>
|
||||
public static string CLI_CompletedWithErrors {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_CompletedWithErrors", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to All files processed successfully!.
|
||||
/// </summary>
|
||||
public static string CLI_AllFilesProcessed {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_AllFilesProcessed", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No input files or pipe specified. Showing usage..
|
||||
/// </summary>
|
||||
public static string CLI_NoInputFiles {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_NoInputFiles", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Warning: Size index {0} is invalid. Using custom size..
|
||||
/// </summary>
|
||||
public static string CLI_WarningInvalidSizeIndex {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_WarningInvalidSizeIndex", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Current Configuration:.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to General Settings:.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigGeneralSettings {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigGeneralSettings", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Shrink only: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigShrinkOnly {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigShrinkOnly", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Replace original: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigReplaceOriginal {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigReplaceOriginal", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Ignore orientation: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigIgnoreOrientation {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigIgnoreOrientation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove metadata: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigRemoveMetadata {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigRemoveMetadata", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Keep date modified: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigKeepDateModified {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigKeepDateModified", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to JPEG quality: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigJpegQuality {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigJpegQuality", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to PNG interlace: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigPngInterlace {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigPngInterlace", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to TIFF compress: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigTiffCompress {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigTiffCompress", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Filename format: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigFilenameFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigFilenameFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Custom Size:.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigCustomSize {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigCustomSize", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Width: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigWidth {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigWidth", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Height: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigHeight {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigHeight", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Fit mode: {0}.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigFitMode {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigFitMode", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Preset Sizes: (* = currently selected).
|
||||
/// </summary>
|
||||
public static string CLI_ConfigPresetSizes {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigPresetSizes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0}: {1} x {2} ({3}).
|
||||
/// </summary>
|
||||
public static string CLI_ConfigPresetSizeFormat {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigPresetSizeFormat", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to → Custom size selected.
|
||||
/// </summary>
|
||||
public static string CLI_ConfigCustomSelected {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_ConfigCustomSelected", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Image Resizer CLI.
|
||||
/// </summary>
|
||||
public static string CLI_UsageTitle {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_UsageTitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Usage: PowerToys.ImageResizer.exe [options] <files>.
|
||||
/// </summary>
|
||||
public static string CLI_UsageLine {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_UsageLine", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Options:.
|
||||
/// </summary>
|
||||
public static string CLI_UsageOptions {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_UsageOptions", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Examples:.
|
||||
/// </summary>
|
||||
public static string CLI_UsageExamples {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_UsageExamples", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to PowerToys.ImageResizer.exe --help.
|
||||
/// </summary>
|
||||
public static string CLI_UsageExampleHelp {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_UsageExampleHelp", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to PowerToys.ImageResizer.exe --width 800 --height 600 image.jpg.
|
||||
/// </summary>
|
||||
public static string CLI_UsageExampleDimensions {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_UsageExampleDimensions", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to PowerToys.ImageResizer.exe --size 50 --unit percent *.jpg.
|
||||
/// </summary>
|
||||
public static string CLI_UsageExamplePercent {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_UsageExamplePercent", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to PowerToys.ImageResizer.exe --size 2 image1.png image2.png.
|
||||
/// </summary>
|
||||
public static string CLI_UsageExamplePreset {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_UsageExamplePreset", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Destination directory for resized images.
|
||||
/// </summary>
|
||||
public static string CLI_Option_Destination {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Destination", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Output filename format (e.g., %1 (%2)).
|
||||
/// </summary>
|
||||
public static string CLI_Option_FileName {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_FileName", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Image files to resize.
|
||||
/// </summary>
|
||||
public static string CLI_Option_Files {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Files", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to How to fit image: fill, fit, stretch.
|
||||
/// </summary>
|
||||
public static string CLI_Option_Fit {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Fit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Height of the resized image in pixels.
|
||||
/// </summary>
|
||||
public static string CLI_Option_Height {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Height", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Display this help message.
|
||||
/// </summary>
|
||||
public static string CLI_Option_Help {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Help", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Ignore image orientation metadata.
|
||||
/// </summary>
|
||||
public static string CLI_Option_IgnoreOrientation {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_IgnoreOrientation", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Preserve the original file modification date.
|
||||
/// </summary>
|
||||
public static string CLI_Option_KeepDateModified {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_KeepDateModified", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Set JPEG quality level (1-100).
|
||||
/// </summary>
|
||||
public static string CLI_Option_Quality {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Quality", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remove image metadata during resizing.
|
||||
/// </summary>
|
||||
public static string CLI_Option_RemoveMetadata {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_RemoveMetadata", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Replace the original image file.
|
||||
/// </summary>
|
||||
public static string CLI_Option_Replace {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Replace", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Display current configuration.
|
||||
/// </summary>
|
||||
public static string CLI_Option_ShowConfig {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_ShowConfig", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Only shrink images, do not enlarge.
|
||||
/// </summary>
|
||||
public static string CLI_Option_ShrinkOnly {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_ShrinkOnly", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use preset size by index (0-based).
|
||||
/// </summary>
|
||||
public static string CLI_Option_Size {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Size", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unit of measurement: pixel, percent, cm, inch.
|
||||
/// </summary>
|
||||
public static string CLI_Option_Unit {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Unit", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Width of the resized image in pixels.
|
||||
/// </summary>
|
||||
public static string CLI_Option_Width {
|
||||
get {
|
||||
return ResourceManager.GetString("CLI_Option_Width", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,4 +347,156 @@
|
||||
<data name="Input_AiSuperResolutionDescription" xml:space="preserve">
|
||||
<value>Upscale images using on-device AI</value>
|
||||
</data>
|
||||
|
||||
<!-- CLI Processing messages -->
|
||||
<data name="CLI_ProcessingFiles" xml:space="preserve">
|
||||
<value>Processing {0} file(s)...</value>
|
||||
</data>
|
||||
<data name="CLI_ProgressFormat" xml:space="preserve">
|
||||
<value>Progress: {0}% ({1}/{2})</value>
|
||||
</data>
|
||||
<data name="CLI_CompletedWithErrors" xml:space="preserve">
|
||||
<value>Completed with {0} error(s):</value>
|
||||
</data>
|
||||
<data name="CLI_AllFilesProcessed" xml:space="preserve">
|
||||
<value>All files processed successfully.</value>
|
||||
</data>
|
||||
<data name="CLI_WarningInvalidSizeIndex" xml:space="preserve">
|
||||
<value>Warning: Invalid size index {0}. Using default.</value>
|
||||
</data>
|
||||
<data name="CLI_NoInputFiles" xml:space="preserve">
|
||||
<value>No input files or pipe specified. Showing usage.</value>
|
||||
</data>
|
||||
|
||||
<!-- CLI Config display -->
|
||||
<data name="CLI_ConfigTitle" xml:space="preserve">
|
||||
<value>ImageResizer - Current Configuration</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigGeneralSettings" xml:space="preserve">
|
||||
<value>General Settings:</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigShrinkOnly" xml:space="preserve">
|
||||
<value> Shrink Only: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigReplaceOriginal" xml:space="preserve">
|
||||
<value> Replace Original: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigIgnoreOrientation" xml:space="preserve">
|
||||
<value> Ignore Orientation: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigRemoveMetadata" xml:space="preserve">
|
||||
<value> Remove Metadata: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigKeepDateModified" xml:space="preserve">
|
||||
<value> Keep Date Modified: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigJpegQuality" xml:space="preserve">
|
||||
<value> JPEG Quality: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigPngInterlace" xml:space="preserve">
|
||||
<value> PNG Interlace: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigTiffCompress" xml:space="preserve">
|
||||
<value> TIFF Compress: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigFilenameFormat" xml:space="preserve">
|
||||
<value> Filename Format: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigCustomSize" xml:space="preserve">
|
||||
<value>Custom Size:</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigWidth" xml:space="preserve">
|
||||
<value> Width: {0} {1}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigHeight" xml:space="preserve">
|
||||
<value> Height: {0} {1}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigFitMode" xml:space="preserve">
|
||||
<value> Fit Mode: {0}</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigPresetSizes" xml:space="preserve">
|
||||
<value>Preset Sizes: (* = currently selected)</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigPresetSizeFormat" xml:space="preserve">
|
||||
<value> [{0}]{1} {2}: {3}x{4} {5} ({6})</value>
|
||||
</data>
|
||||
<data name="CLI_ConfigCustomSelected" xml:space="preserve">
|
||||
<value> [Custom]* {0}x{1} {2} ({3})</value>
|
||||
</data>
|
||||
|
||||
<!-- CLI Usage help -->
|
||||
<data name="CLI_UsageTitle" xml:space="preserve">
|
||||
<value>ImageResizer - PowerToys Image Resizer CLI</value>
|
||||
</data>
|
||||
<data name="CLI_UsageLine" xml:space="preserve">
|
||||
<value>Usage: PowerToys.ImageResizerCLI.exe [options] [files...]</value>
|
||||
</data>
|
||||
<data name="CLI_UsageOptions" xml:space="preserve">
|
||||
<value>Options:</value>
|
||||
</data>
|
||||
<data name="CLI_UsageExamples" xml:space="preserve">
|
||||
<value>Examples:</value>
|
||||
</data>
|
||||
<data name="CLI_UsageExampleHelp" xml:space="preserve">
|
||||
<value> PowerToys.ImageResizerCLI.exe --help</value>
|
||||
</data>
|
||||
<data name="CLI_UsageExampleDimensions" xml:space="preserve">
|
||||
<value> PowerToys.ImageResizerCLI.exe --width 800 --height 600 image.jpg</value>
|
||||
</data>
|
||||
<data name="CLI_UsageExamplePercent" xml:space="preserve">
|
||||
<value> PowerToys.ImageResizerCLI.exe -w 50 -h 50 -u Percent *.jpg</value>
|
||||
</data>
|
||||
<data name="CLI_UsageExamplePreset" xml:space="preserve">
|
||||
<value> PowerToys.ImageResizerCLI.exe --size 0 -d "C:\Output" photo.png</value>
|
||||
</data>
|
||||
|
||||
<!-- CLI Option Descriptions -->
|
||||
<data name="CLI_Option_Destination" xml:space="preserve">
|
||||
<value>Set destination directory</value>
|
||||
</data>
|
||||
<data name="CLI_Option_FileName" xml:space="preserve">
|
||||
<value>Set output filename format (%1=original name, %2=size name)</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Files" xml:space="preserve">
|
||||
<value>Image files to resize</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Fit" xml:space="preserve">
|
||||
<value>Set fit mode (Fill, Fit, Stretch)</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Height" xml:space="preserve">
|
||||
<value>Set height</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Help" xml:space="preserve">
|
||||
<value>Show help information</value>
|
||||
</data>
|
||||
<data name="CLI_Option_IgnoreOrientation" xml:space="preserve">
|
||||
<value>Ignore image orientation</value>
|
||||
</data>
|
||||
<data name="CLI_Option_KeepDateModified" xml:space="preserve">
|
||||
<value>Keep original date modified</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Quality" xml:space="preserve">
|
||||
<value>Set JPEG quality level (1-100)</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Replace" xml:space="preserve">
|
||||
<value>Replace original files</value>
|
||||
</data>
|
||||
<data name="CLI_Option_ShowConfig" xml:space="preserve">
|
||||
<value>Show current configuration</value>
|
||||
</data>
|
||||
<data name="CLI_Option_ShrinkOnly" xml:space="preserve">
|
||||
<value>Only shrink images, don't enlarge</value>
|
||||
</data>
|
||||
<data name="CLI_Option_RemoveMetadata" xml:space="preserve">
|
||||
<value>Remove metadata from resized images</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Size" xml:space="preserve">
|
||||
<value>Use preset size by index (0-based)</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Unit" xml:space="preserve">
|
||||
<value>Set unit (Pixel, Percent, Inch, Centimeter)</value>
|
||||
</data>
|
||||
<data name="CLI_Option_Width" xml:space="preserve">
|
||||
<value>Set width</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -15,6 +15,7 @@ using System.IO.Abstractions;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Threading;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
@@ -42,6 +43,7 @@ namespace ImageResizer.Properties
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||
WriteIndented = true,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
|
||||
};
|
||||
|
||||
private static readonly CompositeFormat ValueMustBeBetween = System.Text.CompositeFormat.Parse(Properties.Resources.ValueMustBeBetween);
|
||||
|
||||
Reference in New Issue
Block a user