digreyt is a Go diagnostic renderer for compilers, parsers, linters and CLI
tools. It can render errors, warnings, info messages and success diagnostics.
The default terminal renderer is built on top of
github.com/slavkiy/colorista. You can keep
the default layout, customize only colors/symbols, or replace the whole renderer
with your own output format.
go get github.com/slavkiy/digreytarena := digreyt.New(source)
arena.Add(digreyt.Error{
CodeName: "ParseError",
Message: "unexpected token",
Arrow: "expected expression",
Severity: digreyt.SeverityError,
IsShowSnippet: true,
Pos: digreyt.Position{
FileName: "main.candy",
Line: 2,
Column: 7,
},
Start: 6,
End: 11,
})
_ = arena.Render(os.Stderr)Screenshot-style output:
× ParseError: unexpected token
──> main.candy 2:7
1 │ fn main() {
2 │ print()
│ ^^^^^ expected expression
digreyt.SeverityError
digreyt.SeverityWarning
digreyt.SeverityInfo
digreyt.SeveritySuccessWarnings can look different from errors:
! ShadowedName: variable shadows previous declaration
──> main.candy 8:5
7 │ let name = "old"
8 │ let name = "new"
│ ^^^^ rename this binding
Info diagnostics can use their own symbol and color:
i TypeInfo: inferred type is string
──> main.candy 3:12
3 │ let title = "Candy"
│ ^^^^^^^ string
Success diagnostics are useful for CLI tools that want to use the same diagnostic channel for positive results:
✓ BuildOk: compiled successfully
Module: candy/main
• generated bytecode
• no fatal diagnostics
Any Go error can become a rich diagnostic by implementing AsDiagnostic.
Wrapped errors work because AddError uses errors.As.
type ParserError struct {
Message string
Span digreyt.Span
}
func (e ParserError) Error() string {
return e.Message
}
func (e ParserError) AsDiagnostic() digreyt.Error {
return digreyt.Error{
CodeName: "ParserError",
Message: e.Message,
Severity: digreyt.SeverityError,
IsShowSnippet: true,
Pos: e.Span.Pos,
Start: e.Span.Start,
End: e.Span.End,
}
}
arena.AddError(fmt.Errorf("parse failed: %w", ParserError{Message: "bad call"}))Plain errors are still accepted:
arena.AddError(errors.New("plain failure"))They become:
× Error: plain failure
Module:
Use RenderTheme when the layout is fine, but the visual language should
change.
theme := digreyt.DefaultRenderTheme()
theme.DescriptionBullet = "- "
theme.SeverityViews[digreyt.SeverityWarning] = digreyt.SeverityView{
Symbol: "warn",
Label: "warning",
SymbolStyles: []colorista.Style{colorista.Bold, colorista.BrightMagenta},
CodeStyles: []colorista.Style{colorista.Bold, colorista.BrightMagenta},
CaretStyles: []colorista.Style{colorista.Bold, colorista.BrightMagenta},
ArrowStyles: []colorista.Style{colorista.BrightMagenta},
BulletStyles: []colorista.Style{colorista.BrightMagenta},
}
arena.Renderer = digreyt.NewColorRenderer(theme)Screenshot-style output:
warn ShadowedName: variable shadows previous declaration
──> main.candy 8:5
7 │ let name = "old"
8 │ let name = "new"
│ ^^^^ rename this binding
You can customize:
ContextLines: how many previous source lines are shown.LocationArrow: marker before file location.ModuleLabel: marker for module-level diagnostics.DescriptionBullet: bullet before description lines.MutedStyles: style for line numbers and separators.LocationStyles: style for file, line and column.SeverityViews: symbol, label, caret, arrow, code and description styles for each severity.
All style fields use colorista.Style, for example colorista.Bold,
colorista.BrightRed, colorista.Rgb(...), colorista.ANSI256(...).
Use RendererFunc when you want a totally different view: compact logs, JSON,
LSP payloads, snapshots for tests, or a UI protocol.
renderer := digreyt.RendererFunc(func(w io.Writer, source string, err digreyt.Error) error {
_, writeErr := fmt.Fprintf(
w,
`{"severity":%q,"code":%q,"message":%q}`+"\n",
err.Severity.String(),
err.CodeName,
err.Message,
)
return writeErr
})
_ = arena.PrintWith(os.Stdout, renderer)Screenshot-style output:
{"severity":"warning","code":"ShadowedName","message":"variable shadows previous declaration"}arena.HasDiagnostics() // any diagnostic
arena.HasErrors() // compatibility alias for any diagnostic
arena.HasSeverity(digreyt.SeverityWarning)
arena.HasFatalErrors() // true only for SeverityError
arena.Clear()Print keeps the old fire-and-forget API:
arena.Print(os.Stderr)Render returns writer/render errors:
if err := arena.Render(os.Stderr); err != nil {
return err
}Manual translations are still deterministic and do not touch the network.
translate.SetLanguage("ru")
err := digreyt.Error{
CodeName: "ParseError",
MessageTranslations: translate.Translations{
{Language: "eng", Text: "unexpected token"},
{Language: "ru", Text: "неожиданный токен"},
},
}
err = err.Localize()The translate package can translate missing text automatically. The default
provider is MyMemory, and you can switch to Google, DeepL, LibreTranslate, or a
fallback chain.
Built-in providers:
translate.MyMemoryTranslator: free unauthenticated MyMemory API.translate.GoogleTranslator: Google Cloud Translation Basic v2.translate.DeepLTranslator: DeepL API Free by default.translate.LibreTranslateTranslator: LibreTranslate managed or self-hosted.translate.TranslatorChain: tries several providers in order.
Default MyMemory behavior:
- API endpoint:
https://api.mymemory.translated.net/get - Request shape:
q=<text>&langpair=<source>|<target> keyanddeemail are optional.- MyMemory documents a 500-byte limit for
q.
translate.SetLanguage("ru")
err := digreyt.Error{
CodeName: "ParseError",
MessageTranslations: translate.Translations{
{Language: "eng", Text: "unexpected token"},
},
ArrowTranslations: translate.Translations{
{Language: "eng", Text: "expected expression"},
},
}
localized, translateErr := err.LocalizeAuto(context.Background())
if translateErr != nil {
return translateErr
}
arena.Add(localized)
_ = arena.Render(os.Stderr)You can render and auto-localize the whole arena in one call:
translate.SetLanguage("ru")
_ = arena.RenderAuto(context.Background(), os.Stderr)MyMemory:
provider := translate.NewMyMemoryTranslator()
provider.Email = "dev@example.com" // optional MyMemory `de` parameter
provider.Key = "private-key" // optional MyMemory key
translate.SetAutoTranslator(provider)MyMemory can also use only translation-memory matches:
provider := translate.NewMyMemoryTranslator()
provider.MachineTranslation = false
translate.SetAutoTranslator(provider)Google Cloud Translation Basic v2 with API key:
provider := translate.NewGoogleTranslator(os.Getenv("GOOGLE_TRANSLATE_KEY"))
translate.SetAutoTranslator(provider)Google with OAuth bearer token instead of API key:
provider := &translate.GoogleTranslator{
BearerToken: os.Getenv("GOOGLE_TRANSLATE_TOKEN"),
Format: "text",
}
translate.SetAutoTranslator(provider)Google request shape:
POST https://translation.googleapis.com/language/translate/v2?q=...&source=en&target=ru&format=text&key=...
DeepL API Free with token:
provider := translate.NewDeepLTranslator(os.Getenv("DEEPL_AUTH_KEY"))
translate.SetAutoTranslator(provider)DeepL API Pro with token:
provider := translate.NewDeepLProTranslator(os.Getenv("DEEPL_AUTH_KEY"))
translate.SetAutoTranslator(provider)DeepL request shape:
POST https://api-free.deepl.com/v2/translate
Authorization: DeepL-Auth-Key <token>
Content-Type: application/json
{"text":["unexpected token"],"source_lang":"EN","target_lang":"RU"}
LibreTranslate managed or self-hosted:
provider := translate.NewLibreTranslateTranslator(
"http://localhost:5000/translate",
"", // optional api_key, depends on the instance
)
translate.SetAutoTranslator(provider)LibreTranslate request shape:
POST http://localhost:5000/translate
Content-Type: application/json
{"q":"unexpected token","source":"en","target":"ru","format":"text"}
Fallback chain:
provider := translate.NewTranslatorChain(
translate.NewGoogleTranslator(os.Getenv("GOOGLE_TRANSLATE_KEY")),
translate.NewDeepLTranslator(os.Getenv("DEEPL_AUTH_KEY")),
translate.NewLibreTranslateTranslator(os.Getenv("LIBRE_TRANSLATE_URL"), os.Getenv("LIBRE_TRANSLATE_KEY")),
translate.NewMyMemoryTranslator(),
)
translate.SetAutoTranslator(provider)Use a custom, local, LibreTranslate or mocked provider by implementing
AutoTranslator:
type LocalTranslator struct{}
func (LocalTranslator) Translate(ctx context.Context, from, to, text string) (string, error) {
return "[" + to + "] " + text, nil
}
translate.SetAutoTranslator(LocalTranslator{})Automatic translation is intentionally explicit. Resolve and Localize never
call the network. Use ResolveAuto, LocalizeAuto or RenderAuto only when
network translation is acceptable for your CLI or tool.
MyMemory API docs: https://mymemory.translated.net/doc/spec.php
Google Translation Basic v2 docs: https://docs.cloud.google.com/translate/docs/reference/rest/v2/translate
DeepL translate API docs: https://developers.deepl.com/api-reference/translate
LibreTranslate API docs: https://docs.libretranslate.com/guides/api_usage/
