Deduplicate adapter execution and storm parsing
This commit is contained in:
@@ -105,29 +105,20 @@ func (r Runner) Render(ctx context.Context, req RenderRequest) (*RenderResult, e
|
|||||||
if req.DataPackagePath == "" {
|
if req.DataPackagePath == "" {
|
||||||
return nil, fmt.Errorf("data package path is required")
|
return nil, fmt.Errorf("data package path is required")
|
||||||
}
|
}
|
||||||
binary := r.Binary
|
execution, err := r.execute(ctx, r.renderArgs(req))
|
||||||
if binary == "" {
|
|
||||||
binary = "scriptorium"
|
|
||||||
}
|
|
||||||
commands := r.Commands
|
|
||||||
if commands == nil {
|
|
||||||
commands = ExecRunner{}
|
|
||||||
}
|
|
||||||
args := r.renderArgs(req)
|
|
||||||
commandResult, err := commands.Run(ctx, binary, args, r.Timeout)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("run scriptorium render: %w", err)
|
return nil, fmt.Errorf("run scriptorium render: %w", err)
|
||||||
}
|
}
|
||||||
result := &RenderResult{
|
result := &RenderResult{
|
||||||
Command: append([]string{binary}, args...),
|
Command: execution.argv(),
|
||||||
Stdout: string(commandResult.Stdout),
|
Stdout: string(execution.result.Stdout),
|
||||||
Stderr: string(commandResult.Stderr),
|
Stderr: string(execution.result.Stderr),
|
||||||
StdoutTruncated: commandResult.StdoutTruncated,
|
StdoutTruncated: execution.result.StdoutTruncated,
|
||||||
StderrTruncated: commandResult.StderrTruncated,
|
StderrTruncated: execution.result.StderrTruncated,
|
||||||
ExitCode: commandResult.ExitCode,
|
ExitCode: execution.result.ExitCode,
|
||||||
}
|
}
|
||||||
if commandResult.ExitCode != 0 {
|
if execution.result.ExitCode != 0 {
|
||||||
return result, fmt.Errorf("scriptorium render exited with code %d: %s", commandResult.ExitCode, result.Stderr)
|
return result, fmt.Errorf("scriptorium render exited with code %d: %s", execution.result.ExitCode, result.Stderr)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
@@ -142,6 +133,32 @@ func (r Runner) Run(ctx context.Context, req RunRequest) (*RunResult, error) {
|
|||||||
if req.OutputPath == "" {
|
if req.OutputPath == "" {
|
||||||
return nil, fmt.Errorf("output path is required")
|
return nil, fmt.Errorf("output path is required")
|
||||||
}
|
}
|
||||||
|
execution, err := r.execute(ctx, r.runArgs(req))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("run scriptorium: %w", err)
|
||||||
|
}
|
||||||
|
result := &RunResult{
|
||||||
|
Command: execution.argv(),
|
||||||
|
Stdout: string(execution.result.Stdout),
|
||||||
|
Stderr: string(execution.result.Stderr),
|
||||||
|
StdoutTruncated: execution.result.StdoutTruncated,
|
||||||
|
StderrTruncated: execution.result.StderrTruncated,
|
||||||
|
ExitCode: execution.result.ExitCode,
|
||||||
|
OutputPath: req.OutputPath,
|
||||||
|
}
|
||||||
|
if execution.result.ExitCode != 0 {
|
||||||
|
return result, fmt.Errorf("scriptorium run exited with code %d: %s", execution.result.ExitCode, result.Stderr)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type execution struct {
|
||||||
|
binary string
|
||||||
|
args []string
|
||||||
|
result CommandResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Runner) execute(ctx context.Context, args []string) (execution, error) {
|
||||||
binary := r.Binary
|
binary := r.Binary
|
||||||
if binary == "" {
|
if binary == "" {
|
||||||
binary = "scriptorium"
|
binary = "scriptorium"
|
||||||
@@ -150,24 +167,15 @@ func (r Runner) Run(ctx context.Context, req RunRequest) (*RunResult, error) {
|
|||||||
if commands == nil {
|
if commands == nil {
|
||||||
commands = ExecRunner{}
|
commands = ExecRunner{}
|
||||||
}
|
}
|
||||||
args := r.runArgs(req)
|
result, err := commands.Run(ctx, binary, args, r.Timeout)
|
||||||
commandResult, err := commands.Run(ctx, binary, args, r.Timeout)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("run scriptorium: %w", err)
|
return execution{}, err
|
||||||
}
|
}
|
||||||
result := &RunResult{
|
return execution{binary: binary, args: args, result: result}, nil
|
||||||
Command: append([]string{binary}, args...),
|
|
||||||
Stdout: string(commandResult.Stdout),
|
|
||||||
Stderr: string(commandResult.Stderr),
|
|
||||||
StdoutTruncated: commandResult.StdoutTruncated,
|
|
||||||
StderrTruncated: commandResult.StderrTruncated,
|
|
||||||
ExitCode: commandResult.ExitCode,
|
|
||||||
OutputPath: req.OutputPath,
|
|
||||||
}
|
}
|
||||||
if commandResult.ExitCode != 0 {
|
|
||||||
return result, fmt.Errorf("scriptorium run exited with code %d: %s", commandResult.ExitCode, result.Stderr)
|
func (e execution) argv() []string {
|
||||||
}
|
return append([]string{e.binary}, e.args...)
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r Runner) renderArgs(req RenderRequest) []string {
|
func (r Runner) renderArgs(req RenderRequest) []string {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/app"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/app"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/config"
|
||||||
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/report"
|
||||||
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/timeutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -184,12 +185,12 @@ func (r Runner) resolveGenerate(args []string) (app.GenerateRequest, error) {
|
|||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return app.GenerateRequest{}, fmt.Errorf("generate requires a report name")
|
return app.GenerateRequest{}, fmt.Errorf("generate requires a report name")
|
||||||
}
|
}
|
||||||
report, ok := reportKind(args[0])
|
reportKind, ok := reportKind(args[0])
|
||||||
if !ok {
|
if !ok {
|
||||||
return app.GenerateRequest{}, fmt.Errorf("unknown generate report %q", args[0])
|
return app.GenerateRequest{}, fmt.Errorf("unknown generate report %q", args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
opts, err := parseGenerateFlags(report, args[1:])
|
opts, err := parseGenerateFlags(reportKind, args[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return app.GenerateRequest{}, err
|
return app.GenerateRequest{}, err
|
||||||
}
|
}
|
||||||
@@ -208,12 +209,12 @@ func (r Runner) resolveGenerate(args []string) (app.GenerateRequest, error) {
|
|||||||
|
|
||||||
req := app.GenerateRequest{
|
req := app.GenerateRequest{
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
Report: report,
|
Report: reportKind,
|
||||||
OutputPath: opts.Output,
|
OutputPath: opts.Output,
|
||||||
Now: r.Clock.Now(),
|
Now: r.Clock.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch report {
|
switch reportKind {
|
||||||
case app.ReportDaily:
|
case app.ReportDaily:
|
||||||
if opts.Date == "" {
|
if opts.Date == "" {
|
||||||
req.Date = timeutil.LocalDate(r.Clock.Now(), location)
|
req.Date = timeutil.LocalDate(r.Clock.Now(), location)
|
||||||
@@ -230,17 +231,12 @@ func (r Runner) resolveGenerate(args []string) (app.GenerateRequest, error) {
|
|||||||
if opts.End == "" {
|
if opts.End == "" {
|
||||||
return app.GenerateRequest{}, fmt.Errorf("generate storm requires --end")
|
return app.GenerateRequest{}, fmt.Errorf("generate storm requires --end")
|
||||||
}
|
}
|
||||||
req.StormStart, err = timeutil.ParseStormTime(opts.Start, location)
|
period, err := report.ParseStormPeriod(opts.Start, opts.End, location)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return app.GenerateRequest{}, err
|
return app.GenerateRequest{}, err
|
||||||
}
|
}
|
||||||
req.StormEnd, err = timeutil.ParseStormTime(opts.End, location)
|
req.StormStart = period.Start
|
||||||
if err != nil {
|
req.StormEnd = period.End
|
||||||
return app.GenerateRequest{}, err
|
|
||||||
}
|
|
||||||
if !req.StormEnd.After(req.StormStart) {
|
|
||||||
return app.GenerateRequest{}, fmt.Errorf("generate storm requires --end after --start")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return req, nil
|
return req, nil
|
||||||
|
|||||||
@@ -682,7 +682,15 @@ func TestResolveGenerateAppliesSharedFlags(t *testing.T) {
|
|||||||
func TestResolveGenerateStormRequiresStartAndEnd(t *testing.T) {
|
func TestResolveGenerateStormRequiresStartAndEnd(t *testing.T) {
|
||||||
runner := Runner{Clock: fixedClock()}
|
runner := Runner{Clock: fixedClock()}
|
||||||
|
|
||||||
_, err := runner.resolveGenerate([]string{"storm", "--start", "2026-05-29T18:00"})
|
_, err := runner.resolveGenerate([]string{"storm", "--end", "2026-05-29T18:00"})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("resolveGenerate() error = nil, want missing start error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "requires --start") {
|
||||||
|
t.Fatalf("error = %q, want missing start", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = runner.resolveGenerate([]string{"storm", "--start", "2026-05-29T18:00"})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("resolveGenerate() error = nil, want missing end error")
|
t.Fatal("resolveGenerate() error = nil, want missing end error")
|
||||||
}
|
}
|
||||||
@@ -691,6 +699,26 @@ func TestResolveGenerateStormRequiresStartAndEnd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveGenerateStormParsesLocalTimestamps(t *testing.T) {
|
||||||
|
runner := Runner{Clock: fixedClock()}
|
||||||
|
|
||||||
|
req, err := runner.resolveGenerate([]string{
|
||||||
|
"storm",
|
||||||
|
"--tz", "America/Chicago",
|
||||||
|
"--start", "2026-05-29T18:00",
|
||||||
|
"--end", "2026-05-30T06:00",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("resolveGenerate() error = %v", err)
|
||||||
|
}
|
||||||
|
if got := req.StormStart.Format(time.RFC3339); got != "2026-05-29T18:00:00-05:00" {
|
||||||
|
t.Fatalf("StormStart = %q, want local Chicago time", got)
|
||||||
|
}
|
||||||
|
if got := req.StormEnd.Format(time.RFC3339); got != "2026-05-30T06:00:00-05:00" {
|
||||||
|
t.Fatalf("StormEnd = %q, want local Chicago time", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolveGenerateStormParsesRFC3339(t *testing.T) {
|
func TestResolveGenerateStormParsesRFC3339(t *testing.T) {
|
||||||
runner := Runner{Clock: fixedClock()}
|
runner := Runner{Clock: fixedClock()}
|
||||||
|
|
||||||
@@ -707,6 +735,22 @@ func TestResolveGenerateStormParsesRFC3339(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveGenerateStormRejectsInvalidBounds(t *testing.T) {
|
||||||
|
runner := Runner{Clock: fixedClock()}
|
||||||
|
|
||||||
|
_, err := runner.resolveGenerate([]string{
|
||||||
|
"storm",
|
||||||
|
"--start", "2026-05-30T06:00",
|
||||||
|
"--end", "2026-05-29T18:00",
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("resolveGenerate() error = nil, want invalid bounds error")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "end time after start time") {
|
||||||
|
t.Fatalf("error = %q, want invalid bounds context", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestResolveRunCommands(t *testing.T) {
|
func TestResolveRunCommands(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
Reference in New Issue
Block a user