Session configuration templates are now proceeded by narratio session init; all other commands require concrete configuration
This commit is contained in:
@@ -44,8 +44,8 @@ func addCommonConfigFlags(fs *flag.FlagSet, flags *commonConfigFlags) {
|
||||
fs.StringVar(&flags.pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
||||
fs.StringVar(&flags.campaignPath, "campaign", "", "path to campaign.yml (optional; defaults searched)")
|
||||
fs.StringVar(&flags.sessionPath, "session", "", "path to session.yml")
|
||||
fs.StringVar(&flags.sessionID, "session-id", "", "session identifier for session.yml templates")
|
||||
fs.StringVar(&flags.previousSessionID, "previous-session-id", "", "previous session identifier for session.yml templates")
|
||||
fs.StringVar(&flags.sessionID, "session-id", "", "expected session identifier and remote session lookup value")
|
||||
fs.StringVar(&flags.previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
||||
}
|
||||
|
||||
func (f commonConfigFlags) sessionOptions() config.SessionLoadOptions {
|
||||
@@ -290,7 +290,18 @@ func SessionInit(ctx context.Context, args []string, out io.Writer) error {
|
||||
return fmt.Errorf("session init: %w", err)
|
||||
}
|
||||
|
||||
data, err := buildSessionYAML(base.Campaign.Campaign, sessionID, previousSessionID, date, title, audioS3Prefix, audioDir)
|
||||
input := sessionInitInput{
|
||||
Campaign: base.Campaign.Campaign,
|
||||
CampaignPath: base.CampaignPath,
|
||||
TemplateFile: base.Campaign.SessionTemplateFile,
|
||||
SessionID: sessionID,
|
||||
PreviousSessionID: previousSessionID,
|
||||
Date: date,
|
||||
Title: title,
|
||||
AudioS3Prefix: audioS3Prefix,
|
||||
AudioDir: audioDir,
|
||||
}
|
||||
data, err := buildSessionInitYAML(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("session init: %w", err)
|
||||
}
|
||||
@@ -588,6 +599,104 @@ func buildSessionYAML(campaign, sessionID, previousSessionID, date, title, audio
|
||||
return data, nil
|
||||
}
|
||||
|
||||
type sessionInitInput struct {
|
||||
Campaign string
|
||||
CampaignPath string
|
||||
TemplateFile string
|
||||
SessionID string
|
||||
PreviousSessionID string
|
||||
Date string
|
||||
Title string
|
||||
AudioS3Prefix string
|
||||
AudioDir string
|
||||
}
|
||||
|
||||
func buildSessionInitYAML(in sessionInitInput) ([]byte, error) {
|
||||
if strings.TrimSpace(in.TemplateFile) == "" {
|
||||
return buildSessionYAML(in.Campaign, in.SessionID, in.PreviousSessionID, in.Date, in.Title, in.AudioS3Prefix, in.AudioDir)
|
||||
}
|
||||
templatePath := resolveSessionInitTemplatePath(in.CampaignPath, in.TemplateFile)
|
||||
templateBytes, err := os.ReadFile(templatePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read session template %q: %w", templatePath, err)
|
||||
}
|
||||
rendered, err := renderSessionInitTemplate(string(templateBytes), in)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("render session template %q: %w", templatePath, err)
|
||||
}
|
||||
return []byte(rendered), nil
|
||||
}
|
||||
|
||||
func resolveSessionInitTemplatePath(campaignPath, templateFile string) string {
|
||||
templateFile = strings.TrimSpace(templateFile)
|
||||
if filepath.IsAbs(templateFile) {
|
||||
return filepath.Clean(templateFile)
|
||||
}
|
||||
return filepath.Clean(filepath.Join(filepath.Dir(campaignPath), templateFile))
|
||||
}
|
||||
|
||||
var sessionInitTemplatePattern = regexp.MustCompile(`\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}`)
|
||||
|
||||
func renderSessionInitTemplate(content string, in sessionInitInput) (string, error) {
|
||||
values := map[string]string{
|
||||
"session_id": strings.TrimSpace(in.SessionID),
|
||||
"previous_session_id": strings.TrimSpace(in.PreviousSessionID),
|
||||
"date": strings.TrimSpace(in.Date),
|
||||
"title": strings.TrimSpace(in.Title),
|
||||
"audio_s3_prefix": strings.TrimSpace(in.AudioS3Prefix),
|
||||
"audio_dir": strings.TrimSpace(in.AudioDir),
|
||||
}
|
||||
used := map[string]struct{}{}
|
||||
unknown := map[string]struct{}{}
|
||||
missing := map[string]struct{}{}
|
||||
rendered := sessionInitTemplatePattern.ReplaceAllStringFunc(content, func(match string) string {
|
||||
parts := sessionInitTemplatePattern.FindStringSubmatch(match)
|
||||
if len(parts) < 2 {
|
||||
return match
|
||||
}
|
||||
name := parts[1]
|
||||
value, ok := values[name]
|
||||
if !ok {
|
||||
unknown[name] = struct{}{}
|
||||
return match
|
||||
}
|
||||
used[name] = struct{}{}
|
||||
if value == "" {
|
||||
missing[name] = struct{}{}
|
||||
return match
|
||||
}
|
||||
return value
|
||||
})
|
||||
if len(unknown) > 0 {
|
||||
return "", fmt.Errorf("unsupported template variable(s): %s", sortedStringSet(unknown))
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
return "", fmt.Errorf("missing required template variable value(s): %s", sortedStringSet(missing))
|
||||
}
|
||||
unused := map[string]struct{}{}
|
||||
for _, name := range []string{"previous_session_id", "date", "title", "audio_s3_prefix", "audio_dir"} {
|
||||
if values[name] == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := used[name]; !ok {
|
||||
unused[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(unused) > 0 {
|
||||
return "", fmt.Errorf("unused template variable value(s): %s", sortedStringSet(unused))
|
||||
}
|
||||
return rendered, nil
|
||||
}
|
||||
|
||||
func sortedStringSet(set map[string]struct{}) string {
|
||||
items := make([]string, 0, len(set))
|
||||
for item := range set {
|
||||
items = append(items, item)
|
||||
}
|
||||
sort.Strings(items)
|
||||
return strings.Join(items, ", ")
|
||||
}
|
||||
|
||||
func renderFindings(out io.Writer, campaign, sessionID string, findings []finding) error {
|
||||
if campaign != "" || sessionID != "" {
|
||||
fmt.Fprintf(out, "Campaign: %s\n", campaign)
|
||||
|
||||
Reference in New Issue
Block a user