printer/templateprinter.go
author Mikael Berthe <mikael@lilotux.net>
Wed, 19 Apr 2017 19:08:47 +0200
changeset 0 5abace724584
child 14 da2059f2fa6a
permissions -rw-r--r--
Initial public release v0.0.9

// Copyright © 2017 Mikael Berthe <mikael@lilotux.net>
//
// Licensed under the MIT license.
// Please see the LICENSE file is this directory.

package printer

import (
	"encoding/json"
	"fmt"
	"io"
	"os"
	"reflect"
	"text/template"

	"github.com/McKael/madon"
)

// TemplatePrinter represents a Template printer
type TemplatePrinter struct {
	rawTemplate string
	template    *template.Template
}

// NewPrinterTemplate returns a Template ResourcePrinter
// For TemplatePrinter, the option parameter contains the template string.
func NewPrinterTemplate(option string) (*TemplatePrinter, error) {
	tmpl := option
	t, err := template.New("output").Parse(tmpl)
	if err != nil {
		return nil, err
	}
	return &TemplatePrinter{
		rawTemplate: tmpl,
		template:    t,
	}, nil
}

// PrintObj sends the object as text to the writer
// If the writer w is nil, standard output will be used.
func (p *TemplatePrinter) PrintObj(obj interface{}, w io.Writer, tmpl string) error {
	if w == nil {
		w = os.Stdout
	}

	if p.template == nil {
		return fmt.Errorf("template not built")
	}

	switch ot := obj.(type) { // I wish I knew a better way...
	case []madon.Account, []madon.Application, []madon.Attachment, []madon.Card,
		[]madon.Client, []madon.Context, []madon.Instance, []madon.Mention,
		[]madon.Notification, []madon.Relationship, []madon.Report,
		[]madon.Results, []madon.Status, []madon.StreamEvent, []madon.Tag:
		return p.templateForeach(ot, w)
	}

	return p.templatePrintSingleObj(obj, w)
}

func (p *TemplatePrinter) templatePrintSingleObj(obj interface{}, w io.Writer) error {
	// This code comes from Kubernetes.
	data, err := json.Marshal(obj)
	if err != nil {
		return err
	}
	out := map[string]interface{}{}
	if err := json.Unmarshal(data, &out); err != nil {
		return err
	}
	if err = p.safeExecute(w, out); err != nil {
		return fmt.Errorf("error executing template %q: %v", p.rawTemplate, err)
	}
	return nil
}

// safeExecute tries to execute the template, but catches panics and returns an error
// should the template engine panic.
// This code comes from Kubernetes.
func (p *TemplatePrinter) safeExecute(w io.Writer, obj interface{}) error {
	var panicErr error
	// Sorry for the double anonymous function. There's probably a clever way
	// to do this that has the defer'd func setting the value to be returned, but
	// that would be even less obvious.
	retErr := func() error {
		defer func() {
			if x := recover(); x != nil {
				panicErr = fmt.Errorf("caught panic: %+v", x)
			}
		}()
		return p.template.Execute(w, obj)
	}()
	if panicErr != nil {
		return panicErr
	}
	return retErr
}

func (p *TemplatePrinter) templateForeach(ol interface{}, w io.Writer) error {
	switch reflect.TypeOf(ol).Kind() {
	case reflect.Slice:
		s := reflect.ValueOf(ol)

		for i := 0; i < s.Len(); i++ {
			o := s.Index(i).Interface()
			if err := p.templatePrintSingleObj(o, w); err != nil {
				return err
			}
		}
	}
	return nil
}