diff --git a/cmd/parka/cmds/examples.go b/cmd/parka/cmds/examples.go index be682ff..ec334fc 100644 --- a/cmd/parka/cmds/examples.go +++ b/cmd/parka/cmds/examples.go @@ -6,7 +6,7 @@ import ( "github.com/go-go-golems/glazed/pkg/cmds" "github.com/go-go-golems/glazed/pkg/cmds/layers" "github.com/go-go-golems/glazed/pkg/cmds/parameters" - "github.com/go-go-golems/glazed/pkg/processor" + "github.com/go-go-golems/glazed/pkg/middlewares" "github.com/go-go-golems/glazed/pkg/types" ) @@ -109,7 +109,7 @@ func (e *ExampleCommand) Run( ctx context.Context, parsedLayers map[string]*layers.ParsedParameterLayer, ps map[string]interface{}, - gp processor.TableProcessor, + gp middlewares.Processor, ) error { obj := types.NewRow( types.MRP("test", ps["test"]), @@ -149,6 +149,11 @@ func (e *ExampleCommand) Description() *cmds.CommandDescription { return e.description } -func (e *ExampleCommand) RunFromParka(c *gin.Context, parsedLayers map[string]*layers.ParsedParameterLayer, ps map[string]interface{}, gp *processor.GlazeProcessor) error { +func (e *ExampleCommand) RunFromParka( + c *gin.Context, + parsedLayers map[string]*layers.ParsedParameterLayer, + ps map[string]interface{}, + gp middlewares.Processor, +) error { return e.Run(c, parsedLayers, ps, gp) } diff --git a/cmd/parka/cmds/serve.go b/cmd/parka/cmds/serve.go index 095cf42..a839df2 100644 --- a/cmd/parka/cmds/serve.go +++ b/cmd/parka/cmds/serve.go @@ -117,10 +117,7 @@ var LsServerCmd = &cobra.Command{ cobra.CheckErr(err) } - err = gp.Finalize(ctx) - cobra.CheckErr(err) - - err = gp.OutputFormatter().Output(ctx, gp.GetTable(), os.Stdout) + err = gp.RunTableMiddlewares(ctx) cobra.CheckErr(err) }, } diff --git a/pkg/glazed/handler.go b/pkg/glazed/handler.go index 234cfb2..d60b2a3 100644 --- a/pkg/glazed/handler.go +++ b/pkg/glazed/handler.go @@ -4,25 +4,26 @@ import ( "bytes" "github.com/gin-gonic/gin" "github.com/go-go-golems/glazed/pkg/cmds" + "github.com/go-go-golems/glazed/pkg/formatters" "github.com/go-go-golems/glazed/pkg/formatters/json" - "github.com/go-go-golems/glazed/pkg/processor" + "github.com/go-go-golems/glazed/pkg/middlewares" + "github.com/go-go-golems/glazed/pkg/middlewares/table" "github.com/go-go-golems/glazed/pkg/settings" "github.com/go-go-golems/parka/pkg/glazed/parser" + "golang.org/x/sync/errgroup" "io" "net/http" "os" ) -// CreateProcessorFunc is a simple func type to create a cmds.GlazeProcessor -// and formatters.OutputFormatter out of a CommandContext. -// -// This is so that we can create a processor that is configured based on the input -// data provided in CommandContext. For example, the user might want to request a specific response -// format through a query argument or through a header. -type CreateProcessorFunc func(c *gin.Context, pc *CommandContext) ( - processor.TableProcessor, - error, -) +type GinOutputFormatter interface { + Output(w io.Writer) error + RegisterMiddlewares(p *middlewares.TableProcessor) error +} + +type GinOutputFormatterFactory interface { + CreateOutputFormatter(c *gin.Context, pc *CommandContext) (GinOutputFormatter, error) +} // HandleOptions groups all the settings for a gin handler that handles a glazed command. type HandleOptions struct { @@ -43,7 +44,7 @@ type HandleOptions struct { Handlers []CommandHandlerFunc // CreateProcessor takes a gin.Context and a CommandContext and returns a processor.TableProcessor (and a content-type) - CreateProcessor CreateProcessorFunc + OutputFormatterFactory GinOutputFormatterFactory // This is the actual gin output writer Writer io.Writer @@ -53,10 +54,10 @@ type HandleOption func(*HandleOptions) func (h *HandleOptions) Copy(options ...HandleOption) *HandleOptions { ret := &HandleOptions{ - ParserOptions: h.ParserOptions, - Handlers: h.Handlers, - CreateProcessor: h.CreateProcessor, - Writer: h.Writer, + ParserOptions: h.ParserOptions, + Handlers: h.Handlers, + OutputFormatterFactory: h.OutputFormatterFactory, + Writer: h.Writer, } for _, option := range options { @@ -92,26 +93,20 @@ func WithWriter(w io.Writer) HandleOption { } } -func WithCreateProcessor(createProcessor CreateProcessorFunc) HandleOption { - return func(o *HandleOptions) { - o.CreateProcessor = createProcessor - } -} - func CreateJSONProcessor(_ *gin.Context, pc *CommandContext) ( - processor.TableProcessor, + *middlewares.TableProcessor, error, ) { l, ok := pc.ParsedLayers["glazed"] l.Parameters["output"] = "json" - var gp *processor.GlazeProcessor + var gp *middlewares.TableProcessor var err error if ok { - gp, err = settings.SetupProcessor(l.Parameters) + gp, err = settings.SetupTableProcessor(l.Parameters) } else { - gp, err = settings.SetupProcessor(map[string]interface{}{ + gp, err = settings.SetupTableProcessor(map[string]interface{}{ "output": "json", }) } @@ -146,7 +141,7 @@ func GinHandleGlazedCommand( // running the GlazeCommand, and then returning the output file as an attachment. // This usually requires the caller to provide a temporary file path. // -// TODO(manuel, 2023-06-22) Now that OutputFormatter renders directly into a io.Writer, +// TODO(manuel, 2023-06-22) Now that TableOutputFormatter renders directly into a io.Writer, // I don't think we need all this anymore, we just need to set the relevant header. func GinHandleGlazedCommandWithOutputFile( cmd cmds.GlazeCommand, @@ -198,60 +193,83 @@ func runGlazeCommand(c *gin.Context, cmd cmds.GlazeCommand, opts *HandleOptions) } } - var gp processor.TableProcessor - + var gp *middlewares.TableProcessor var err error - if opts.CreateProcessor != nil { - // TODO(manuel, 2023-03-02) We might want to switch on the requested content type here too - // This would be done by passing in a handler that configures the glazed layer accordingly. - gp, err = opts.CreateProcessor(c, pc) + + glazedLayer := pc.ParsedLayers["glazed"] + + if glazedLayer != nil { + gp, err = settings.SetupTableProcessor(glazedLayer.Parameters) + if err != nil { + return err + } } else { - gp, err = SetupProcessor(pc) + gp = middlewares.NewTableProcessor() } - if err != nil { - return err + + var writer io.Writer = c.Writer + if opts.Writer != nil { + writer = opts.Writer } - of := gp.OutputFormatter() - contentType := of.ContentType() + if opts.OutputFormatterFactory != nil { + of, err := opts.OutputFormatterFactory.CreateOutputFormatter(c, pc) + if err != nil { + return err + } + // remove table middlewares to do streaming rows + gp.ReplaceTableMiddleware() - if opts.Writer == nil { - c.Writer.Header().Set("Content-Type", contentType) - } + // create rowOutputChannelMiddleware here? But that's actually a responsibility of the OutputFormatterFactory. + // we need to create these before running the command, and we need to figure out a way to get the Columns. - err = cmd.Run(c, pc.ParsedLayers, pc.ParsedParameters, gp) - if err != nil { - return err + err = of.RegisterMiddlewares(gp) + if err != nil { + return err + } + + eg := &errgroup.Group{} + eg.Go(func() error { + return cmd.Run(c, pc.ParsedLayers, pc.ParsedParameters, gp) + }) + + eg.Go(func() error { + // we somehow need to pass the channels to the OutputFormatterFactory + return of.Output(writer) + }) + + // no cancellation on error? + + return eg.Wait() } - var writer io.Writer = c.Writer - if opts.Writer != nil { - writer = opts.Writer + // here we run a normal full table render + var of formatters.TableOutputFormatter + if glazedLayer != nil { + of, err = settings.SetupTableOutputFormatter(glazedLayer.Parameters) + if err != nil { + return err + } + } else { + of = json.NewOutputFormatter( + json.WithOutputIndividualRows(true), + ) } - err = of.Output(c, gp.GetTable(), writer) - if err != nil { - return err + if opts.Writer == nil { + c.Writer.Header().Set("Content-Type", of.ContentType()) } - return err -} + gp.AddTableMiddleware(table.NewOutputMiddleware(of, writer)) -// SetupProcessor creates a new cmds.GlazeProcessor. It uses the parsed layer glazed if present, and return -// a simple JsonOutputFormatter and standard glazed processor otherwise. -func SetupProcessor(pc *CommandContext, options ...processor.GlazeProcessorOption) (*processor.GlazeProcessor, error) { - l, ok := pc.ParsedLayers["glazed"] - if ok { - gp, err := settings.SetupProcessor(l.Parameters) - return gp, err + err = cmd.Run(c, pc.ParsedLayers, pc.ParsedParameters, gp) + if err != nil { + return err } - of := json.NewOutputFormatter( - json.WithOutputIndividualRows(true), - ) - gp, err := processor.NewGlazeProcessor(of, options...) + err = gp.RunTableMiddlewares(c) if err != nil { - return nil, err + return err } - return gp, nil + return nil } diff --git a/pkg/handlers/command-dir/command-dir.go b/pkg/handlers/command-dir/command-dir.go index 1c55606..76d5597 100644 --- a/pkg/handlers/command-dir/command-dir.go +++ b/pkg/handlers/command-dir/command-dir.go @@ -480,13 +480,19 @@ func (cd *CommandDirHandler) Serve(server *parka.Server, path string) error { // See https://github.com/go-go-golems/sqleton/issues/162 _ = cd.IndexTemplateName + dt := &datatables.DataTables{ + Command: sqlCommand.Description(), + Links: links, + BasePath: path, + JSRendering: true, + UseDataTables: false, + AdditionalData: cd.AdditionalData, + } + dataTablesProcessorFunc := datatables.NewDataTablesCreateOutputProcessorFunc( cd.TemplateLookup, cd.TemplateName, - datatables.WithLinks(links...), - datatables.WithJSRendering(), - datatables.WithAdditionalData(cd.AdditionalData), - datatables.WithBasePath(path), + dt, ) handle := server.HandleSimpleQueryCommand( diff --git a/pkg/render/datatables/datatables.go b/pkg/render/datatables/datatables.go index dd547f8..d2a6950 100644 --- a/pkg/render/datatables/datatables.go +++ b/pkg/render/datatables/datatables.go @@ -1,15 +1,14 @@ package datatables import ( - "context" "embed" "github.com/gin-gonic/gin" "github.com/go-go-golems/glazed/pkg/cmds" "github.com/go-go-golems/glazed/pkg/formatters" "github.com/go-go-golems/glazed/pkg/formatters/json" - "github.com/go-go-golems/glazed/pkg/formatters/table" - "github.com/go-go-golems/glazed/pkg/processor" - "github.com/go-go-golems/glazed/pkg/settings" + table_formatter "github.com/go-go-golems/glazed/pkg/formatters/table" + "github.com/go-go-golems/glazed/pkg/middlewares" + "github.com/go-go-golems/glazed/pkg/middlewares/row" "github.com/go-go-golems/glazed/pkg/types" "github.com/go-go-golems/parka/pkg/glazed" "github.com/go-go-golems/parka/pkg/render" @@ -56,94 +55,6 @@ type DataTables struct { AdditionalData map[string]interface{} } -type DataTablesOutputFormatter struct { - *render.HTMLTemplateOutputFormatter - dataTablesData *DataTables -} - -type DataTablesOutputFormatterOption func(*DataTablesOutputFormatter) - -func WithCommand(cmd *cmds.CommandDescription) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.Command = cmd - } -} - -func WithLongDescription(desc string) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.LongDescription = desc - } -} - -func WithReplaceAdditionalData(data map[string]interface{}) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.AdditionalData = data - } -} - -func WithAdditionalData(data map[string]interface{}) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - if d.dataTablesData.AdditionalData == nil { - d.dataTablesData.AdditionalData = data - } else { - for k, v := range data { - d.dataTablesData.AdditionalData[k] = v - } - } - } -} - -// WithJSRendering enables JS rendering for the DataTables renderer. -// This means that we will render the table into the toplevel element -// `tableData` in javascript, and not call the parent output formatter -func WithJSRendering() DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.JSRendering = true - } -} - -func WithLayout(l *layout.Layout) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.Layout = l - } -} - -func WithLinks(links ...layout.Link) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.Links = links - } -} - -func WithBasePath(path string) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.BasePath = path - } -} - -func WithAppendLinks(links ...layout.Link) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.Links = append(d.dataTablesData.Links, links...) - } -} - -func WithPrependLinks(links ...layout.Link) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.Links = append(links, d.dataTablesData.Links...) - } -} - -func WithColumns(columns ...string) DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.Columns = columns - } -} - -func WithUseDataTables() DataTablesOutputFormatterOption { - return func(d *DataTablesOutputFormatter) { - d.dataTablesData.UseDataTables = true - } -} - //go:embed templates/* var templateFS embed.FS @@ -159,120 +70,113 @@ func NewDataTablesLookupTemplate() *render.LookupTemplateFromFS { return l } -func NewDataTablesOutputFormatter( +func (dt *DataTables) Clone() *DataTables { + return &DataTables{ + Command: dt.Command, + LongDescription: dt.LongDescription, + Layout: dt.Layout, + Links: dt.Links, + BasePath: dt.BasePath, + JSStream: dt.JSStream, + HTMLStream: dt.HTMLStream, + JSRendering: dt.JSRendering, + Columns: dt.Columns, + UseDataTables: dt.UseDataTables, + AdditionalData: dt.AdditionalData, + } +} + +type OutputFormatter struct { + t *template.Template + dt *DataTables + // rowC is the channel where the rows are sent to. They will need to get converted + // to template.JS or template.HTML before being sent to either + rowC chan string + // columnsC is the channel where the column names are sent to. Since the row.ColumnsChannelMiddleware + // instance that sends columns to this channel is running before the row firmware, we should be careful + // about not blocking. Potentially, this could be done by starting a goroutine in the middleware, + // since we have a context there, and there is no need to block the middleware processing. + columnsC chan []types.FieldName +} + +func NewOutputFormatter( t *template.Template, - of *table.OutputFormatter, - options ...DataTablesOutputFormatterOption, -) *DataTablesOutputFormatter { - ret := &DataTablesOutputFormatter{ - HTMLTemplateOutputFormatter: render.NewHTMLTemplateOutputFormatter(t, of), - dataTablesData: &DataTables{}, - } + dt *DataTables) *OutputFormatter { - for _, option := range options { - option(ret) - } + // make the NewOutputChannelMiddleware generic to send string/template.JS/template.HTML over the wire + rowC := make(chan string, 100) + + // make a channel to receive column names + columnsC := make(chan []types.FieldName, 10) - return ret + // we need to make sure that we are closing the channel correctly. Should middlewares have a Close method? + // that actually sounds reasonable + return &OutputFormatter{ + t: t, + dt: dt, + rowC: rowC, + columnsC: columnsC, + } } -func (d *DataTablesOutputFormatter) ContentType() string { - return "text/html; charset=utf-8" +type OutputFormatterFactory struct { + TemplateName string + Lookup render.TemplateLookup + DataTables *DataTables } -func (d *DataTablesOutputFormatter) Output(ctx context.Context, table *types.Table, w io.Writer) error { - dt := d.dataTablesData - if dt.JSRendering { - jsonOutputFormatter := json.NewOutputFormatter() - dt.JSStream = formatters.StartFormatIntoChannel[template.JS](ctx, table, jsonOutputFormatter) - } else { - dt.HTMLStream = formatters.StartFormatIntoChannel[template.HTML](ctx, table, d.OutputFormatter) +func (dtoff *OutputFormatterFactory) CreateOutputFormatter( + c *gin.Context, + pc *glazed.CommandContext, +) (*OutputFormatter, error) { + // Lookup template on every request, not up front. That way, templates can be reloaded without recreating the gin + // server. + t, err := dtoff.Lookup.Lookup(dtoff.TemplateName) + if err != nil { + return nil, err } - // TODO(manuel, 2023-06-20) We need to properly pass the columns here, which can't be set upstream - // since we already pass in the JSStream here and we keep it, I think we are better off cloning the - // DataTables struct, or even separating it out to make d.dataTablesData immutable and just contain the - // toplevel config. - if table != nil { - dt.Columns = table.Columns + layout_, err := layout.ComputeLayout(pc) + if err != nil { + return nil, err } - err := d.HTMLTemplateOutputFormatter.Template.Execute(w, dt) + description := pc.Cmd.Description() + dt_ := dtoff.DataTables.Clone() + longHTML, err := render.RenderMarkdownToHTML(description.Long) if err != nil { - return err + return nil, err } - return nil -} - -// NewDataTablesCreateOutputProcessorFunc creates a glazed.CreateProcessorFunc based on a TemplateLookup -// and a template name. -func NewDataTablesCreateOutputProcessorFunc( - lookup render.TemplateLookup, - templateName string, - options ...DataTablesOutputFormatterOption, -) glazed.CreateProcessorFunc { - return func(c *gin.Context, pc *glazed.CommandContext) ( - processor.TableProcessor, - error, - ) { - // Lookup template on every request, not up front. That way, templates can be reloaded without recreating the gin - // server. - t, err := lookup.Lookup(templateName) - if err != nil { - return nil, err - } - - l, ok := pc.ParsedLayers["glazed"] - var gp *processor.GlazeProcessor - - if ok { - l.Parameters["output"] = "table" - l.Parameters["table-format"] = "html" - - gp, err = settings.SetupProcessor(l.Parameters) - } else { - gp, err = settings.SetupProcessor(map[string]interface{}{ - "output": "table", - "table-format": "html", - }) - } - - if err != nil { - return nil, err - } + dt_.LongDescription = longHTML + dt_.Layout = layout_ - layout_, err := layout.ComputeLayout(pc) - if err != nil { - return nil, err - } + return NewOutputFormatter(t, dtoff.DataTables), nil +} - description := pc.Cmd.Description() +func (dt *OutputFormatter) RegisterMiddlewares( + m *middlewares.TableProcessor, +) error { + var of formatters.RowOutputFormatter + if dt.dt.JSRendering { + of = json.NewOutputFormatter() + dt.dt.JSStream = make(chan template.JS, 100) + } else { + of = table_formatter.NewOutputFormatter("html") + dt.dt.HTMLStream = make(chan template.HTML, 100) + } - longHTML, err := render.RenderMarkdownToHTML(description.Long) - if err != nil { - return nil, err - } + // TODO add a "get columns" middleware that can be used to get the columns from the table + // over a single channel that we can wait on in the render call - options_ := []DataTablesOutputFormatterOption{ - WithCommand(description), - WithLongDescription(longHTML), - WithLayout(layout_), - } - options_ = append(options_, options...) + m.AddRowMiddleware(row.NewOutputChannelMiddleware(of, dt.rowC)) - of := NewDataTablesOutputFormatter( - t, - gp.OutputFormatter().(*table.OutputFormatter), - options_..., - ) + return nil +} - gp2, err := processor.NewGlazeProcessor(of) - if err != nil { - return nil, err - } +func (dt *OutputFormatter) Output(c *gin.Context, pc *glazed.CommandContext, w io.Writer) error { + // clear all table middlewares because we are streaming using a row output middleware - return gp2, nil - } + return nil } diff --git a/pkg/render/html.go b/pkg/render/html.go index 893885b..666675f 100644 --- a/pkg/render/html.go +++ b/pkg/render/html.go @@ -1,12 +1,7 @@ package render import ( - "context" "github.com/gin-gonic/gin" - "github.com/go-go-golems/glazed/pkg/formatters/table" - "github.com/go-go-golems/glazed/pkg/processor" - "github.com/go-go-golems/glazed/pkg/settings" - "github.com/go-go-golems/glazed/pkg/types" "github.com/go-go-golems/parka/pkg/glazed" "github.com/go-go-golems/parka/pkg/render/layout" "html/template" @@ -19,14 +14,14 @@ import ( // a processor.TableProcessor. Here we create a processor that uses a HTMLTemplateOutputFormatter (which // we are converting to a more specialized DataTableOutputFormatter), and then wrap all this through a // HTMLTableProcessor. But really the HTMLTableProcessor is just there to wrap the output formatter and -// the template used. But the template used should be captured by the OutputFormatter in the first place. +// the template used. But the template used should be captured by the TableOutputFormatter in the first place. // // As such, we can use a generic TableProcessor (why is there even a processor to be overloaded, if the definition of // processor.TableProcessor is the following: // //type TableProcessor interface { // AddRow(ctx context.Context, obj map[string]interface{}) error -// OutputFormatter() formatters.OutputFormatter +// TableOutputFormatter() formatters.TableOutputFormatter //} // // Probably because we use the processor.GlazeProcessor class as a helper, which is able to handle the different @@ -36,10 +31,9 @@ import ( // HTMLTemplateOutputFormatter wraps a normal HTML table output formatter, and allows // a template to be added in the back in the front. type HTMLTemplateOutputFormatter struct { - // We use a table outputFormatter because we need to access the Table itself. - *table.OutputFormatter - Template *template.Template - Data map[string]interface{} + TemplateName string + Lookup TemplateLookup + Data map[string]interface{} } type HTMLTemplateOutputFormatterOption func(*HTMLTemplateOutputFormatter) @@ -56,13 +50,13 @@ func WithHTMLTemplateOutputFormatterData(data map[string]interface{}) HTMLTempla } func NewHTMLTemplateOutputFormatter( - t *template.Template, - of *table.OutputFormatter, + lookup TemplateLookup, + templateName string, options ...HTMLTemplateOutputFormatterOption, ) *HTMLTemplateOutputFormatter { ret := &HTMLTemplateOutputFormatter{ - OutputFormatter: of, - Template: t, + TemplateName: templateName, + Lookup: lookup, } for _, option := range options { @@ -72,92 +66,46 @@ func NewHTMLTemplateOutputFormatter( return ret } -func (H *HTMLTemplateOutputFormatter) Output(ctx context.Context, table *types.Table, w io.Writer) error { - data := map[string]interface{}{} - for k, v := range H.Data { +func (H *HTMLTemplateOutputFormatter) Output(c *gin.Context, pc *glazed.CommandContext, w io.Writer) error { + // Here, we use the parsed layer to configure the glazed middlewares. + // We then use the created formatters.TableOutputFormatter as a basis for + // our own output formatter that renders into an HTML template. + var err error - data[k] = v + layout_, err := layout.ComputeLayout(pc) + if err != nil { + return err } - data["Columns"] = table.Columns - err := H.Template.Execute(w, data) + description := pc.Cmd.Description() + longHTML, err := RenderMarkdownToHTML(description.Long) if err != nil { return err } - return err -} - -// NewHTMLTemplateLookupCreateProcessorFunc creates a glazed.CreateProcessorFunc based on a TemplateLookup -// and a template name. -func NewHTMLTemplateLookupCreateProcessorFunc( - lookup TemplateLookup, - templateName string, - options ...HTMLTemplateOutputFormatterOption, -) glazed.CreateProcessorFunc { - return func(c *gin.Context, pc *glazed.CommandContext) ( - processor.TableProcessor, - error, - ) { - // Lookup template on every request, not up front. That way, templates can be reloaded without recreating the gin - // server. - t, err := lookup.Lookup(templateName) - if err != nil { - return nil, err - } - - // Here, we use the parsed layer to configure the glazed middlewares. - // We then use the created formatters.OutputFormatter as a basis for - // our own output formatter that renders into an HTML template. - l, ok := pc.ParsedLayers["glazed"] - - var gp *processor.GlazeProcessor - - if ok { - l.Parameters["output"] = "table" - l.Parameters["table-format"] = "html" - - gp, err = settings.SetupProcessor(l.Parameters) - } else { - gp, err = settings.SetupProcessor(map[string]interface{}{ - "output": "table", - "table-format": "html", - }) - } - - if err != nil { - return nil, err - } - - layout_, err := layout.ComputeLayout(pc) - if err != nil { - return nil, err - } - - description := pc.Cmd.Description() + data := map[string]interface{}{} + for k, v := range H.Data { + data[k] = v + } + data["Command"] = description + data["LongDescription"] = template.HTML(longHTML) + data["Layout"] = layout_ - longHTML, err := RenderMarkdownToHTML(description.Long) - if err != nil { - return nil, err - } + // TODO(manuel, 2023-06-30) Get the column names out of a RowOutputMiddleware + //data["Columns"] = table.Columns - options_ := []HTMLTemplateOutputFormatterOption{ - WithHTMLTemplateOutputFormatterData( - map[string]interface{}{ - "Command": description, - "LongDescription": template.HTML(longHTML), - "Layout": layout_, - }), - } - options_ = append(options_, options...) + t, err := H.Lookup.Lookup(H.TemplateName) + if err != nil { + return err + } - of := NewHTMLTemplateOutputFormatter(t, gp.OutputFormatter().(*table.OutputFormatter), options_...) - gp2, err := processor.NewGlazeProcessor(of) - if err != nil { - return nil, err - } + // TODO: we are missing the background processing of the rows here - return gp2, nil + err = t.Execute(w, data) + if err != nil { + return err } + + return err } diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go index dabbb8b..107d696 100644 --- a/pkg/server/server_test.go +++ b/pkg/server/server_test.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" "github.com/go-go-golems/glazed/pkg/cmds" "github.com/go-go-golems/glazed/pkg/cmds/layers" - "github.com/go-go-golems/glazed/pkg/processor" + "github.com/go-go-golems/glazed/pkg/middlewares" "github.com/go-go-golems/glazed/pkg/types" "github.com/go-go-golems/parka/pkg/server" "github.com/stretchr/testify/assert" @@ -27,7 +27,7 @@ func (t *TestCommand) Run( ctx context.Context, parsedLayers map[string]*layers.ParsedParameterLayer, ps map[string]interface{}, - gp processor.TableProcessor, + gp middlewares.Processor, ) error { err := gp.AddRow(ctx, types.NewRow( types.MRP("foo", 1), @@ -75,5 +75,4 @@ func TestRunGlazedCommand(t *testing.T) { assert.Equal(t, float64(1), v["foo"]) assert.Equal(t, "baz", v["bar"]) }) - }