Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

26 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Plingo

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.

Install

go get github.com/slavkiy/digreyt

Basic Error

arena := 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

Severity Types

digreyt.SeverityError
digreyt.SeverityWarning
digreyt.SeverityInfo
digreyt.SeveritySuccess

Warnings 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

Complex Go Errors

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:

Custom Theme

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(...).

Completely Custom Renderer

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 Helpers

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

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()

Automatic Translations

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>
  • key and de email 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)

Provider Settings

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{})

Notes

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/

About

Error diagnostic module on the go

Resources

Stars

Watchers

Forks

Packages

Contributors

Languages