mirror of
https://github.com/yusing/godoxy.git
synced 2026-03-29 05:11:51 +02:00
This is a large-scale refactoring across the codebase that replaces the custom `gperr.Error` type with Go's standard `error` interface. The changes include: - Replacing `gperr.Error` return types with `error` in function signatures - Using `errors.New()` and `fmt.Errorf()` instead of `gperr.New()` and `gperr.Errorf()` - Using `%w` format verb for error wrapping instead of `.With()` method - Replacing `gperr.Subject()` calls with `gperr.PrependSubject()` - Converting error logging from `gperr.Log*()` functions to zerolog's `.Err().Msg()` pattern - Update NewLogger to handle multiline error message - Updating `goutils` submodule to latest commit This refactoring aligns with Go idioms and removes the dependency on custom error handling abstractions in favor of standard library patterns.
217 lines
5.1 KiB
Go
217 lines
5.1 KiB
Go
package icons
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
gperr "github.com/yusing/goutils/errs"
|
|
)
|
|
|
|
type (
|
|
URL struct {
|
|
Source `json:"source"`
|
|
|
|
FullURL *string `json:"value,omitempty"` // only for absolute/relative icons
|
|
Extra *Extra `json:"extra,omitempty"` // only for walkxcode/selfhst icons
|
|
}
|
|
|
|
Extra struct {
|
|
Key Key `json:"key"`
|
|
Ref string `json:"ref"`
|
|
FileType string `json:"file_type"`
|
|
IsLight bool `json:"is_light"`
|
|
IsDark bool `json:"is_dark"`
|
|
}
|
|
|
|
Source string
|
|
Variant string
|
|
)
|
|
|
|
const (
|
|
SourceAbsolute Source = "https://"
|
|
SourceRelative Source = "@target"
|
|
SourceWalkXCode Source = "@walkxcode"
|
|
SourceSelfhSt Source = "@selfhst"
|
|
)
|
|
|
|
const (
|
|
VariantNone Variant = ""
|
|
VariantLight Variant = "light"
|
|
VariantDark Variant = "dark"
|
|
)
|
|
|
|
var ErrInvalidIconURL = errors.New("invalid icon url")
|
|
|
|
func NewURL(source Source, refOrName, format string) *URL {
|
|
switch source {
|
|
case SourceWalkXCode, SourceSelfhSt:
|
|
default:
|
|
panic("invalid icon source")
|
|
}
|
|
isLight, isDark := false, false
|
|
if strings.HasSuffix(refOrName, "-light") {
|
|
isLight = true
|
|
refOrName = strings.TrimSuffix(refOrName, "-light")
|
|
} else if strings.HasSuffix(refOrName, "-dark") {
|
|
isDark = true
|
|
refOrName = strings.TrimSuffix(refOrName, "-dark")
|
|
}
|
|
return &URL{
|
|
Source: source,
|
|
Extra: &Extra{
|
|
Key: NewKey(source, refOrName),
|
|
FileType: format,
|
|
Ref: refOrName,
|
|
IsLight: isLight,
|
|
IsDark: isDark,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (u *URL) HasIcon() bool {
|
|
return hasIcon(u)
|
|
}
|
|
|
|
func (u *URL) WithVariant(variant Variant) *URL {
|
|
switch u.Source {
|
|
case SourceWalkXCode, SourceSelfhSt:
|
|
default:
|
|
return u // no variant for absolute/relative icons
|
|
}
|
|
|
|
var extra *Extra
|
|
if u.Extra != nil {
|
|
extra = &Extra{
|
|
Key: u.Extra.Key,
|
|
Ref: u.Extra.Ref,
|
|
FileType: u.Extra.FileType,
|
|
IsLight: variant == VariantLight,
|
|
IsDark: variant == VariantDark,
|
|
}
|
|
extra.Ref = strings.TrimSuffix(extra.Ref, "-light")
|
|
extra.Ref = strings.TrimSuffix(extra.Ref, "-dark")
|
|
}
|
|
return &URL{
|
|
Source: u.Source,
|
|
FullURL: u.FullURL,
|
|
Extra: extra,
|
|
}
|
|
}
|
|
|
|
// Parse implements strutils.Parser.
|
|
func (u *URL) Parse(v string) error {
|
|
return u.parse(v, true)
|
|
}
|
|
|
|
func (u *URL) parse(v string, checkExists bool) error {
|
|
if v == "" {
|
|
return ErrInvalidIconURL
|
|
}
|
|
slashIndex := strings.Index(v, "/")
|
|
if slashIndex == -1 {
|
|
return ErrInvalidIconURL
|
|
}
|
|
beforeSlash := v[:slashIndex]
|
|
switch beforeSlash {
|
|
case "http:", "https:":
|
|
u.FullURL = &v
|
|
u.Source = SourceAbsolute
|
|
case "@target", "": // @target/favicon.ico, /favicon.ico
|
|
url := v[slashIndex:]
|
|
if url == "/" {
|
|
return fmt.Errorf("%w: empty path", ErrInvalidIconURL)
|
|
}
|
|
u.FullURL = &url
|
|
u.Source = SourceRelative
|
|
case "@selfhst", "@walkxcode": // selfh.st / walkxcode Icons, @selfhst/<reference>.<format>
|
|
if beforeSlash == "@selfhst" {
|
|
u.Source = SourceSelfhSt
|
|
} else {
|
|
u.Source = SourceWalkXCode
|
|
}
|
|
parts := strings.Split(v[slashIndex+1:], ".")
|
|
if len(parts) != 2 {
|
|
return fmt.Errorf("%w: expect %s/<reference>.<format>, e.g. %s/adguard-home.webp", ErrInvalidIconURL, beforeSlash, beforeSlash)
|
|
}
|
|
reference, format := parts[0], strings.ToLower(parts[1])
|
|
if reference == "" || format == "" {
|
|
return ErrInvalidIconURL
|
|
}
|
|
switch format {
|
|
case "svg", "png", "webp":
|
|
default:
|
|
return fmt.Errorf("%w: invalid image format, expect svg/png/webp", ErrInvalidIconURL)
|
|
}
|
|
isLight, isDark := false, false
|
|
if strings.HasSuffix(reference, "-light") {
|
|
isLight = true
|
|
reference = strings.TrimSuffix(reference, "-light")
|
|
} else if strings.HasSuffix(reference, "-dark") {
|
|
isDark = true
|
|
reference = strings.TrimSuffix(reference, "-dark")
|
|
}
|
|
u.Extra = &Extra{
|
|
Key: NewKey(u.Source, reference),
|
|
FileType: format,
|
|
Ref: reference,
|
|
IsLight: isLight,
|
|
IsDark: isDark,
|
|
}
|
|
if checkExists && !u.HasIcon() {
|
|
return fmt.Errorf("%w: no such icon %s.%s from %s", ErrInvalidIconURL, reference, format, u.Source)
|
|
}
|
|
default:
|
|
return gperr.PrependSubject(ErrInvalidIconURL, v)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *URL) URL() string {
|
|
if u.FullURL != nil {
|
|
return *u.FullURL
|
|
}
|
|
if u.Extra == nil {
|
|
return ""
|
|
}
|
|
filename := u.Extra.Ref
|
|
if u.Extra.IsLight {
|
|
filename += "-light"
|
|
} else if u.Extra.IsDark {
|
|
filename += "-dark"
|
|
}
|
|
switch u.Source {
|
|
case SourceWalkXCode:
|
|
return fmt.Sprintf("https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/%s/%s.%s", u.Extra.FileType, filename, u.Extra.FileType)
|
|
case SourceSelfhSt:
|
|
return fmt.Sprintf("https://cdn.jsdelivr.net/gh/selfhst/icons/%s/%s.%s", u.Extra.FileType, filename, u.Extra.FileType)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (u *URL) String() string {
|
|
if u.FullURL != nil {
|
|
return *u.FullURL
|
|
}
|
|
if u.Extra == nil {
|
|
return ""
|
|
}
|
|
var suffix string
|
|
if u.Extra.IsLight {
|
|
suffix = "-light"
|
|
} else if u.Extra.IsDark {
|
|
suffix = "-dark"
|
|
}
|
|
return fmt.Sprintf("%s/%s%s.%s", u.Source, u.Extra.Ref, suffix, u.Extra.FileType)
|
|
}
|
|
|
|
func (u *URL) MarshalText() ([]byte, error) {
|
|
return []byte(u.String()), nil
|
|
}
|
|
|
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
|
func (u *URL) UnmarshalText(data []byte) error {
|
|
return u.parse(string(data), false)
|
|
}
|