Split HumanReadablePrinter struct into generator and printer structs

This commit is contained in:
Sean Sullivan 2019-08-13 21:36:29 -07:00
parent f4521bf5a2
commit 6d903c4787
2 changed files with 35 additions and 42 deletions

View File

@ -43,30 +43,25 @@ type handlerEntry struct {
args []reflect.Value args []reflect.Value
} }
// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide // HumanReadableGenerator is an implementation of TableGenerator used to generate
// more elegant output. It is not threadsafe, but you may call PrintObj repeatedly; headers // a table for a specific resource. The table is printed with a TablePrinter using
// will only be printed if the object type changes. This makes it useful for printing items // PrintObj().
// received from watches. type HumanReadableGenerator struct {
type HumanReadablePrinter struct { handlerMap map[reflect.Type]*handlerEntry
handlerMap map[reflect.Type]*handlerEntry
options PrintOptions
lastType interface{}
lastColumns []metav1beta1.TableColumnDefinition
printedHeaders bool
} }
var _ TableGenerator = &HumanReadablePrinter{} var _ TableGenerator = &HumanReadableGenerator{}
var _ PrintHandler = &HumanReadablePrinter{} var _ PrintHandler = &HumanReadableGenerator{}
// NewTableGenerator creates a HumanReadablePrinter suitable for calling GenerateTable(). // NewTableGenerator creates a HumanReadableGenerator suitable for calling GenerateTable().
func NewTableGenerator() *HumanReadablePrinter { func NewTableGenerator() *HumanReadableGenerator {
return &HumanReadablePrinter{ return &HumanReadableGenerator{
handlerMap: make(map[reflect.Type]*handlerEntry), handlerMap: make(map[reflect.Type]*handlerEntry),
} }
} }
// With method - accepts a list of builder functions that modify HumanReadablePrinter // With method - accepts a list of builder functions that modify HumanReadableGenerator
func (h *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePrinter { func (h *HumanReadableGenerator) With(fns ...func(PrintHandler)) *HumanReadableGenerator {
for _, fn := range fns { for _, fn := range fns {
fn(h) fn(h)
} }
@ -76,7 +71,7 @@ func (h *HumanReadablePrinter) With(fns ...func(PrintHandler)) *HumanReadablePri
// GenerateTable returns a table for the provided object, using the printer registered for that type. It returns // GenerateTable returns a table for the provided object, using the printer registered for that type. It returns
// a table that includes all of the information requested by options, but will not remove rows or columns. The // a table that includes all of the information requested by options, but will not remove rows or columns. The
// caller is responsible for applying rules related to filtering rows or columns. // caller is responsible for applying rules related to filtering rows or columns.
func (h *HumanReadablePrinter) GenerateTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error) { func (h *HumanReadableGenerator) GenerateTable(obj runtime.Object, options PrintOptions) (*metav1beta1.Table, error) {
t := reflect.TypeOf(obj) t := reflect.TypeOf(obj)
handler, ok := h.handlerMap[t] handler, ok := h.handlerMap[t]
if !ok { if !ok {
@ -126,9 +121,9 @@ func (h *HumanReadablePrinter) GenerateTable(obj runtime.Object, options PrintOp
return table, nil return table, nil
} }
// TableHandler adds a print handler with a given set of columns to HumanReadablePrinter instance. // TableHandler adds a print handler with a given set of columns to HumanReadableGenerator instance.
// See ValidateRowPrintHandlerFunc for required method signature. // See ValidateRowPrintHandlerFunc for required method signature.
func (h *HumanReadablePrinter) TableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error { func (h *HumanReadableGenerator) TableHandler(columnDefinitions []metav1beta1.TableColumnDefinition, printFunc interface{}) error {
printFuncValue := reflect.ValueOf(printFunc) printFuncValue := reflect.ValueOf(printFunc)
if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil { if err := ValidateRowPrintHandlerFunc(printFuncValue); err != nil {
utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err)) utilruntime.HandleError(fmt.Errorf("unable to register print function: %v", err))

View File

@ -61,13 +61,23 @@ var (
withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too. withNamespacePrefixColumns = []string{"NAMESPACE"} // TODO(erictune): print cluster name too.
) )
// HumanReadablePrinter is an implementation of ResourcePrinter which attempts to provide
// more elegant output. It is not threadsafe, but you may call PrintObj repeatedly; headers
// will only be printed if the object type changes. This makes it useful for printing items
// received from watches.
type HumanReadablePrinter struct {
options PrintOptions
lastType interface{}
lastColumns []metav1beta1.TableColumnDefinition
printedHeaders bool
}
// NewTablePrinter creates a printer suitable for calling PrintObj(). // NewTablePrinter creates a printer suitable for calling PrintObj().
// TODO(seans3): Change return type to ResourcePrinter interface once we no longer need // TODO(seans3): Change return type to ResourcePrinter interface once we no longer need
// to constuct the "handlerMap". // to constuct the "handlerMap".
func NewTablePrinter(options PrintOptions) *HumanReadablePrinter { func NewTablePrinter(options PrintOptions) *HumanReadablePrinter {
printer := &HumanReadablePrinter{ printer := &HumanReadablePrinter{
handlerMap: make(map[reflect.Type]*handlerEntry), options: options,
options: options,
} }
return printer return printer
} }
@ -81,6 +91,7 @@ func printHeader(columnNames []string, w io.Writer) error {
// PrintObj prints the obj in a human-friendly format according to the type of the obj. // PrintObj prints the obj in a human-friendly format according to the type of the obj.
func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error { func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) error {
w, found := output.(*tabwriter.Writer) w, found := output.(*tabwriter.Writer)
if !found { if !found {
w = GetNewTabWriter(output) w = GetNewTabWriter(output)
@ -94,7 +105,7 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
obj = event.Object.Object obj = event.Object.Object
} }
// Case 1: Parameter "obj" is a table from server; print it. // Parameter "obj" is a table from server; print it.
// display tables following the rules of options // display tables following the rules of options
if table, ok := obj.(*metav1beta1.Table); ok { if table, ok := obj.(*metav1beta1.Table); ok {
// Do not print headers if this table has no column definitions, or they are the same as the last ones we printed // Do not print headers if this table has no column definitions, or they are the same as the last ones we printed
@ -117,6 +128,8 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
h.printedHeaders = true h.printedHeaders = true
} }
// TODO(seans3): Remove the following decorateTable call. Table modification
// (and creation) should only happen in table generation (tablegenerator.go).
if err := decorateTable(table, localOptions); err != nil { if err := decorateTable(table, localOptions); err != nil {
return err return err
} }
@ -131,25 +144,7 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er
return printTable(table, output, localOptions) return printTable(table, output, localOptions)
} }
// Case 2: Parameter "obj" is not a table; search for a handler to print it. // Could not find print handler for "obj"; use the default or status print handler.
// TODO(seans3): Remove this case in 1.16, since table should be returned from server-side printing.
// print with a registered handler
t := reflect.TypeOf(obj)
if handler := h.handlerMap[t]; handler != nil {
includeHeaders := h.lastType != t && !h.options.NoHeaders
if h.lastType != nil && h.lastType != t && !h.options.NoHeaders {
fmt.Fprintln(output)
}
if err := printRowsForHandlerEntry(output, handler, eventType, obj, h.options, includeHeaders); err != nil {
return err
}
h.lastType = t
return nil
}
// Case 3: Could not find print handler for "obj"; use the default or status print handler.
// Print with the default or status handler, and use the columns from the last time // Print with the default or status handler, and use the columns from the last time
var handler *handlerEntry var handler *handlerEntry
if _, isStatus := obj.(*metav1.Status); isStatus { if _, isStatus := obj.(*metav1.Status); isStatus {
@ -291,6 +286,9 @@ func addColumns(pos columnAddPosition, table *metav1beta1.Table, columns []metav
return nil return nil
} }
// TODO(seans3): This method modifies the table, to it should only happen
// during table generation (tablegenerator.go), and not during table printing.
//
// decorateTable takes a table and attempts to add label columns and the // decorateTable takes a table and attempts to add label columns and the
// namespace column. It will fill empty columns with nil (if the object // namespace column. It will fill empty columns with nil (if the object
// does not expose metadata). It returns an error if the table cannot // does not expose metadata). It returns an error if the table cannot