-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support for hex/rgb/rgba colour formats (#3)
- Loading branch information
1 parent
b5f799c
commit c0474c7
Showing
7 changed files
with
400 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package revisor | ||
|
||
import ( | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"slices" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
type ColourFormat string | ||
|
||
const ( | ||
ColourUnknown ColourFormat = "" | ||
ColourHex ColourFormat = "hex" | ||
ColourRGB ColourFormat = "rgb" | ||
ColourRGBA ColourFormat = "rgba" | ||
) | ||
|
||
type cFormatSpec struct { | ||
Format ColourFormat | ||
Prefix string | ||
Validate func(spec cFormatSpec, code string) error | ||
} | ||
|
||
var ( | ||
defaultColourFormats = []ColourFormat{ColourRGB, ColourRGBA} | ||
colourComponents = []string{"r", "g", "b", "alpha"} | ||
colourFormats = []cFormatSpec{ | ||
{ | ||
Format: ColourHex, | ||
Prefix: "#", | ||
Validate: parseHex, | ||
}, | ||
{ | ||
Format: ColourRGBA, | ||
Prefix: "rgba", | ||
Validate: parseRGBA, | ||
}, | ||
{ | ||
Format: ColourRGB, | ||
Prefix: "rgb", | ||
Validate: parseRGBA, | ||
}, | ||
} | ||
) | ||
|
||
func validateColour(value string, formats []ColourFormat) error { | ||
var ( | ||
spec cFormatSpec | ||
code string | ||
) | ||
|
||
for _, s := range colourFormats { | ||
after, ok := strings.CutPrefix(value, s.Prefix) | ||
if !ok { | ||
continue | ||
} | ||
|
||
spec = s | ||
code = after | ||
|
||
break | ||
} | ||
|
||
if len(formats) == 0 { | ||
formats = defaultColourFormats | ||
} | ||
|
||
if spec.Format == ColourUnknown || !slices.Contains(formats, spec.Format) { | ||
if len(formats) == 1 { | ||
return fmt.Errorf("expected a colour in the format %q", | ||
formats[0]) | ||
} | ||
|
||
return fmt.Errorf("expected a colour in one of the formats %s", | ||
quotedSlice(formats)) | ||
} | ||
|
||
return spec.Validate(spec, code) | ||
} | ||
|
||
const hexColourLength = 6 | ||
|
||
func parseHex(_ cFormatSpec, code string) error { | ||
if len(code) != hexColourLength { | ||
return fmt.Errorf("code length: expected %d characters, got %d", | ||
hexColourLength, len(code)) | ||
} | ||
|
||
_, err := hex.DecodeString(code) | ||
if err != nil { | ||
return fmt.Errorf("invalid hex code: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func parseRGBA(spec cFormatSpec, code string) error { | ||
rest, ok := strings.CutPrefix(code, "(") | ||
if !ok { | ||
return errors.New("missing starting '('") | ||
} | ||
|
||
rest, ok = strings.CutSuffix(rest, ")") | ||
if !ok { | ||
return errors.New("missing closing ')'") | ||
} | ||
|
||
numberStrings := strings.Split(rest, ",") | ||
components := len(numberStrings) | ||
|
||
//nolint: exhaustive | ||
switch spec.Format { | ||
case ColourRGB: | ||
if components != 3 { | ||
return fmt.Errorf("expected three components in a rgb() value, got %d", components) | ||
} | ||
case ColourRGBA: | ||
if components != 4 { | ||
return fmt.Errorf("expected four components in a rgba() value, got %d", components) | ||
} | ||
|
||
n, err := strconv.ParseFloat(strings.TrimSpace(numberStrings[3]), 64) | ||
if err != nil { | ||
return fmt.Errorf("invalid alpha value: %w", err) | ||
} | ||
|
||
if n < 0 || n > 1 { | ||
return fmt.Errorf("%q out of range", colourComponents[3]) | ||
} | ||
default: | ||
return fmt.Errorf( | ||
"configuration error: cannot parse %q with parseRGBA()", | ||
spec.Format, | ||
) | ||
} | ||
|
||
for i, ns := range numberStrings[:3] { | ||
n, err := strconv.Atoi(strings.TrimSpace(ns)) | ||
if err != nil { | ||
return fmt.Errorf("invalid %q value: %w", | ||
colourComponents[i], err) | ||
} | ||
|
||
if n < 0 || n > 255 { | ||
return fmt.Errorf("%q out of range", colourComponents[i]) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func quotedSlice[T any](s []T) string { | ||
ss := make([]string, len(s)) | ||
|
||
for i, v := range s { | ||
ss[i] = strconv.Quote(fmt.Sprintf("%v", v)) | ||
} | ||
|
||
return strings.Join(ss, ", ") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
{ | ||
"uuid": "67912d12-bea0-4ec3-a8d4-16d669bd35bc", | ||
"type": "test/colour-doc", | ||
"content": [ | ||
{ | ||
"type": "test/colour", | ||
"data": { | ||
"hexy": "#fefefe", | ||
"solid": "rgb(10,10,10)", | ||
"transparent": "rgba(10, 10, 10, 0.5)", | ||
"anycol": "rgba(10, 10, 10, 0.5)" | ||
} | ||
}, | ||
{ | ||
"type": "test/colour", | ||
"data": { | ||
"hexy": "#feqefe", | ||
"solid": "rgb(-1,10,10)", | ||
"transparent": "rgba(10, 10, 10, 2)", | ||
"anycol": "rgb(10, 10, 10)" | ||
} | ||
}, | ||
{ | ||
"type": "test/colour", | ||
"data": { | ||
"hexy": "#fefefefe", | ||
"solid": "rgba(10,10,10,0.3)", | ||
"transparent": "rgb(10, 10, 10)", | ||
"anycol": "#ab332a" | ||
} | ||
}, | ||
{ | ||
"type": "test/colour", | ||
"data": { | ||
"hexy": "fefefe", | ||
"anycol": "nope" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"version": 1, | ||
"name": "colour", | ||
"documents": [ | ||
{ | ||
"declares": "test/colour-doc", | ||
"content": [ | ||
{ | ||
"declares": {"type": "test/colour"}, | ||
"data": { | ||
"hexy": { | ||
"format": "colour", | ||
"colourFormats": ["hex"], | ||
"optional": true | ||
}, | ||
"solid": { | ||
"format": "colour", | ||
"colourFormats": ["rgb"], | ||
"optional": true | ||
}, | ||
"transparent": { | ||
"format": "colour", | ||
"colourFormats": ["rgba"], | ||
"optional": true | ||
}, | ||
"anycol": { | ||
"format": "colour", | ||
"colourFormats": ["rgba", "rgb", "hex"], | ||
"optional": true | ||
} | ||
} | ||
} | ||
] | ||
} | ||
] | ||
} |
Oops, something went wrong.