| /* |
| * Copyright 2019-present Ciena Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package format |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "io" |
| "reflect" |
| "regexp" |
| "strconv" |
| "strings" |
| "text/tabwriter" |
| "text/template" |
| "text/template/parse" |
| ) |
| |
| var nameFinder = regexp.MustCompile(`\.([\._A-Za-z0-9]*)}}`) |
| |
| type Format string |
| |
| /* TrimAndPad |
| * |
| * Modify `s` so that it is exactly `l` characters long, removing |
| * characters from the end, or adding spaces as necessary. |
| */ |
| |
| func TrimAndPad(s string, l int) string { |
| // TODO: support right justification if a negative number is passed |
| if len(s) > l { |
| s = s[:l] |
| } |
| return s + strings.Repeat(" ", l-len(s)) |
| } |
| |
| /* GetHeaderString |
| * |
| * From a template, extract the set of column names. |
| */ |
| |
| func GetHeaderString(tmpl *template.Template, nameLimit int) string { |
| var header string |
| for _, n := range tmpl.Tree.Root.Nodes { |
| switch n.Type() { |
| case parse.NodeText: |
| header += n.String() |
| case parse.NodeString: |
| header += n.String() |
| case parse.NodeAction: |
| found := nameFinder.FindStringSubmatch(n.String()) |
| if len(found) == 2 { |
| if nameLimit > 0 { |
| parts := strings.Split(found[1], ".") |
| start := len(parts) - nameLimit |
| if start < 0 { |
| start = 0 |
| } |
| header += strings.ToUpper(strings.Join(parts[start:], ".")) |
| } else { |
| header += strings.ToUpper(found[1]) |
| } |
| } |
| } |
| } |
| return header |
| } |
| |
| func (f Format) IsTable() bool { |
| return strings.HasPrefix(string(f), "table") |
| } |
| |
| func (f Format) Execute(writer io.Writer, withHeaders bool, nameLimit int, data interface{}) error { |
| var tabWriter *tabwriter.Writer = nil |
| format := f |
| |
| if f.IsTable() { |
| tabWriter = tabwriter.NewWriter(writer, 0, 4, 4, ' ', 0) |
| format = Format(strings.TrimPrefix(string(f), "table")) |
| } |
| |
| tmpl, err := template.New("output").Parse(string(format)) |
| if err != nil { |
| return err |
| } |
| |
| if f.IsTable() && withHeaders { |
| header := GetHeaderString(tmpl, nameLimit) |
| |
| if _, err = tabWriter.Write([]byte(header)); err != nil { |
| return err |
| } |
| if _, err = tabWriter.Write([]byte("\n")); err != nil { |
| return err |
| } |
| |
| slice := reflect.ValueOf(data) |
| if slice.Kind() == reflect.Slice { |
| for i := 0; i < slice.Len(); i++ { |
| if err = tmpl.Execute(tabWriter, slice.Index(i).Interface()); err != nil { |
| return err |
| } |
| if _, err = tabWriter.Write([]byte("\n")); err != nil { |
| return err |
| } |
| } |
| } else { |
| if err = tmpl.Execute(tabWriter, data); err != nil { |
| return err |
| } |
| if _, err = tabWriter.Write([]byte("\n")); err != nil { |
| return err |
| } |
| } |
| tabWriter.Flush() |
| return nil |
| } |
| |
| slice := reflect.ValueOf(data) |
| if slice.Kind() == reflect.Slice { |
| for i := 0; i < slice.Len(); i++ { |
| if err = tmpl.Execute(writer, slice.Index(i).Interface()); err != nil { |
| return err |
| } |
| if _, err = writer.Write([]byte("\n")); err != nil { |
| return err |
| } |
| } |
| } else { |
| if err = tmpl.Execute(writer, data); err != nil { |
| return err |
| } |
| if _, err = writer.Write([]byte("\n")); err != nil { |
| return err |
| } |
| } |
| return nil |
| |
| } |
| |
| /* |
| * ExecuteFixedWidth |
| * |
| * Formats a table row using a set of fixed column widths. Used for streaming |
| * output where column widths cannot be automatically determined because only |
| * one line of the output is available at a time. |
| * |
| * Assumes the format uses tab as a field delimiter. |
| * |
| * columnWidths: struct that contains column widths |
| * header: If true return the header. If false then evaluate data and return data. |
| * data: Data to evaluate |
| */ |
| |
| func (f Format) ExecuteFixedWidth(columnWidths interface{}, header bool, data interface{}) (string, error) { |
| if !f.IsTable() { |
| return "", errors.New("Fixed width is only available on table format") |
| } |
| |
| outputAs := strings.TrimPrefix(string(f), "table") |
| tmpl, err := template.New("output").Parse(string(outputAs)) |
| if err != nil { |
| return "", fmt.Errorf("Failed to parse template: %v", err) |
| } |
| |
| var buf bytes.Buffer |
| var tabSepOutput string |
| |
| if header { |
| // Caller wants the table header. |
| tabSepOutput = GetHeaderString(tmpl, 1) |
| } else { |
| // Caller wants the data. |
| err = tmpl.Execute(&buf, data) |
| if err != nil { |
| return "", fmt.Errorf("Failed to execute template: %v", err) |
| } |
| tabSepOutput = buf.String() |
| } |
| |
| // Extract the column width constants by running the template on the |
| // columnWidth structure. This will cause text.template to split the |
| // column widths exactly like it did the output (i.e. separated by |
| // tab characters) |
| buf.Reset() |
| err = tmpl.Execute(&buf, columnWidths) |
| if err != nil { |
| return "", fmt.Errorf("Failed to execute template on widths: %v", err) |
| } |
| tabSepWidth := buf.String() |
| |
| // Loop through the fields and widths, printing each field to the |
| // preset width. |
| output := "" |
| outParts := strings.Split(tabSepOutput, "\t") |
| widthParts := strings.Split(tabSepWidth, "\t") |
| for i, outPart := range outParts { |
| width, err := strconv.Atoi(widthParts[i]) |
| if err != nil { |
| return "", fmt.Errorf("Failed to parse width %s: %v", widthParts[i], err) |
| } |
| output = output + TrimAndPad(outPart, width) + " " |
| } |
| |
| // remove any trailing spaces |
| output = strings.TrimRight(output, " ") |
| |
| return output, nil |
| } |