Initial commit

Signed-off-by: jolheiser <john.olheiser@gmail.com>
main v0.0.1
jolheiser 2022-05-05 16:28:39 -05:00
commit 47c700808a
Signed by: jolheiser
GPG Key ID: B853ADA5DA7BBF7A
15 changed files with 850 additions and 0 deletions

3
.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
.idea/
/chromajson.exe
/chromajson

19
LICENSE 100644
View File

@ -0,0 +1,19 @@
Copyright (c) 2022 John Olheiser
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

15
README.md 100644
View File

@ -0,0 +1,15 @@
# chromajson
A JSON schema and loader for [chroma](https://github.com/alecthomas/chroma).
## Schema
[schema.json](schema/schema.json) provides a [JSON schema](https://json-schema.org).
## Styles
There is a valid [catppuccin](https://github.com/catppuccin) style in [catppuccin.json](schema/testdata/catppuccin.json).
## License
[MIT](LICENSE)

79
chromajson.go 100644
View File

@ -0,0 +1,79 @@
package chromajson
import (
"encoding/json"
"fmt"
"io"
"strings"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/styles"
)
// Style is a chroma style
type Style struct {
Name string `json:"name"`
Entries map[string]StyleEntry `json:"style"`
}
// StyleEntry is a mapping for a chroma style
type StyleEntry struct {
Color string `json:"color"`
Background string `json:"background"`
Border string `json:"border"`
Accents []string `json:"accent"`
}
// String returns a chroma-representable style entry
func (s StyleEntry) String() string {
normalise := func(c string) string {
return "#" + strings.TrimLeft(c, "#")
}
parts := make([]string, 0, 4)
if len(s.Accents) > 0 {
parts = append(parts, strings.Join(s.Accents, " "))
}
if s.Background != "" {
parts = append(parts, fmt.Sprintf("bg:%s", normalise(s.Background)))
}
if s.Border != "" {
parts = append(parts, fmt.Sprintf("border:%s", normalise(s.Border)))
}
if s.Color != "" {
parts = append(parts, normalise(s.Color))
}
return strings.Join(parts, " ")
}
// Chroma converts a Style to a chroma.Style
func (s *Style) Chroma() (*chroma.Style, error) {
style := chroma.NewStyleBuilder(s.Name)
for name, entry := range s.Entries {
if token, ok := tokenToType[name]; ok {
style.Add(token, entry.String())
}
}
return style.Build()
}
// Register registers the Style directly to chroma
func (s *Style) Register() error {
style, err := s.Chroma()
if err != nil {
return err
}
styles.Register(style)
return nil
}
// LoadStyle loads a Style from an io.Reader
func LoadStyle(r io.Reader) (*Style, error) {
var s Style
d := json.NewDecoder(r)
d.DisallowUnknownFields()
if err := d.Decode(&s); err != nil {
return nil, err
}
return &s, nil
}

31
chromajson_test.go 100644
View File

@ -0,0 +1,31 @@
package chromajson
import (
"testing"
"github.com/matryer/is"
)
func TestStyleEntry(t *testing.T) {
tt := []struct {
Name string
Entry *StyleEntry
Expected string
}{
{Name: "color1", Entry: &StyleEntry{Color: "#FFF"}, Expected: "#FFF"},
{Name: "color2", Entry: &StyleEntry{Color: "white"}, Expected: "#white"},
{Name: "background1", Entry: &StyleEntry{Background: "#FFF"}, Expected: "bg:#FFF"},
{Name: "background2", Entry: &StyleEntry{Background: "white"}, Expected: "bg:#white"},
{Name: "border1", Entry: &StyleEntry{Border: "#FFF"}, Expected: "border:#FFF"},
{Name: "border2", Entry: &StyleEntry{Border: "white"}, Expected: "border:#white"},
{Name: "accent", Entry: &StyleEntry{Accents: []string{"bold"}}, Expected: "bold"},
{Name: "mix", Entry: &StyleEntry{Color: "black", Accents: []string{"bold"}, Background: "#ffffff"}, Expected: "bold bg:#ffffff #black"},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
assert := is.New(t)
assert.Equal(tc.Entry.String(), tc.Expected)
})
}
}

View File

@ -0,0 +1,79 @@
package main
import (
"flag"
"fmt"
"io"
"os"
"github.com/alecthomas/chroma/v2/quick"
"go.jolheiser.com/chromajson"
"go.jolheiser.com/chromajson/schema"
)
func main() {
fs := flag.NewFlagSet("chromajson", flag.ExitOnError)
styleFlag := fs.String("style", ".style.json", "Chroma JSON style config")
lintFlag := fs.Bool("lint", false, "Lint config and exit")
if err := fs.Parse(os.Args[1:]); err != nil {
fmt.Println(err)
return
}
if !*lintFlag && fs.NArg() == 0 {
fmt.Println("input argument is required to show style")
return
}
fi, err := os.Open(*styleFlag)
if err != nil {
fmt.Println(err)
return
}
defer fi.Close()
if *lintFlag {
lint(fi)
return
}
style, err := chromajson.LoadStyle(fi)
if err != nil {
fmt.Println(err)
return
}
if err := style.Register(); err != nil {
fmt.Println(err)
return
}
source, err := os.ReadFile(fs.Arg(0))
if err != nil {
fmt.Println(err)
return
}
if err := quick.Highlight(fs.Output(), string(source), "", "terminal256", style.Name); err != nil {
fmt.Println(err)
return
}
}
func lint(r io.Reader) {
ok, err := schema.Lint(r)
if ok {
fmt.Println("✅ Config is valid!")
return
}
fmt.Printf("❌ Config is invalid: %v\n", err)
rerrs, ok := err.(schema.ResultErrors)
if !ok {
fmt.Println(err)
return
}
for _, rerr := range rerrs {
fmt.Printf("In %s: %s\n", rerr.Field(), rerr.Description())
}
}

15
go.mod 100644
View File

@ -0,0 +1,15 @@
module go.jolheiser.com/chromajson
go 1.18
require (
github.com/alecthomas/chroma/v2 v2.0.0-alpha4
github.com/matryer/is v1.4.0
github.com/xeipuuv/gojsonschema v1.2.0
)
require (
github.com/dlclark/regexp2 v1.4.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
)

21
go.sum 100644
View File

@ -0,0 +1,21 @@
github.com/alecthomas/chroma/v2 v2.0.0-alpha4 h1:6s0y/julsg565meUfJd/aDv5nR4srI3Z3RgyId8w3Ro=
github.com/alecthomas/chroma/v2 v2.0.0-alpha4/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=

127
mapping.go 100644
View File

@ -0,0 +1,127 @@
package chromajson
import "github.com/alecthomas/chroma/v2"
var tokenToType = map[string]chroma.TokenType{
"background": chroma.Background,
"pre_wrapper": chroma.PreWrapper,
"line": chroma.Line,
"line_numbers": chroma.LineNumbers,
"line_numbers_table": chroma.LineNumbersTable,
"line_highlight": chroma.LineHighlight,
"line_table": chroma.LineTable,
"line_table_td": chroma.LineTableTD,
"code_line": chroma.CodeLine,
"error": chroma.Error,
"other": chroma.Other,
"none": chroma.None,
"eof_type": chroma.EOFType,
"keyword": chroma.Keyword,
"keyword_constant": chroma.KeywordConstant,
"keyword_declaration": chroma.KeywordDeclaration,
"keyword_namespace": chroma.KeywordNamespace,
"keyword_pseudo": chroma.KeywordPseudo,
"keyword_reserved": chroma.KeywordReserved,
"keyword_type": chroma.KeywordType,
"name": chroma.Name,
"name_attribute": chroma.NameAttribute,
"name_builtin": chroma.NameBuiltin,
"name_builtin_pseudo": chroma.NameBuiltinPseudo,
"name_class": chroma.NameClass,
"name_constant": chroma.NameConstant,
"name_decorator": chroma.NameDecorator,
"name_entity": chroma.NameEntity,
"name_exception": chroma.NameException,
"name_function": chroma.NameFunction,
"name_function_magic": chroma.NameFunctionMagic,
"name_keyword": chroma.NameKeyword,
"name_label": chroma.NameLabel,
"name_namespace": chroma.NameNamespace,
"name_operator": chroma.NameOperator,
"name_other": chroma.NameOther,
"name_pseudo": chroma.NamePseudo,
"name_property": chroma.NameProperty,
"name_tag": chroma.NameTag,
"name_variable": chroma.NameVariable,
"name_variable_anonymous": chroma.NameVariableAnonymous,
"name_variable_class": chroma.NameVariableClass,
"name_variable_global": chroma.NameVariableGlobal,
"name_variable_instance": chroma.NameVariableInstance,
"name_variable_magic": chroma.NameVariableMagic,
"literal": chroma.Literal,
"literal_date": chroma.LiteralDate,
"literal_other": chroma.LiteralOther,
"literal_string": chroma.LiteralString,
"literal_string_affix": chroma.LiteralStringAffix,
"literal_string_atom": chroma.LiteralStringAtom,
"literal_string_backtick": chroma.LiteralStringBacktick,
"literal_string_boolean": chroma.LiteralStringBoolean,
"literal_string_char": chroma.LiteralStringChar,
"literal_string_delimiter": chroma.LiteralStringDelimiter,
"literal_string_doc": chroma.LiteralStringDoc,
"literal_string_double": chroma.LiteralStringDouble,
"literal_string_escape": chroma.LiteralStringEscape,
"literal_string_heredoc": chroma.LiteralStringHeredoc,
"literal_string_interpol": chroma.LiteralStringInterpol,
"literal_string_name": chroma.LiteralStringName,
"literal_string_other": chroma.LiteralStringOther,
"literal_string_regex": chroma.LiteralStringRegex,
"literal_string_single": chroma.LiteralStringSingle,
"literal_string_symbol": chroma.LiteralStringSymbol,
"literal_number": chroma.LiteralNumber,
"literal_number_bin": chroma.LiteralNumberBin,
"literal_number_float": chroma.LiteralNumberFloat,
"literal_number_hex": chroma.LiteralNumberHex,
"literal_number_integer": chroma.LiteralNumberInteger,
"literal_number_integer_long": chroma.LiteralNumberIntegerLong,
"literal_number_oct": chroma.LiteralNumberOct,
"operator": chroma.Operator,
"operator_word": chroma.OperatorWord,
"punctuation": chroma.Punctuation,
"comment": chroma.Comment,
"comment_hashbang": chroma.CommentHashbang,
"comment_multiline": chroma.CommentMultiline,
"comment_single": chroma.CommentSingle,
"comment_special": chroma.CommentSpecial,
"comment_preproc": chroma.CommentPreproc,
"comment_preproc_file": chroma.CommentPreprocFile,
"generic": chroma.Generic,
"generic_deleted": chroma.GenericDeleted,
"generic_emph": chroma.GenericEmph,
"generic_error": chroma.GenericError,
"generic_heading": chroma.GenericHeading,
"generic_inserted": chroma.GenericInserted,
"generic_output": chroma.GenericOutput,
"generic_prompt": chroma.GenericPrompt,
"generic_strong": chroma.GenericStrong,
"generic_subheading": chroma.GenericSubheading,
"generic_traceback": chroma.GenericTraceback,
"generic_underline": chroma.GenericUnderline,
"text": chroma.Text,
"text_whitespace": chroma.TextWhitespace,
"text_symbol": chroma.TextSymbol,
"text_punctuation": chroma.TextPunctuation,
"whitespace": chroma.Whitespace,
"date": chroma.Date,
"string": chroma.String,
"string_affix": chroma.StringAffix,
"string_backtick": chroma.StringBacktick,
"string_char": chroma.StringChar,
"string_delimiter": chroma.StringDelimiter,
"string_doc": chroma.StringDoc,
"string_double": chroma.StringDouble,
"string_escape": chroma.StringEscape,
"string_heredoc": chroma.StringHeredoc,
"string_interpol": chroma.StringInterpol,
"string_other": chroma.StringOther,
"string_regex": chroma.StringRegex,
"string_single": chroma.StringSingle,
"string_symbol": chroma.StringSymbol,
"number": chroma.Number,
"number_bin": chroma.NumberBin,
"number_float": chroma.NumberFloat,
"number_hex": chroma.NumberHex,
"number_integer": chroma.NumberInteger,
"number_integer_long": chroma.NumberIntegerLong,
"number_oct": chroma.NumberOct,
}

44
schema/schema.go 100644
View File

@ -0,0 +1,44 @@
package schema
import (
_ "embed"
"fmt"
"io"
"strings"
"github.com/xeipuuv/gojsonschema"
)
var (
//go:embed schema.json
schema []byte
schemaLoader = gojsonschema.NewBytesLoader(schema)
)
// Lint is for linting a style against the schema
func Lint(r io.Reader) (bool, error) {
sourceLoader, t := gojsonschema.NewReaderLoader(r)
if _, err := io.Copy(io.Discard, t); err != nil {
return false, err
}
result, err := gojsonschema.Validate(schemaLoader, sourceLoader)
if err != nil {
return false, err
}
if len(result.Errors()) > 0 {
return false, ResultErrors(result.Errors())
}
return result.Valid(), nil
}
// ResultErrors is a slice of gojsonschema.ResultError that implements error
type ResultErrors []gojsonschema.ResultError
// Error implements error
func (r ResultErrors) Error() string {
errs := make([]string, 0, len(r))
for _, re := range r {
errs = append(errs, fmt.Sprintf("%s: %s", re.Field(), re.Description()))
}
return strings.Join(errs, " | ")
}

207
schema/schema.json 100644
View File

@ -0,0 +1,207 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.jojodev.com/jolheiser/chromajson/schema/schema.json",
"title": "Chroma style configuration",
"description": "A chroma style to be registered",
"type": "object",
"required": ["name"],
"additionalProperties": false,
"properties": {
"name": {
"description": "Style name",
"type": "string"
},
"style": {
"description": "Chroma style",
"type": "object",
"properties": {
"background": {"$ref": "#/$defs/style"},
"pre_wrapper": {"$ref": "#/$defs/style"},
"line": {"$ref": "#/$defs/style"},
"line_numbers": {"$ref": "#/$defs/style"},
"line_numbers_table": {"$ref": "#/$defs/style"},
"line_highlight": {"$ref": "#/$defs/style"},
"line_table": {"$ref": "#/$defs/style"},
"line_table_td": {"$ref": "#/$defs/style"},
"code_line": {"$ref": "#/$defs/style"},
"error": {"$ref": "#/$defs/style"},
"other": {"$ref": "#/$defs/style"},
"none": {"$ref": "#/$defs/style"},
"eof_type": {"$ref": "#/$defs/style"},
"keyword": {"$ref": "#/$defs/style"},
"keyword_constant": {"$ref": "#/$defs/style"},
"keyword_declaration": {"$ref": "#/$defs/style"},
"keyword_namespace": {"$ref": "#/$defs/style"},
"keyword_pseudo": {"$ref": "#/$defs/style"},
"keyword_reserved": {"$ref": "#/$defs/style"},
"keyword_type": {"$ref": "#/$defs/style"},
"name": {"$ref": "#/$defs/style"},
"name_attribute": {"$ref": "#/$defs/style"},
"name_builtin": {"$ref": "#/$defs/style"},
"name_builtin_pseudo": {"$ref": "#/$defs/style"},
"name_class": {"$ref": "#/$defs/style"},
"name_constant": {"$ref": "#/$defs/style"},
"name_decorator": {"$ref": "#/$defs/style"},
"name_entity": {"$ref": "#/$defs/style"},
"name_exception": {"$ref": "#/$defs/style"},
"name_function": {"$ref": "#/$defs/style"},
"name_function_magic": {"$ref": "#/$defs/style"},
"name_keyword": {"$ref": "#/$defs/style"},
"name_label": {"$ref": "#/$defs/style"},
"name_namespace": {"$ref": "#/$defs/style"},
"name_operator": {"$ref": "#/$defs/style"},
"name_other": {"$ref": "#/$defs/style"},
"name_pseudo": {"$ref": "#/$defs/style"},
"name_property": {"$ref": "#/$defs/style"},
"name_tag": {"$ref": "#/$defs/style"},
"name_variable": {"$ref": "#/$defs/style"},
"name_variable_anonymous": {"$ref": "#/$defs/style"},
"name_variable_class": {"$ref": "#/$defs/style"},
"name_variable_global": {"$ref": "#/$defs/style"},
"name_variable_instance": {"$ref": "#/$defs/style"},
"name_variable_magic": {"$ref": "#/$defs/style"},
"literal": {"$ref": "#/$defs/style"},
"literal_date": {"$ref": "#/$defs/style"},
"literal_other": {"$ref": "#/$defs/style"},
"literal_string": {"$ref": "#/$defs/style"},
"literal_string_affix": {"$ref": "#/$defs/style"},
"literal_string_atom": {"$ref": "#/$defs/style"},
"literal_string_backtick": {"$ref": "#/$defs/style"},
"literal_string_boolean": {"$ref": "#/$defs/style"},
"literal_string_char": {"$ref": "#/$defs/style"},
"literal_string_delimiter": {"$ref": "#/$defs/style"},
"literal_string_doc": {"$ref": "#/$defs/style"},
"literal_string_double": {"$ref": "#/$defs/style"},
"literal_string_escape": {"$ref": "#/$defs/style"},
"literal_string_heredoc": {"$ref": "#/$defs/style"},
"literal_string_interpol": {"$ref": "#/$defs/style"},
"literal_string_name": {"$ref": "#/$defs/style"},
"literal_string_other": {"$ref": "#/$defs/style"},
"literal_string_regex": {"$ref": "#/$defs/style"},
"literal_string_single": {"$ref": "#/$defs/style"},
"literal_string_symbol": {"$ref": "#/$defs/style"},
"literal_number": {"$ref": "#/$defs/style"},
"literal_number_bin": {"$ref": "#/$defs/style"},
"literal_number_float": {"$ref": "#/$defs/style"},
"literal_number_hex": {"$ref": "#/$defs/style"},
"literal_number_integer": {"$ref": "#/$defs/style"},
"literal_number_integer_long": {"$ref": "#/$defs/style"},
"literal_number_oct": {"$ref": "#/$defs/style"},
"operator": {"$ref": "#/$defs/style"},
"operator_word": {"$ref": "#/$defs/style"},
"punctuation": {"$ref": "#/$defs/style"},
"comment": {"$ref": "#/$defs/style"},
"comment_hashbang": {"$ref": "#/$defs/style"},
"comment_multiline": {"$ref": "#/$defs/style"},
"comment_single": {"$ref": "#/$defs/style"},
"comment_special": {"$ref": "#/$defs/style"},
"comment_preproc": {"$ref": "#/$defs/style"},
"comment_preproc_file": {"$ref": "#/$defs/style"},
"generic": {"$ref": "#/$defs/style"},
"generic_deleted": {"$ref": "#/$defs/style"},
"generic_emph": {"$ref": "#/$defs/style"},
"generic_error": {"$ref": "#/$defs/style"},
"generic_heading": {"$ref": "#/$defs/style"},
"generic_inserted": {"$ref": "#/$defs/style"},
"generic_output": {"$ref": "#/$defs/style"},
"generic_prompt": {"$ref": "#/$defs/style"},
"generic_strong": {"$ref": "#/$defs/style"},
"generic_subheading": {"$ref": "#/$defs/style"},
"generic_traceback": {"$ref": "#/$defs/style"},
"generic_underline": {"$ref": "#/$defs/style"},
"text": {"$ref": "#/$defs/style"},
"text_whitespace": {"$ref": "#/$defs/style"},
"text_symbol": {"$ref": "#/$defs/style"},
"text_punctuation": {"$ref": "#/$defs/style"},
"whitespace": {"$ref": "#/properties/style/properties/text_whitespace"},
"date": {"$ref": "#/properties/style/properties/literal_date"},
"string": {"$ref": "#/properties/style/properties/literal_string"},
"string_affix": {"$ref": "#/properties/style/properties/literal_string_affix"},
"string_backtick": {"$ref": "#/properties/style/properties/literal_string_backtick"},
"string_char": {"$ref": "#/properties/style/properties/literal_string_char"},
"string_delimiter": {"$ref": "#/properties/style/properties/literal_string_delimiter"},
"string_doc": {"$ref": "#/properties/style/properties/literal_string_doc"},
"string_double": {"$ref": "#/properties/style/properties/literal_string_double"},
"string_escape": {"$ref": "#/properties/style/properties/literal_string_escape"},
"string_heredoc": {"$ref": "#/properties/style/properties/literal_string_heredoc"},
"string_interpol": {"$ref": "#/properties/style/properties/literal_string_interpol"},
"string_other": {"$ref": "#/properties/style/properties/literal_string_other"},
"string_regex": {"$ref": "#/properties/style/properties/literal_string_regex"},
"string_single": {"$ref": "#/properties/style/properties/literal_string_single"},
"string_symbol": {"$ref": "#/properties/style/properties/literal_string_symbol"},
"number": {"$ref": "#/properties/style/properties/literal_number"},
"number_bin": {"$ref": "#/properties/style/properties/literal_number"},
"number_float": {"$ref": "#/properties/style/properties/literal_number_bin"},
"number_hex": {"$ref": "#/properties/style/properties/literal_number_hex"},
"number_integer": {"$ref": "#/properties/style/properties/literal_number_integer"},
"number_integer_long": {"$ref": "#/properties/style/properties/literal_number_integer_long"},
"number_oct": {"$ref": "#/properties/style/properties/literal_number_oct"}
},
"additionalProperties": false
}
},
"$defs": {
"style": {
"description": "Token style",
"type": "object",
"additionalProperties": false,
"properties": {
"accent": {
"description": "Font accent(s)",
"type": "array",
"items": {
"type": "string",
"enum": ["italic", "noitalic", "bold", "nobold", "underline", "nounderline", "inherit", "noinherit"]
},
"uniqueItems": true
},
"color": {
"description": "Font color",
"oneOf": [
{"$ref": "#/$defs/hex"},
{"$ref": "#/$defs/ansi"}
]
},
"background": {
"description": "Background color",
"oneOf": [
{"$ref": "#/$defs/hex"},
{"$ref": "#/$defs/ansi"}
]
},
"border": {
"description": "Border color",
"oneOf": [
{"$ref": "#/$defs/hex"},
{"$ref": "#/$defs/ansi"}
]
}
}
},
"hex": {
"description": "Hex color",
"type": "string",
"pattern": "^#(?:[A-Fa-f\\d]{3}|[A-Fa-f\\d]{6})$"
},
"ansi": {
"description": "ANSI color",
"type": "string",
"enum": ["black", "darkred", "darkgreen", "brown", "darkblue", "purple", "teal", "lightgray", "darkgray", "red", "green", "yellow", "blue", "fuchsia", "turquoise", "white"]
}
}
}

View File

@ -0,0 +1,43 @@
package schema
import (
"embed"
"fmt"
"testing"
"github.com/matryer/is"
)
//go:embed testdata
var testdata embed.FS
func TestLint(t *testing.T) {
tt := []struct {
Name string
NumErr int
}{
{Name: "catppuccin"},
{Name: "minimal"},
{Name: "bad", NumErr: 8},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
assert := is.New(t)
fi, err := testdata.Open(fmt.Sprintf("testdata/%s.json", tc.Name))
assert.NoErr(err) // Should open file
defer fi.Close()
ok, err := Lint(fi)
if tc.NumErr > 0 {
assert.True(!ok) // Linter should fail
errs, ok := err.(ResultErrors)
assert.True(ok) // err should be of type ResultErrors
assert.Equal(tc.NumErr, len(errs)) // Number of errors should be consistent
} else {
assert.NoErr(err) // Linter should pass
}
})
}
}

18
schema/testdata/bad.json vendored 100644
View File

@ -0,0 +1,18 @@
{
"name": "bad",
"style": {
"background": {
"color": "rainbow"
},
"text": {
"background": "#ZZZ"
},
"foo": {},
"number_integer": {
"border": "hello, world"
},
"comment": {
"accent": ["bold", "bold"]
}
}
}

146
schema/testdata/catppuccin.json vendored 100644
View File

@ -0,0 +1,146 @@
{
"name": "catppuccin",
"style": {
"text_whitespace": {
"color": "#302D41"
},
"comment": {
"color": "#6E6C7E",
"accent": ["italic"]
},
"comment_preproc": {
"color": "#96CDFB"
},
"keyword": {
"color": "#DDB6F2"
},
"keyword_pseudo": {
"color": "#DDB6F2",
"accent": ["bold"]
},
"keyword_type": {
"color": "#F2CDCD"
},
"keyword_constant": {
"color": "#DDB6F2",
"accent": ["italic"]
},
"operator": {
"color": "#89DCEB"
},
"operator_word": {
"color": "#89DCEB",
"accent": ["bold"]
},
"name": {
"color": "#C9CBFF"
},
"name_builtin": {
"color": "#D9E0EE",
"accent": ["italic"]
},
"name_function": {
"color": "#89DCEB"
},
"name_class": {
"color": "#FAE3B0"
},
"name_namespace": {
"color": "#FAE3B0"
},
"name_exception": {
"color": "#E8A2AF"
},
"name_variable": {
"color": "#F8BD96"
},
"name_constant": {
"color": "#FAE3B0"
},
"name_label": {
"color": "#FAE3B0"
},
"name_entity": {
"color": "#F5C2E7"
},
"name_attribute": {
"color": "#FAE3B0"
},
"name_tag": {
"color": "#DDB6F2"
},
"name_decorator": {
"color": "#F5C2E7"
},
"name_other": {
"color": "#F8BD96"
},
"punctuation": {
"color": "#D9E0EE"
},
"string": {
"color": "#ABE9B3"
},
"string_doc": {
"color": "#ABE9B3"
},
"string_interpol": {
"color": "#ABE9B3"
},
"string_escape": {
"color": "#96CDFB"
},
"string_regex": {
"color": "#96CDFB"
},
"string_symbol": {
"color": "#ABE9B3"
},
"string_other": {
"color": "#ABE9B3"
},
"number": {
"color": "#B5E8E0"
},
"generic_heading": {
"color": "#89DCEB",
"accent": ["bold"]
},
"generic_subheading": {
"color": "#89DCEB",
"accent": ["bold"]
},
"generic_deleted": {
"color": "#E8A2AF"
},
"generic_inserted": {
"color": "#ABE9B3"
},
"generic_error": {
"color": "#E8A2AF"
},
"generic_emph": {
"accent": ["italic"]
},
"generic_strong": {
"accent": ["bold"]
},
"generic_prompt": {
"color": "#988BA2",
"accent": ["bold"]
},
"generic_output": {
"color": "#F8BD96"
},
"generic_traceback": {
"color": "#E8A2AF"
},
"error": {
"color": "#F28FAD"
},
"background": {
"color": "#F8BD96",
"background": "#1E1E2E"
}
}
}

3
schema/testdata/minimal.json vendored 100644
View File

@ -0,0 +1,3 @@
{
"name": "minimal"
}