commit
47c700808a
|
@ -0,0 +1,3 @@
|
||||||
|
.idea/
|
||||||
|
/chromajson.exe
|
||||||
|
/chromajson
|
|
@ -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.
|
|
@ -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)
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
|
@ -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=
|
|
@ -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,
|
||||||
|
}
|
|
@ -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, " | ")
|
||||||
|
}
|
|
@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"name": "bad",
|
||||||
|
"style": {
|
||||||
|
"background": {
|
||||||
|
"color": "rainbow"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"background": "#ZZZ"
|
||||||
|
},
|
||||||
|
"foo": {},
|
||||||
|
"number_integer": {
|
||||||
|
"border": "hello, world"
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
"accent": ["bold", "bold"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"name": "minimal"
|
||||||
|
}
|
Reference in New Issue