internal/output/formatter.go (view raw)
1package output
2
3import (
4 "encoding/json"
5 "fmt"
6 "strings"
7 "time"
8)
9
10// Formatter formats output in different formats.
11type Formatter struct {
12 formatType string
13}
14
15// NewFormatter creates a new formatter with the specified format.
16func NewFormatter(formatType string) (*Formatter, error) {
17 formatType = strings.ToLower(formatType)
18 if formatType != "text" && formatType != "markdown" && formatType != "json" {
19 return nil, fmt.Errorf("unsupported format: %s", formatType)
20 }
21 return &Formatter{formatType: formatType}, nil
22}
23
24// FormatData formats data according to the specified format.
25func (f *Formatter) FormatData(data map[string]any) (string, error) {
26 switch f.formatType {
27 case "json":
28 return f.formatJSON(data)
29 case "markdown":
30 return f.formatMarkdown(data)
31 default: // text
32 return f.formatText(data)
33 }
34}
35
36func (f *Formatter) formatJSON(data map[string]any) (string, error) {
37 bytes, err := json.MarshalIndent(data, "", " ")
38 if err != nil {
39 return "", fmt.Errorf("failed to marshal JSON: %w", err)
40 }
41 return string(bytes), nil
42}
43
44func (f *Formatter) formatMarkdown(data map[string]any) (string, error) {
45 var lines []string
46
47 // Title
48 title := getString(data, "title", "LLM Aggregator Summary")
49 lines = append(lines, fmt.Sprintf("# %s", title))
50 lines = append(lines, "")
51
52 // Metadata
53 lines = append(lines, "## Metadata")
54 lines = append(lines, "")
55
56 prompt := getString(data, "prompt", "Unknown")
57 model := getString(data, "model", "Unknown")
58 articlesCount := getInt(data, "articles_count", 0)
59 timestamp := getString(data, "timestamp", time.Now().Format(time.RFC3339))
60
61 lines = append(lines, fmt.Sprintf("- **Prompt**: %s", prompt))
62 lines = append(lines, fmt.Sprintf("- **Model**: %s", model))
63 lines = append(lines, fmt.Sprintf("- **Articles Analysed**: %d", articlesCount))
64 lines = append(lines, fmt.Sprintf("- **Generated**: %s", timestamp))
65 lines = append(lines, "")
66
67 // Summary
68 lines = append(lines, "## Summary")
69 lines = append(lines, "")
70 summary := getString(data, "summary", "No summary available.")
71 lines = append(lines, summary)
72 lines = append(lines, "")
73
74 // Articles (if included)
75 if articles, ok := data["articles"].([]map[string]any); ok && len(articles) > 0 {
76 lines = append(lines, "## Articles Analysed")
77 lines = append(lines, "")
78
79 for i, article := range articles {
80 lines = append(lines, fmt.Sprintf("### Article %d: %s", i+1, article["title"]))
81 lines = append(lines, "")
82
83 if source, ok := article["source_feed"].(string); ok && source != "" {
84 lines = append(lines, fmt.Sprintf("**Source**: %s", source))
85 }
86
87 if published, ok := article["published"]; ok {
88 switch pub := published.(type) {
89 case time.Time:
90 if !pub.IsZero() {
91 lines = append(lines, fmt.Sprintf("**Published**: %s", pub.Format("2006-01-02 15:04")))
92 }
93 case string:
94 lines = append(lines, fmt.Sprintf("**Published**: %s", pub))
95 }
96 }
97
98 if author, ok := article["author"].(string); ok && author != "" {
99 lines = append(lines, fmt.Sprintf("**Author**: %s", author))
100 }
101
102 if link, ok := article["link"].(string); ok && link != "" {
103 lines = append(lines, fmt.Sprintf("**Link**: [%s](%s)", link, link))
104 }
105
106 lines = append(lines, "")
107 }
108 }
109
110 return strings.Join(lines, "\n"), nil
111}
112
113func (f *Formatter) formatText(data map[string]any) (string, error) {
114 var lines []string
115
116 // Title/Header
117 title := getString(data, "title", "LLM Aggregator Summary")
118 lines = append(lines, strings.Repeat("=", 80))
119 lines = append(lines, centerText(title, 80))
120 lines = append(lines, strings.Repeat("=", 80))
121 lines = append(lines, "")
122
123 // Metadata
124 lines = append(lines, "METADATA")
125 lines = append(lines, strings.Repeat("-", 40))
126
127 prompt := getString(data, "prompt", "Unknown")
128 model := getString(data, "model", "Unknown")
129 articlesCount := getInt(data, "articles_count", 0)
130 timestamp := getString(data, "timestamp", time.Now().Format(time.RFC3339))
131
132 lines = append(lines, fmt.Sprintf("Prompt: %s", prompt))
133 lines = append(lines, fmt.Sprintf("Model: %s", model))
134 lines = append(lines, fmt.Sprintf("Articles Analysed: %d", articlesCount))
135 lines = append(lines, fmt.Sprintf("Generated: %s", timestamp))
136 lines = append(lines, "")
137
138 // Summary
139 lines = append(lines, "SUMMARY")
140 lines = append(lines, strings.Repeat("-", 40))
141 lines = append(lines, "")
142 summary := getString(data, "summary", "No summary available.")
143 lines = append(lines, summary)
144 lines = append(lines, "")
145
146 // Articles (if included)
147 if articles, ok := data["articles"].([]map[string]any); ok && len(articles) > 0 {
148 lines = append(lines, "ARTICLES ANALYSED")
149 lines = append(lines, strings.Repeat("-", 40))
150 lines = append(lines, "")
151
152 for i, article := range articles {
153 lines = append(lines, fmt.Sprintf("[Article %d]", i+1))
154 lines = append(lines, fmt.Sprintf("Title: %s", article["title"]))
155
156 if source, ok := article["source_feed"].(string); ok && source != "" {
157 lines = append(lines, fmt.Sprintf("Source: %s", source))
158 }
159
160 if published, ok := article["published"]; ok {
161 switch pub := published.(type) {
162 case time.Time:
163 if !pub.IsZero() {
164 lines = append(lines, fmt.Sprintf("Published: %s", pub.Format("2006-01-02 15:04")))
165 }
166 case string:
167 lines = append(lines, fmt.Sprintf("Published: %s", pub))
168 }
169 }
170
171 if author, ok := article["author"].(string); ok && author != "" {
172 lines = append(lines, fmt.Sprintf("Author: %s", author))
173 }
174
175 if link, ok := article["link"].(string); ok && link != "" {
176 lines = append(lines, fmt.Sprintf("Link: %s", link))
177 }
178
179 // Show preview of content
180 if content, ok := article["content"].(string); ok && content != "" {
181 preview := content
182 if len(preview) > 200 {
183 preview = preview[:200] + "..."
184 }
185 lines = append(lines, fmt.Sprintf("Preview: %s", preview))
186 }
187
188 lines = append(lines, "")
189 }
190 }
191
192 // Footer
193 lines = append(lines, strings.Repeat("=", 80))
194 lines = append(lines, centerText("End of Summary", 80))
195 lines = append(lines, strings.Repeat("=", 80))
196
197 return strings.Join(lines, "\n"), nil
198}
199
200// Helper functions
201func getString(data map[string]any, key, defaultValue string) string {
202 if val, ok := data[key].(string); ok {
203 return val
204 }
205 return defaultValue
206}
207
208func getInt(data map[string]any, key string, defaultValue int) int {
209 if val, ok := data[key].(int); ok {
210 return val
211 }
212 if val, ok := data[key].(float64); ok {
213 return int(val)
214 }
215 return defaultValue
216}
217
218func centerText(text string, width int) string {
219 if len(text) >= width {
220 return text
221 }
222 padding := (width - len(text)) / 2
223 return strings.Repeat(" ", padding) + text + strings.Repeat(" ", width-padding-len(text))
224}