Enhance image resizing functionality with batch processing and quality options. Update README with new usage examples and commands for favicon generation. Add indirect dependencies for besticon and x/image.
This commit is contained in:
parent
31a4fc3467
commit
9395e3b1b2
55
README.md
55
README.md
@ -23,12 +23,33 @@ go build -o dttt
|
||||
Resize images to a specified width while maintaining aspect ratio.
|
||||
|
||||
```bash
|
||||
# Single file resize
|
||||
dttt resize [input_file] [output_file] [width]
|
||||
|
||||
# Batch resize all images in a directory
|
||||
dttt resize [input_directory] [output_directory] [width] --batch
|
||||
```
|
||||
|
||||
Example:
|
||||
The command supports multiple image formats (JPG, PNG, WebP) and preserves the original file format.
|
||||
|
||||
Options:
|
||||
- `--batch, -b`: Enable batch resizing mode for directories
|
||||
- `--quality, -q`: Quality for lossy formats (1-100, default: 90)
|
||||
- `--extensions, -e`: Comma-separated list of file extensions to process in batch mode (default: jpg,jpeg,png,webp)
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
dttt resize input.jpg output.jpg 300
|
||||
# Resize a single image
|
||||
dttt resize input.jpg output.jpg 800
|
||||
|
||||
# Resize a single image with custom quality
|
||||
dttt resize input.webp output.webp 1200 --quality 85
|
||||
|
||||
# Batch resize all images in a directory to 1000px width
|
||||
dttt resize ./photos ./resized 1000 --batch
|
||||
|
||||
# Batch resize only JPG and PNG files
|
||||
dttt resize ./originals ./thumbnails 400 --batch --extensions jpg,png
|
||||
```
|
||||
|
||||
### convert
|
||||
@ -69,6 +90,36 @@ dttt convert ./photos ./webp_photos --batch --format webp --extensions jpg,png
|
||||
dttt convert ./input ./output --batch --format jpg --quality 85
|
||||
```
|
||||
|
||||
### favicon
|
||||
|
||||
Generate a complete favicon package from a single image source, similar to favicon.io.
|
||||
|
||||
```bash
|
||||
dttt favicon [input_image] [output_dir] [flags]
|
||||
```
|
||||
|
||||
This creates all common favicon formats and sizes required for modern websites, including:
|
||||
- PNG favicons in various sizes (16x16, 32x32, 48x48 and more)
|
||||
- Apple Touch Icons for iOS devices
|
||||
- Android/PWA icons for Android and Progressive Web Apps
|
||||
- Web app manifest and browserconfig files
|
||||
- HTML snippet for easy inclusion in your website
|
||||
|
||||
Options:
|
||||
- `--name, -n`: Application name for the web manifest (default: "App")
|
||||
- `--short-name, -s`: Short application name for the web manifest (default: "App")
|
||||
|
||||
Examples:
|
||||
```bash
|
||||
# Basic usage
|
||||
dttt favicon logo.png ./favicons
|
||||
|
||||
# With custom application name
|
||||
dttt favicon logo.png ./favicons --name "My Awesome Website" --short-name "MyApp"
|
||||
```
|
||||
|
||||
The command generates all the necessary favicon files in the output directory, ready to be used in your website. Modern browsers support PNG favicons, so an .ico file is not needed anymore.
|
||||
|
||||
### rename
|
||||
|
||||
Rename files in bulk based on specified patterns.
|
||||
|
||||
190
cmd/favicon.go
Normal file
190
cmd/favicon.go
Normal file
@ -0,0 +1,190 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var faviconSizes = []int{16, 32, 48, 64, 72, 96, 128, 144, 152, 192, 196, 512}
|
||||
var appleTouchSizes = []int{57, 60, 72, 76, 114, 120, 144, 152, 180}
|
||||
var androidSizes = []int{36, 48, 72, 96, 144, 192, 196, 512}
|
||||
|
||||
// faviconCmd represents the favicon command
|
||||
var faviconCmd = &cobra.Command{
|
||||
Use: "favicon [input_image] [output_dir]",
|
||||
Short: "Generate a complete favicon package from an image",
|
||||
Long: `Generate a complete favicon package from a single image.
|
||||
This creates all common favicon formats and sizes required for modern websites, including:
|
||||
- PNG favicons in various sizes (16x16, 32x32, 48x48, etc.)
|
||||
- Apple Touch Icons
|
||||
- Android/PWA icons
|
||||
- Web app manifest and browserconfig files
|
||||
|
||||
Example: dttt favicon logo.png ./favicons --name "My Awesome App" --short-name "MyApp"`,
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
inputFile := args[0]
|
||||
outputDir := args[1]
|
||||
|
||||
appName, _ := cmd.Flags().GetString("name")
|
||||
shortName, _ := cmd.Flags().GetString("short-name")
|
||||
|
||||
generateFavicons(inputFile, outputDir, appName, shortName)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(faviconCmd)
|
||||
|
||||
// Add flags for web manifest customization
|
||||
faviconCmd.Flags().StringP("name", "n", "App", "Application name for the web manifest")
|
||||
faviconCmd.Flags().StringP("short-name", "s", "App", "Short application name for the web manifest")
|
||||
}
|
||||
|
||||
func generateFavicons(inputFile, outputDir, appName, shortName string) {
|
||||
// Open and decode the input image
|
||||
input, err := os.Open(inputFile)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening input file:", err)
|
||||
}
|
||||
defer input.Close()
|
||||
|
||||
// Decode the input image
|
||||
srcImg, _, err := image.Decode(input)
|
||||
if err != nil {
|
||||
log.Fatal("Error decoding input image:", err)
|
||||
}
|
||||
|
||||
// Create output directory if it doesn't exist
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
log.Fatal("Error creating output directory:", err)
|
||||
}
|
||||
|
||||
// Generate standard favicons (PNG)
|
||||
fmt.Println("Generating favicons...")
|
||||
for _, size := range faviconSizes {
|
||||
generateResizedPNG(srcImg, outputDir, fmt.Sprintf("favicon-%dx%d.png", size, size), size)
|
||||
}
|
||||
|
||||
// Generate specific named favicons
|
||||
generateResizedPNG(srcImg, outputDir, "favicon-16x16.png", 16)
|
||||
generateResizedPNG(srcImg, outputDir, "favicon-32x32.png", 32)
|
||||
generateResizedPNG(srcImg, outputDir, "favicon-48x48.png", 48)
|
||||
generateResizedPNG(srcImg, outputDir, "favicon.png", 32) // Default favicon
|
||||
|
||||
// Generate Apple Touch Icons
|
||||
fmt.Println("Generating Apple Touch Icons...")
|
||||
for _, size := range appleTouchSizes {
|
||||
generateResizedPNG(srcImg, outputDir, fmt.Sprintf("apple-touch-icon-%dx%d.png", size, size), size)
|
||||
}
|
||||
// Generate default Apple Touch Icon
|
||||
generateResizedPNG(srcImg, outputDir, "apple-touch-icon.png", 180)
|
||||
generateResizedPNG(srcImg, outputDir, "apple-touch-icon-precomposed.png", 180)
|
||||
|
||||
// Generate Android/PWA icons
|
||||
fmt.Println("Generating Android/PWA icons...")
|
||||
for _, size := range androidSizes {
|
||||
generateResizedPNG(srcImg, outputDir, fmt.Sprintf("android-chrome-%dx%d.png", size, size), size)
|
||||
}
|
||||
|
||||
// Generate web app manifest
|
||||
fmt.Println("Generating web app manifest...")
|
||||
generateWebManifest(outputDir, appName, shortName)
|
||||
|
||||
// Generate browserconfig
|
||||
fmt.Println("Generating browserconfig...")
|
||||
generateBrowserconfig(outputDir)
|
||||
|
||||
// Generate HTML include snippet
|
||||
fmt.Println("Generating HTML include snippet...")
|
||||
generateHTMLSnippet(outputDir)
|
||||
|
||||
fmt.Println("Note: No .ico file was generated; modern browsers support PNG favicons.")
|
||||
fmt.Println("Favicon package generated successfully in", outputDir)
|
||||
}
|
||||
|
||||
func generateResizedPNG(srcImg image.Image, outputDir, filename string, size int) {
|
||||
resized := resize.Resize(uint(size), uint(size), srcImg, resize.Lanczos3)
|
||||
|
||||
outPath := filepath.Join(outputDir, filename)
|
||||
outFile, err := os.Create(outPath)
|
||||
if err != nil {
|
||||
log.Printf("Error creating %s: %v", filename, err)
|
||||
return
|
||||
}
|
||||
defer outFile.Close()
|
||||
|
||||
if err := png.Encode(outFile, resized); err != nil {
|
||||
log.Printf("Error encoding %s: %v", filename, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func generateWebManifest(outputDir, appName, shortName string) {
|
||||
manifest := fmt.Sprintf(`{
|
||||
"name": "%s",
|
||||
"short_name": "%s",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}`, appName, shortName)
|
||||
|
||||
err := os.WriteFile(filepath.Join(outputDir, "site.webmanifest"), []byte(manifest), 0644)
|
||||
if err != nil {
|
||||
log.Printf("Error writing site.webmanifest: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateBrowserconfig(outputDir string) {
|
||||
config := `<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="/favicon-72x72.png"/>
|
||||
<square150x150logo src="/favicon-144x144.png"/>
|
||||
<square310x310logo src="/favicon-192x192.png"/>
|
||||
<TileColor>#ffffff</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>`
|
||||
|
||||
err := os.WriteFile(filepath.Join(outputDir, "browserconfig.xml"), []byte(config), 0644)
|
||||
if err != nil {
|
||||
log.Printf("Error writing browserconfig.xml: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateHTMLSnippet(outputDir string) {
|
||||
snippet := `<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||
<meta name="msapplication-TileColor" content="#ffffff">
|
||||
<meta name="theme-color" content="#ffffff">`
|
||||
|
||||
err := os.WriteFile(filepath.Join(outputDir, "favicon-snippet.html"), []byte(snippet), 0644)
|
||||
if err != nil {
|
||||
log.Printf("Error writing favicon-snippet.html: %v", err)
|
||||
}
|
||||
}
|
||||
183
cmd/resize.go
183
cmd/resize.go
@ -4,56 +4,150 @@ import (
|
||||
"fmt"
|
||||
"image"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/chai2010/webp"
|
||||
"github.com/nfnt/resize"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// resizeCmd represents the resize command
|
||||
var resizeCmd = &cobra.Command{
|
||||
Use: "resize [input_file] [output_file] [width]",
|
||||
Short: "Resize an image to the specified width",
|
||||
Long: `Resize an image to the specified width using Lanczos3 interpolation.
|
||||
Example: dttt resize input.jpg output.jpg 300`,
|
||||
Args: cobra.ExactArgs(3),
|
||||
Use: "resize [input] [output] [width]",
|
||||
Short: "Resize images to the specified width",
|
||||
Long: `Resize images to the specified width using Lanczos3 interpolation.
|
||||
Single file mode:
|
||||
dttt resize input.jpg output.jpg 300
|
||||
|
||||
Batch mode (resize all images in a directory):
|
||||
dttt resize ./photos ./resized 800 --batch`,
|
||||
Args: cobra.RangeArgs(2, 3),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.Help()
|
||||
return
|
||||
isBatch, _ := cmd.Flags().GetBool("batch")
|
||||
quality, _ := cmd.Flags().GetInt("quality")
|
||||
extensions, _ := cmd.Flags().GetString("extensions")
|
||||
|
||||
if isBatch {
|
||||
if len(args) != 3 {
|
||||
log.Fatal("Batch mode requires input directory, output directory, and width")
|
||||
}
|
||||
|
||||
inputDir := args[0]
|
||||
outputDir := args[1]
|
||||
widthStr := args[2]
|
||||
|
||||
width, err := strconv.Atoi(widthStr)
|
||||
if err != nil {
|
||||
log.Fatal("Invalid width:", err)
|
||||
}
|
||||
|
||||
batchResize(inputDir, outputDir, width, extensions, quality)
|
||||
} else {
|
||||
if len(args) != 3 {
|
||||
log.Fatal("Single file mode requires input file, output file, and width")
|
||||
}
|
||||
|
||||
inputFile := args[0]
|
||||
outputFile := args[1]
|
||||
widthStr := args[2]
|
||||
|
||||
width, err := strconv.Atoi(widthStr)
|
||||
if err != nil {
|
||||
log.Fatal("Invalid width:", err)
|
||||
}
|
||||
|
||||
resizeImage(inputFile, outputFile, width, quality)
|
||||
}
|
||||
|
||||
inputFile := args[0]
|
||||
outputFile := args[1]
|
||||
widthStr := args[2]
|
||||
|
||||
width, err := strconv.Atoi(widthStr)
|
||||
if err != nil {
|
||||
log.Fatal("Invalid width:", err)
|
||||
}
|
||||
|
||||
resizeImage(inputFile, outputFile, width)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(resizeCmd)
|
||||
|
||||
// Add flags
|
||||
resizeCmd.Flags().BoolP("batch", "b", false, "Enable batch resizing mode for directories")
|
||||
resizeCmd.Flags().IntP("quality", "q", 90, "Quality for lossy formats (1-100)")
|
||||
resizeCmd.Flags().StringP("extensions", "e", "jpg,jpeg,png,webp", "Comma-separated list of file extensions to process in batch mode")
|
||||
}
|
||||
|
||||
func resizeImage(inputFile, outputFile string, width int) {
|
||||
func batchResize(inputDir, outputDir string, width int, extensions string, quality int) {
|
||||
// Ensure output directory exists
|
||||
if err := os.MkdirAll(outputDir, 0755); err != nil {
|
||||
log.Fatal("Failed to create output directory:", err)
|
||||
}
|
||||
|
||||
// Parse extensions
|
||||
validExts := strings.Split(extensions, ",")
|
||||
for i, ext := range validExts {
|
||||
validExts[i] = strings.ToLower(strings.TrimSpace(ext))
|
||||
if !strings.HasPrefix(validExts[i], ".") {
|
||||
validExts[i] = "." + validExts[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Get all files from input directory
|
||||
files, err := os.ReadDir(inputDir)
|
||||
if err != nil {
|
||||
log.Fatal("Error reading input directory:", err)
|
||||
}
|
||||
|
||||
// Track statistics
|
||||
successCount := 0
|
||||
errorCount := 0
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if file has a valid extension
|
||||
fileExt := strings.ToLower(filepath.Ext(file.Name()))
|
||||
isValidExt := false
|
||||
for _, ext := range validExts {
|
||||
if ext == fileExt {
|
||||
isValidExt = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isValidExt {
|
||||
continue
|
||||
}
|
||||
|
||||
// Generate input and output file paths
|
||||
inputPath := filepath.Join(inputDir, file.Name())
|
||||
outputPath := filepath.Join(outputDir, file.Name())
|
||||
|
||||
// Resize the file
|
||||
err := resizeImageWithError(inputPath, outputPath, width, quality)
|
||||
if err != nil {
|
||||
fmt.Printf("Error resizing %s: %v\n", file.Name(), err)
|
||||
errorCount++
|
||||
} else {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Batch resize complete: %d files resized successfully, %d failed\n", successCount, errorCount)
|
||||
}
|
||||
|
||||
func resizeImageWithError(inputFile, outputFile string, width, quality int) error {
|
||||
// Open the input image file
|
||||
input, err := os.Open(inputFile)
|
||||
if err != nil {
|
||||
log.Fatal("Error opening input file:", err)
|
||||
return fmt.Errorf("error opening input file: %w", err)
|
||||
}
|
||||
defer input.Close()
|
||||
|
||||
// Decode the input image file
|
||||
img, _, err := image.Decode(input)
|
||||
img, format, err := image.Decode(input)
|
||||
if err != nil {
|
||||
log.Fatal("Error decoding input image:", err)
|
||||
return fmt.Errorf("error decoding input image: %w", err)
|
||||
}
|
||||
|
||||
// Resize the image to the target width
|
||||
@ -62,15 +156,48 @@ func resizeImage(inputFile, outputFile string, width int) {
|
||||
// Create the output image file
|
||||
output, err := os.Create(outputFile)
|
||||
if err != nil {
|
||||
log.Fatal("Error creating output file:", err)
|
||||
return fmt.Errorf("error creating output file: %w", err)
|
||||
}
|
||||
defer output.Close()
|
||||
|
||||
// Write the resized image to the output file
|
||||
if err := jpeg.Encode(output, resizedImg, nil); err != nil {
|
||||
log.Fatal("Error encoding resized image:", err)
|
||||
// Determine the output format based on the file extension
|
||||
fileExt := strings.ToLower(filepath.Ext(outputFile))
|
||||
|
||||
// Encode based on output format
|
||||
switch fileExt {
|
||||
case ".jpg", ".jpeg":
|
||||
err = jpeg.Encode(output, resizedImg, &jpeg.Options{Quality: quality})
|
||||
case ".png":
|
||||
err = png.Encode(output, resizedImg)
|
||||
case ".webp":
|
||||
err = webp.Encode(output, resizedImg, &webp.Options{Lossless: quality == 100, Quality: float32(quality)})
|
||||
default:
|
||||
// Use input format if output format is not recognized
|
||||
switch format {
|
||||
case "jpeg":
|
||||
err = jpeg.Encode(output, resizedImg, &jpeg.Options{Quality: quality})
|
||||
case "png":
|
||||
err = png.Encode(output, resizedImg)
|
||||
case "webp":
|
||||
err = webp.Encode(output, resizedImg, &webp.Options{Lossless: quality == 100, Quality: float32(quality)})
|
||||
default:
|
||||
// Default to JPEG if format is unknown
|
||||
err = jpeg.Encode(output, resizedImg, &jpeg.Options{Quality: quality})
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Image resized successfully!")
|
||||
if err != nil {
|
||||
return fmt.Errorf("error encoding resized image: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Resized %s from %s to %dpx width\n", filepath.Base(inputFile), format, width)
|
||||
return nil
|
||||
}
|
||||
|
||||
func resizeImage(inputFile, outputFile string, width, quality int) {
|
||||
err := resizeImageWithError(inputFile, outputFile, width, quality)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println("Image resized successfully!")
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@ -12,6 +12,7 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mat/besticon v3.12.0+incompatible // indirect
|
||||
github.com/mattn/go-isatty v0.0.18 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
@ -35,6 +36,7 @@ require (
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.9.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
|
||||
golang.org/x/sync v0.5.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/term v0.6.0 // indirect
|
||||
|
||||
6
go.sum
6
go.sum
@ -19,6 +19,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mat/besticon v3.12.0+incompatible h1:1KTD6wisfjfnX+fk9Kx/6VEZL+MAW1LhCkL9Q47H9Bg=
|
||||
github.com/mat/besticon v3.12.0+incompatible/go.mod h1:mA1auQYHt6CW5e7L9HJLmqVQC8SzNk2gVwouO0AbiEU=
|
||||
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
|
||||
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
@ -76,6 +78,8 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@ -84,8 +88,10 @@ golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user