Documentation
¶
Overview ¶
Package plugin is the wire protocol a TDL backend speaks, and the SDK for writing one.
Writing a backend ¶
A backend implements Backend. A plugin's main is Serve and nothing else:
func main() {
if err := plugin.Serve(myBackend{}); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
Build it as tdl-gen-<name> and put it on PATH.
What a request contains ¶
The prelude's declarations (string, List, Option, and the rest) are in the model untagged; filter by the filename in a declaration's position.
A node carries the directives of every target block, each naming its target; keep your own with Directives. A directive expanded from a class names that class.
`tdl ir --format json` shows what a model contains.
Returning files ¶
A backend returns contents, and tdl writes them. Paths are relative to Request.Out; an absolute one, or one climbing out with "..", is refused and nothing is written. A problem with the model belongs in Response diagnostics, not in an error from Backend.Generate.
What a plugin will not see ¶
The declaration-level directives of a dependency's target blocks; only their block-scope directives arrive, on the dependency's import. And class-scoped directives on types that satisfy a class only through a conditional instance: a directive on Auditable reaches Audited and not the Page<Audited> that satisfies Auditable through an instance.
The wire ¶
Messages are protobuf, framed with a varint length prefix, in both directions over one connection. tdl sends a Handshake first and a plugin answers with a HandshakeReply, refusing a version it does not support. Conn is the codec.
Example (Backend) ¶
A plugin's main is Serve and nothing else.
package main
import (
"context"
"fmt"
"strings"
"github.com/unstoppablemango/tdl/ir"
"github.com/unstoppablemango/tdl/plugin"
)
// shout is a complete backend: it declares what it understands and turns
// a model into files.
type shout struct{}
func (shout) Describe() plugin.Description {
return plugin.Description{
Name: "shout",
Version: "1.0.0",
Directives: []*plugin.DirectiveSpec{{
Name: "as",
MinArgs: 1,
MaxArgs: 1,
ArgKinds: []ir.LiteralKind{ir.LiteralKind_LITERAL_KIND_STRING},
}},
}
}
func (shout) Generate(_ context.Context, req *plugin.Request) (*plugin.Response, error) {
var b strings.Builder
for _, decl := range req.GetModel().GetDecls() {
name := decl.GetMeta().GetName()
// Keep only this target's directives.
for _, d := range plugin.Directives(req.GetTarget(), decl.GetDirectives()) {
if d.GetName() == "as" && len(d.GetArgs()) == 1 {
name = d.GetArgs()[0].GetText()
}
}
fmt.Fprintln(&b, strings.ToUpper(name))
}
return &plugin.Response{
Files: []*plugin.File{{Path: "shouted.txt", Content: []byte(b.String())}},
}, nil
}
// A plugin's main is Serve and nothing else.
func main() {
model := &ir.Model{
Decls: []*ir.Decl{
{Meta: &ir.Meta{Name: "Order"}},
{
Meta: &ir.Meta{Name: "Customer"},
Directives: []*ir.Directive{{Name: "as", Target: "shout", Args: []*ir.Literal{{Text: "buyer"}}}},
},
},
}
resp, err := shout{}.Generate(context.Background(), &plugin.Request{
Target: "shout",
Model: model,
})
if err != nil {
panic(err)
}
fmt.Print(string(resp.GetFiles()[0].GetContent()))
}
Output: ORDER BUYER
Index ¶
- Constants
- Variables
- func Directives(target string, all []*ir.Directive) []*ir.Directive
- func Serve(b Backend) error
- func ServeConn(ctx context.Context, b Backend, conn *Conn) error
- type Backend
- type Conn
- type Description
- type Diagnostic
- func (*Diagnostic) Descriptor() ([]byte, []int)deprecated
- func (x *Diagnostic) GetMessage() string
- func (x *Diagnostic) GetPosition() *ir.Position
- func (x *Diagnostic) GetSeverity() Severity
- func (*Diagnostic) ProtoMessage()
- func (x *Diagnostic) ProtoReflect() protoreflect.Message
- func (x *Diagnostic) Reset()
- func (x *Diagnostic) String() string
- type DirectiveSpec
- func (*DirectiveSpec) Descriptor() ([]byte, []int)deprecated
- func (x *DirectiveSpec) GetArgKinds() []ir.LiteralKind
- func (x *DirectiveSpec) GetMaxArgs() int32
- func (x *DirectiveSpec) GetMinArgs() int32
- func (x *DirectiveSpec) GetName() string
- func (x *DirectiveSpec) GetRepeatable() bool
- func (*DirectiveSpec) ProtoMessage()
- func (x *DirectiveSpec) ProtoReflect() protoreflect.Message
- func (x *DirectiveSpec) Reset()
- func (x *DirectiveSpec) String() string
- type Features
- type File
- type Handshake
- func (*Handshake) Descriptor() ([]byte, []int)deprecated
- func (x *Handshake) GetFramingVersion() int32
- func (x *Handshake) GetIrVersion() string
- func (x *Handshake) GetWatch() bool
- func (*Handshake) ProtoMessage()
- func (x *Handshake) ProtoReflect() protoreflect.Message
- func (x *Handshake) Reset()
- func (x *Handshake) String() string
- type HandshakeReply
- func (*HandshakeReply) Descriptor() ([]byte, []int)deprecated
- func (x *HandshakeReply) GetAccepted() bool
- func (x *HandshakeReply) GetDirectives() []*DirectiveSpec
- func (x *HandshakeReply) GetFeatures() *Features
- func (x *HandshakeReply) GetName() string
- func (x *HandshakeReply) GetRefusal() string
- func (x *HandshakeReply) GetVersion() string
- func (*HandshakeReply) ProtoMessage()
- func (x *HandshakeReply) ProtoReflect() protoreflect.Message
- func (x *HandshakeReply) Reset()
- func (x *HandshakeReply) String() string
- type Request
- func (*Request) Descriptor() ([]byte, []int)deprecated
- func (x *Request) GetDryRun() bool
- func (x *Request) GetModel() *ir.Model
- func (x *Request) GetOut() string
- func (x *Request) GetTarget() string
- func (*Request) ProtoMessage()
- func (x *Request) ProtoReflect() protoreflect.Message
- func (x *Request) Reset()
- func (x *Request) String() string
- type Response
- func (*Response) Descriptor() ([]byte, []int)deprecated
- func (x *Response) GetDiagnostics() []*Diagnostic
- func (x *Response) GetFiles() []*File
- func (x *Response) GetPost() []string
- func (*Response) ProtoMessage()
- func (x *Response) ProtoReflect() protoreflect.Message
- func (x *Response) Reset()
- func (x *Response) String() string
- type Severity
Examples ¶
Constants ¶
const FramingVersion = 1
FramingVersion is the wire framing this package implements: a varint byte count followed by the encoded message.
const IRVersion = "tdl.ir.v1"
IRVersion is the protobuf package the model is encoded with.
const MaxMessageSize = 64 << 20
MaxMessageSize bounds a single message. A length prefix over it is refused before anything is allocated.
Variables ¶
var ( Severity_name = map[int32]string{ 0: "SEVERITY_UNSPECIFIED", 1: "SEVERITY_ERROR", 2: "SEVERITY_WARNING", } Severity_value = map[string]int32{ "SEVERITY_UNSPECIFIED": 0, "SEVERITY_ERROR": 1, "SEVERITY_WARNING": 2, } )
Enum value maps for Severity.
var ErrTooLarge = errors.New("plugin: message exceeds the maximum size")
ErrTooLarge is returned when a length prefix exceeds MaxMessageSize.
var File_tdl_plugin_v1_plugin_proto protoreflect.FileDescriptor
Functions ¶
func Directives ¶
Directives returns the directives in all that belong to target. A node carries the directives of every target block in the model.
Types ¶
type Backend ¶
type Backend interface {
// Describe reports what this backend is and what directives it
// understands. tdl checks a target block against them before
// generating.
Describe() Description
// Generate returns the files for one request. It returns an error only
// when it cannot produce a response at all; a problem with the model
// belongs in Response.diagnostics.
Generate(ctx context.Context, req *Request) (*Response, error)
}
Backend turns a resolved model into files, in process or served as a plugin by Serve.
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is one end of a plugin connection. It is not safe for concurrent use.
func (*Conn) Recv ¶
Recv reads one message into m. A stream that ends between messages returns io.EOF; one that ends part way through a message returns io.ErrUnexpectedEOF.
type Description ¶
type Description struct {
Name string
Version string
Directives []*DirectiveSpec
Reuse bool
}
Description is what a backend says about itself.
func (Description) Reply ¶
func (d Description) Reply() *HandshakeReply
Reply turns a description into the handshake reply that carries it.
type Diagnostic ¶
type Diagnostic struct {
Severity Severity `protobuf:"varint,1,opt,name=severity,enum=tdl.plugin.v1.Severity" json:"severity,omitempty"`
Message string `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"`
Position *ir.Position `protobuf:"bytes,3,opt,name=position" json:"position,omitempty"`
// contains filtered or unexported fields
}
Diagnostic is a problem a backend found.
It carries a position rather than a node ID, because an ID alone does not say which table it indexes.
func (*Diagnostic) Descriptor
deprecated
func (*Diagnostic) Descriptor() ([]byte, []int)
Deprecated: Use Diagnostic.ProtoReflect.Descriptor instead.
func (*Diagnostic) GetMessage ¶
func (x *Diagnostic) GetMessage() string
func (*Diagnostic) GetPosition ¶
func (x *Diagnostic) GetPosition() *ir.Position
func (*Diagnostic) GetSeverity ¶
func (x *Diagnostic) GetSeverity() Severity
func (*Diagnostic) ProtoMessage ¶
func (*Diagnostic) ProtoMessage()
func (*Diagnostic) ProtoReflect ¶
func (x *Diagnostic) ProtoReflect() protoreflect.Message
func (*Diagnostic) Reset ¶
func (x *Diagnostic) Reset()
func (*Diagnostic) String ¶
func (x *Diagnostic) String() string
type DirectiveSpec ¶
type DirectiveSpec struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
MinArgs int32 `protobuf:"varint,2,opt,name=min_args,json=minArgs" json:"min_args,omitempty"`
// max_args is the largest number of arguments accepted, or -1 for any
// number.
MaxArgs int32 `protobuf:"varint,3,opt,name=max_args,json=maxArgs" json:"max_args,omitempty"`
// arg_kinds constrains each argument by position. An empty list accepts
// any kind, and a list shorter than the argument count constrains only
// the arguments it covers.
ArgKinds []ir.LiteralKind `protobuf:"varint,4,rep,packed,name=arg_kinds,json=argKinds,enum=tdl.ir.v1.LiteralKind" json:"arg_kinds,omitempty"`
// repeatable says several entries of this directive at the same
// specificity all reach the backend in source order. Otherwise they are
// an error.
Repeatable bool `protobuf:"varint,5,opt,name=repeatable" json:"repeatable,omitempty"`
// contains filtered or unexported fields
}
DirectiveSpec is the shape of one directive a backend accepts.
A directive in a target block that no spec declares is a warning and is passed through anyway.
func (*DirectiveSpec) Descriptor
deprecated
func (*DirectiveSpec) Descriptor() ([]byte, []int)
Deprecated: Use DirectiveSpec.ProtoReflect.Descriptor instead.
func (*DirectiveSpec) GetArgKinds ¶
func (x *DirectiveSpec) GetArgKinds() []ir.LiteralKind
func (*DirectiveSpec) GetMaxArgs ¶
func (x *DirectiveSpec) GetMaxArgs() int32
func (*DirectiveSpec) GetMinArgs ¶
func (x *DirectiveSpec) GetMinArgs() int32
func (*DirectiveSpec) GetName ¶
func (x *DirectiveSpec) GetName() string
func (*DirectiveSpec) GetRepeatable ¶ added in v0.2.10
func (x *DirectiveSpec) GetRepeatable() bool
func (*DirectiveSpec) ProtoMessage ¶
func (*DirectiveSpec) ProtoMessage()
func (*DirectiveSpec) ProtoReflect ¶
func (x *DirectiveSpec) ProtoReflect() protoreflect.Message
func (*DirectiveSpec) Reset ¶
func (x *DirectiveSpec) Reset()
func (*DirectiveSpec) String ¶
func (x *DirectiveSpec) String() string
type Features ¶
type Features struct {
// reuse says the plugin can serve more than one request on a
// connection. It must treat each as independent.
Reuse bool `protobuf:"varint,1,opt,name=reuse" json:"reuse,omitempty"`
// contains filtered or unexported fields
}
Features are the optional parts of the protocol a backend supports.
func (*Features) Descriptor
deprecated
func (*Features) ProtoMessage ¶
func (*Features) ProtoMessage()
func (*Features) ProtoReflect ¶
func (x *Features) ProtoReflect() protoreflect.Message
type File ¶
type File struct {
Path string `protobuf:"bytes,1,opt,name=path" json:"path,omitempty"`
Content []byte `protobuf:"bytes,2,opt,name=content" json:"content,omitempty"`
// contains filtered or unexported fields
}
File is one generated file, with a path relative to Request.out. An absolute path, or one containing "..", is an error and nothing is written.
func (*File) Descriptor
deprecated
func (*File) GetContent ¶
func (*File) ProtoMessage ¶
func (*File) ProtoMessage()
func (*File) ProtoReflect ¶
func (x *File) ProtoReflect() protoreflect.Message
type Handshake ¶
type Handshake struct {
// framing_version is the wire framing this connection uses. Version 1 is
// a varint length prefix followed by an encoded message, in both
// directions.
FramingVersion int32 `protobuf:"varint,1,opt,name=framing_version,json=framingVersion" json:"framing_version,omitempty"`
// ir_version is the protobuf package the model is encoded with, such as
// "tdl.ir.v1". Within a version, field numbers are never reused and new
// fields are additive.
IrVersion string `protobuf:"bytes,2,opt,name=ir_version,json=irVersion" json:"ir_version,omitempty"`
// watch says this connection may serve more than one request.
Watch bool `protobuf:"varint,3,opt,name=watch" json:"watch,omitempty"`
// contains filtered or unexported fields
}
Handshake is what tdl sends first, before any request.
A plugin that cannot read what is coming refuses here instead of ignoring fields it does not know and emitting wrong code.
func (*Handshake) Descriptor
deprecated
func (*Handshake) GetFramingVersion ¶
func (*Handshake) GetIrVersion ¶
func (*Handshake) ProtoMessage ¶
func (*Handshake) ProtoMessage()
func (*Handshake) ProtoReflect ¶
func (x *Handshake) ProtoReflect() protoreflect.Message
type HandshakeReply ¶
type HandshakeReply struct {
Accepted bool `protobuf:"varint,1,opt,name=accepted" json:"accepted,omitempty"`
// refusal says what the plugin needed, when accepted is false. It should
// name both versions.
Refusal string `protobuf:"bytes,2,opt,name=refusal" json:"refusal,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"`
Version string `protobuf:"bytes,4,opt,name=version" json:"version,omitempty"`
// directives are what this backend understands. tdl checks the target
// block against them before generating.
Directives []*DirectiveSpec `protobuf:"bytes,5,rep,name=directives" json:"directives,omitempty"`
Features *Features `protobuf:"bytes,6,opt,name=features" json:"features,omitempty"`
// contains filtered or unexported fields
}
HandshakeReply is the plugin's answer.
func (*HandshakeReply) Descriptor
deprecated
func (*HandshakeReply) Descriptor() ([]byte, []int)
Deprecated: Use HandshakeReply.ProtoReflect.Descriptor instead.
func (*HandshakeReply) GetAccepted ¶
func (x *HandshakeReply) GetAccepted() bool
func (*HandshakeReply) GetDirectives ¶
func (x *HandshakeReply) GetDirectives() []*DirectiveSpec
func (*HandshakeReply) GetFeatures ¶
func (x *HandshakeReply) GetFeatures() *Features
func (*HandshakeReply) GetName ¶
func (x *HandshakeReply) GetName() string
func (*HandshakeReply) GetRefusal ¶
func (x *HandshakeReply) GetRefusal() string
func (*HandshakeReply) GetVersion ¶
func (x *HandshakeReply) GetVersion() string
func (*HandshakeReply) ProtoMessage ¶
func (*HandshakeReply) ProtoMessage()
func (*HandshakeReply) ProtoReflect ¶
func (x *HandshakeReply) ProtoReflect() protoreflect.Message
func (*HandshakeReply) Reset ¶
func (x *HandshakeReply) Reset()
func (*HandshakeReply) String ¶
func (x *HandshakeReply) String() string
type Request ¶
type Request struct {
// target names the block being served. A backend keeps the directives
// tagged with this name.
Target string `protobuf:"bytes,1,opt,name=target" json:"target,omitempty"`
// model is one package, with the prelude's declarations merged into it.
Model *ir.Model `protobuf:"bytes,2,opt,name=model" json:"model,omitempty"`
// out is the directory the response's paths are relative to.
Out string `protobuf:"bytes,3,opt,name=out" json:"out,omitempty"`
// dry_run says tdl will diff the response against disk rather than
// write it. A backend may skip work that cannot affect the files; ignoring
// the flag is correct.
DryRun bool `protobuf:"varint,4,opt,name=dry_run,json=dryRun" json:"dry_run,omitempty"`
// contains filtered or unexported fields
}
Request is everything a backend needs for one generation. Nothing travels in argv or environment variables.
func (*Request) Descriptor
deprecated
func (*Request) ProtoMessage ¶
func (*Request) ProtoMessage()
func (*Request) ProtoReflect ¶
func (x *Request) ProtoReflect() protoreflect.Message
type Response ¶
type Response struct {
Files []*File `protobuf:"bytes,1,rep,name=files" json:"files,omitempty"`
// post names commands to run over the written files, by name only. The
// project decides what a name maps to; a name it has not declared is
// skipped with a warning.
Post []string `protobuf:"bytes,2,rep,name=post" json:"post,omitempty"`
Diagnostics []*Diagnostic `protobuf:"bytes,3,rep,name=diagnostics" json:"diagnostics,omitempty"`
// contains filtered or unexported fields
}
Response is what a backend returns: file contents, which tdl writes, so tdl enforces path confinement, --verify, and --clean.
func (*Response) Descriptor
deprecated
func (*Response) GetDiagnostics ¶
func (x *Response) GetDiagnostics() []*Diagnostic
func (*Response) ProtoMessage ¶
func (*Response) ProtoMessage()
func (*Response) ProtoReflect ¶
func (x *Response) ProtoReflect() protoreflect.Message
type Severity ¶
type Severity int32
Severity is how much a diagnostic matters.
func (Severity) Descriptor ¶
func (Severity) Descriptor() protoreflect.EnumDescriptor
func (Severity) EnumDescriptor
deprecated
func (Severity) Number ¶
func (x Severity) Number() protoreflect.EnumNumber
func (Severity) Type ¶
func (Severity) Type() protoreflect.EnumType