diff options
Diffstat (limited to 'internal/goprecords/report.go')
| -rw-r--r-- | internal/goprecords/report.go | 285 |
1 files changed, 0 insertions, 285 deletions
diff --git a/internal/goprecords/report.go b/internal/goprecords/report.go index cd86fcd..7d04ce3 100644 --- a/internal/goprecords/report.go +++ b/internal/goprecords/report.go @@ -1,158 +1,11 @@ package goprecords import ( - "flag" "fmt" - "html/template" "io" - "net/url" "sort" - "strconv" - "strings" ) -// ReportConfig holds parsed report configuration. -type ReportConfig struct { - Category Category - Metric Metric - Limit uint - OutputFormat OutputFormat - All bool - IncludeKernel bool - StatsOrder string -} - -// ReportFlags holds flag pointers registered on a FlagSet. -type ReportFlags struct { - category *string - metric *string - limit *uint - outputFormat *string - all *bool - includeKernel *bool - statsOrder *string -} - -// RegisterReportFlags registers common report flags on the given FlagSet. -func RegisterReportFlags(fs *flag.FlagSet) *ReportFlags { - return &ReportFlags{ - category: fs.String("category", "Host", "Category: Host, Kernel, KernelMajor, KernelName"), - metric: fs.String("metric", "Uptime", "Metric: Boots, Uptime, Score, Downtime, Lifespan"), - limit: fs.Uint("limit", 20, "Limit output to num of entries"), - outputFormat: fs.String("output-format", "Plaintext", "Output format: Plaintext, Markdown, Gemtext, HTML"), - all: fs.Bool("all", false, "Generate all possible stats but Kernel"), - includeKernel: fs.Bool("include-kernel", false, "Also include Kernel when using -all"), - statsOrder: fs.String("stats-order", "", "Comma-separated Category:Metric order for -all"), - } -} - -// Parse converts flag values into a ReportConfig. -func (rf *ReportFlags) Parse() (ReportConfig, error) { - cat, err := ParseCategory(*rf.category) - if err != nil { - return ReportConfig{}, err - } - met, err := ParseMetric(*rf.metric) - if err != nil { - return ReportConfig{}, err - } - outFmt, err := ParseOutputFormat(*rf.outputFormat) - if err != nil { - return ReportConfig{}, err - } - return ReportConfig{ - Category: cat, - Metric: met, - Limit: *rf.limit, - OutputFormat: outFmt, - All: *rf.all, - IncludeKernel: *rf.includeKernel, - StatsOrder: *rf.statsOrder, - }, nil -} - -// ParseReportQuery builds a ReportConfig from URL query parameters using the -// same names and defaults as RegisterReportFlags (category, metric, limit, -// output-format, all, include-kernel, stats-order). It also accepts Category, -// Metric, and OutputFormat as alternate keys (same values as the CLI). -func ParseReportQuery(q url.Values) (ReportConfig, error) { - catStr := firstQuery(q, "category", "Category") - if catStr == "" { - catStr = "Host" - } - cat, err := ParseCategory(catStr) - if err != nil { - return ReportConfig{}, err - } - metStr := firstQuery(q, "metric", "Metric") - if metStr == "" { - metStr = "Uptime" - } - met, err := ParseMetric(metStr) - if err != nil { - return ReportConfig{}, err - } - limit := uint(20) - if ls := q.Get("limit"); ls != "" { - v, err := strconv.ParseUint(ls, 10, 32) - if err != nil { - return ReportConfig{}, fmt.Errorf("invalid limit %q", ls) - } - limit = uint(v) - } - outStr := firstQuery(q, "output-format", "OutputFormat") - if outStr == "" { - outStr = "Plaintext" - } - outFmt, err := ParseOutputFormat(outStr) - if err != nil { - return ReportConfig{}, err - } - all := false - if v := q.Get("all"); v != "" { - all, err = parseQueryBool(v) - if err != nil { - return ReportConfig{}, err - } - } - includeKernel := false - if v := q.Get("include-kernel"); v != "" { - includeKernel, err = parseQueryBool(v) - if err != nil { - return ReportConfig{}, err - } - } - return ReportConfig{ - Category: cat, - Metric: met, - Limit: limit, - OutputFormat: outFmt, - All: all, - IncludeKernel: includeKernel, - StatsOrder: q.Get("stats-order"), - }, nil -} - -func firstQuery(q url.Values, keys ...string) string { - for _, k := range keys { - if v := q.Get(k); v != "" { - return v - } - } - return "" -} - -func parseQueryBool(s string) (bool, error) { - switch strings.ToLower(strings.TrimSpace(s)) { - case "true", "1", "yes": - return true, nil - case "false", "0", "no", "": - return false, nil - default: - return false, fmt.Errorf("invalid boolean %q", s) - } -} - // WriteReports renders reports to w based on the given config. func WriteReports(w io.Writer, aggregates *Aggregates, cfg ReportConfig) error { if !cfg.All { @@ -395,141 +248,3 @@ func (r reportBuilder) humanStrAgg(a *Aggregate) string { return formatDuration(a.Uptime) } } - -func (r reportBuilder) formatReport(rows []tableRow, hasLastKernel bool, outputFormat OutputFormat) string { - cW, nW, vW, lkW := r.reportWidths(rows, hasLastKernel) - border := r.buildBorder(cW, nW, vW, lkW, hasLastKernel) - header := r.buildReportHeader(cW, nW, vW, lkW, hasLastKernel, border, outputFormat) - fmtStr := r.buildFormatStr(cW, nW, vW, lkW, hasLastKernel) - body := r.buildReportBody(rows, fmtStr, hasLastKernel) - out := header + body + border - if outputFormat == FormatMarkdown || outputFormat == FormatGemtext { - out += "```\n" - } - return out -} - -func (r reportBuilder) formatReportHTML(rows []tableRow, hasLastKernel bool) string { - cW, nW, vW, lkW := r.reportWidths(rows, hasLastKernel) - border := r.buildBorder(cW, nW, vW, lkW, hasLastKernel) - fmtStr := r.buildFormatStr(cW, nW, vW, lkW, hasLastKernel) - var headRow string - if hasLastKernel { - headRow = fmt.Sprintf(fmtStr+"\n", "Pos", r.category.String(), r.metric.String(), "Last Kernel") - } else { - headRow = fmt.Sprintf(fmtStr+"\n", "Pos", r.category.String(), r.metric.String()) - } - body := r.buildReportBody(rows, fmtStr, hasLastKernel) - ascii := border + headRow + border + body + border - - hl := int(r.headerIndent) - if hl < 1 { - hl = 1 - } - if hl > 6 { - hl = 6 - } - title := fmt.Sprintf("Top %d %s's by %s", r.limit, r.metric, r.category) - desc := MetricDescription(r.metric) - - var b strings.Builder - b.WriteString("<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>") - b.WriteString(template.HTMLEscapeString(title)) - b.WriteString("</title>\n</head>\n<body>\n<h") - b.WriteString(strconv.Itoa(hl)) - b.WriteString(">") - b.WriteString(template.HTMLEscapeString(title)) - b.WriteString("</h") - b.WriteString(strconv.Itoa(hl)) - b.WriteString(">\n") - if desc != "" { - b.WriteString("<p>") - b.WriteString(template.HTMLEscapeString(desc)) - b.WriteString("</p>\n") - } - b.WriteString("<pre>") - b.WriteString(template.HTMLEscapeString(ascii)) - b.WriteString("</pre>\n</body>\n</html>\n") - return b.String() -} - -func (r reportBuilder) reportWidths(rows []tableRow, hasLastKernel bool) (countW, nameW, valueW, lastKernelW int) { - countW = 3 - nameW = len(r.category.String()) - valueW = len(r.metric.String()) - if hasLastKernel { - lastKernelW = len("Last Kernel") - } - for _, row := range rows { - if len(row.Pos) > countW { - countW = len(row.Pos) - } - if len(row.Name) > nameW { - nameW = len(row.Name) - } - if len(row.Value) > valueW { - valueW = len(row.Value) - } - if len(row.LastKernel) > lastKernelW { - lastKernelW = len(row.LastKernel) - } - } - return countW, nameW, valueW, lastKernelW -} - -func (r reportBuilder) buildBorder(countW, nameW, valueW, lastKernelW int, hasLastKernel bool) string { - parts := []string{ - "+" + strings.Repeat("-", 2+countW), - "+" + strings.Repeat("-", 2+nameW), - "+" + strings.Repeat("-", 2+valueW), - } - if hasLastKernel { - parts = append(parts, "+"+strings.Repeat("-", 2+lastKernelW)) - } - return strings.Join(parts, "") + "+\n" -} - -func (r reportBuilder) buildReportHeader(countW, nameW, valueW, lastKernelW int, hasLastKernel bool, border string, outputFormat OutputFormat) string { - var h string - if outputFormat == FormatMarkdown || outputFormat == FormatGemtext { - h = strings.Repeat("#", int(r.headerIndent)) + " " - } - h += fmt.Sprintf("Top %d %s's by %s\n\n", r.limit, r.metric, r.category) - desc := MetricDescription(r.metric) - lineLimit := len(border) - if outputFormat == FormatPlaintext && lineLimit > 0 && len(desc) > lineLimit-1 { - desc = " " + wordWrap(desc, lineLimit-1) - } - h += desc + "\n\n" - if outputFormat == FormatMarkdown || outputFormat == FormatGemtext { - h += "```\n" - } - h += border - fmtStr := r.buildFormatStr(countW, nameW, valueW, lastKernelW, hasLastKernel) - if hasLastKernel { - h += fmt.Sprintf(fmtStr+"\n", "Pos", r.category.String(), r.metric.String(), "Last Kernel") - } else { - h += fmt.Sprintf(fmtStr+"\n", "Pos", r.category.String(), r.metric.String()) - } - h += border - return h -} - -func (r reportBuilder) buildFormatStr(countW, nameW, valueW, lastKernelW int, hasLastKernel bool) string { - if hasLastKernel { - return fmt.Sprintf("| %%%ds | %%%ds | %%%ds | %%%ds |", countW, nameW, valueW, lastKernelW) - } - return fmt.Sprintf("| %%%ds | %%%ds | %%%ds |", countW, nameW, valueW) -} - -func (r reportBuilder) buildReportBody(rows []tableRow, fmtStr string, hasLastKernel bool) string { - var b strings.Builder - for _, row := range rows { - if hasLastKernel { - b.WriteString(fmt.Sprintf(fmtStr+"\n", row.Pos, row.Name, row.Value, row.LastKernel)) - } else { - b.WriteString(fmt.Sprintf(fmtStr+"\n", row.Pos, row.Name, row.Value)) - } - } - return b.String() -} |
