Merge pull request #6242 from nalind/common-formats
Use containers/common's formats package instead of our own
This commit is contained in:
		
						commit
						9f6205610c
					
				|  | @ -8,8 +8,8 @@ import ( | |||
| 
 | ||||
| 	"github.com/containers/buildah" | ||||
| 	"github.com/containers/buildah/define" | ||||
| 	"github.com/containers/buildah/pkg/formats" | ||||
| 	"github.com/containers/buildah/util" | ||||
| 	"github.com/containers/common/pkg/formats" | ||||
| 	"github.com/containers/storage" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  |  | |||
|  | @ -10,9 +10,9 @@ import ( | |||
| 	"time" | ||||
| 
 | ||||
| 	buildahcli "github.com/containers/buildah/pkg/cli" | ||||
| 	"github.com/containers/buildah/pkg/formats" | ||||
| 	"github.com/containers/buildah/pkg/parse" | ||||
| 	"github.com/containers/common/libimage" | ||||
| 	"github.com/containers/common/pkg/formats" | ||||
| 	"github.com/docker/go-units" | ||||
| 	"github.com/spf13/cobra" | ||||
| ) | ||||
|  |  | |||
|  | @ -6,10 +6,10 @@ import ( | |||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"runtime" | ||||
| 	"text/template" | ||||
| 
 | ||||
| 	"github.com/containers/buildah" | ||||
| 	"github.com/containers/buildah/define" | ||||
| 	"github.com/containers/common/pkg/formats" | ||||
| 	"github.com/spf13/cobra" | ||||
| 	"golang.org/x/term" | ||||
| ) | ||||
|  | @ -71,7 +71,7 @@ func infoCmd(c *cobra.Command, iopts infoResults) error { | |||
| 		} else if !matched { | ||||
| 			return fmt.Errorf("invalid format provided: %s", format) | ||||
| 		} | ||||
| 		t, err := template.New("format").Parse(format) | ||||
| 		t, err := formats.NewParse("info", format) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("template parsing error: %w", err) | ||||
| 		} | ||||
|  |  | |||
|  | @ -6,11 +6,11 @@ import ( | |||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"text/template" | ||||
| 
 | ||||
| 	"github.com/containers/buildah" | ||||
| 	buildahcli "github.com/containers/buildah/pkg/cli" | ||||
| 	"github.com/containers/buildah/pkg/parse" | ||||
| 	"github.com/containers/common/pkg/formats" | ||||
| 	"github.com/spf13/cobra" | ||||
| 	"golang.org/x/term" | ||||
| ) | ||||
|  | @ -113,7 +113,7 @@ func inspectCmd(c *cobra.Command, args []string, iopts inspectResults) error { | |||
| 		} else if !matched { | ||||
| 			return fmt.Errorf("invalid format provided: %s", format) | ||||
| 		} | ||||
| 		t, err := template.New("format").Parse(format) | ||||
| 		t, err := formats.NewParse("inspect", format) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("template parsing error: %w", err) | ||||
| 		} | ||||
|  |  | |||
							
								
								
									
										2
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										2
									
								
								go.mod
								
								
								
								
							|  | @ -41,7 +41,6 @@ require ( | |||
| 	golang.org/x/sync v0.15.0 | ||||
| 	golang.org/x/sys v0.33.0 | ||||
| 	golang.org/x/term v0.32.0 | ||||
| 	sigs.k8s.io/yaml v1.4.0 | ||||
| 	tags.cncf.io/container-device-interface v1.0.1 | ||||
| ) | ||||
| 
 | ||||
|  | @ -156,5 +155,6 @@ require ( | |||
| 	google.golang.org/protobuf v1.36.6 // indirect | ||||
| 	gopkg.in/yaml.v3 v3.0.1 // indirect | ||||
| 	k8s.io/klog v1.0.0 // indirect | ||||
| 	sigs.k8s.io/yaml v1.4.0 // indirect | ||||
| 	tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect | ||||
| ) | ||||
|  |  | |||
|  | @ -0,0 +1,2 @@ | |||
| // The formats package is an API compatibility wrapper which merely calls into github.com/containers/common/pkg/formats.  Newer code should instead call that package directly.
 | ||||
| package formats | ||||
|  | @ -1,166 +1,30 @@ | |||
| package formats | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"text/tabwriter" | ||||
| 	"text/template" | ||||
| 
 | ||||
| 	"golang.org/x/term" | ||||
| 	"sigs.k8s.io/yaml" | ||||
| 	"github.com/containers/common/pkg/formats" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// JSONString const to save on duplicate variable names
 | ||||
| 	JSONString = "json" | ||||
| 	JSONString = formats.JSONString | ||||
| 	// IDString const to save on duplicates for Go templates
 | ||||
| 	IDString = "{{.ID}}" | ||||
| 
 | ||||
| 	parsingErrorStr = "Template parsing error" | ||||
| 	IDString = formats.IDString | ||||
| ) | ||||
| 
 | ||||
| // Writer interface for outputs
 | ||||
| type Writer interface { | ||||
| 	Out() error | ||||
| } | ||||
| type Writer = formats.Writer | ||||
| 
 | ||||
| // JSONStructArray for JSON output
 | ||||
| type JSONStructArray struct { | ||||
| 	Output []any | ||||
| } | ||||
| type JSONStructArray = formats.JSONStructArray | ||||
| 
 | ||||
| // StdoutTemplateArray for Go template output
 | ||||
| type StdoutTemplateArray struct { | ||||
| 	Output   []any | ||||
| 	Template string | ||||
| 	Fields   map[string]string | ||||
| } | ||||
| type StdoutTemplateArray = formats.StdoutTemplateArray | ||||
| 
 | ||||
| // JSONStruct for JSON output
 | ||||
| type JSONStruct struct { | ||||
| 	Output any | ||||
| } | ||||
| type JSONStruct = formats.JSONStruct | ||||
| 
 | ||||
| // StdoutTemplate for Go template output
 | ||||
| type StdoutTemplate struct { | ||||
| 	Output   any | ||||
| 	Template string | ||||
| 	Fields   map[string]string | ||||
| } | ||||
| type StdoutTemplate = formats.StdoutTemplate | ||||
| 
 | ||||
| // YAMLStruct for YAML output
 | ||||
| type YAMLStruct struct { | ||||
| 	Output any | ||||
| } | ||||
| 
 | ||||
| func setJSONFormatEncoder(isTerminal bool, w io.Writer) *json.Encoder { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	enc.SetIndent("", "    ") | ||||
| 	if isTerminal { | ||||
| 		enc.SetEscapeHTML(false) | ||||
| 	} | ||||
| 	return enc | ||||
| } | ||||
| 
 | ||||
| // Out method for JSON Arrays
 | ||||
| func (j JSONStructArray) Out() error { | ||||
| 	buf := bytes.NewBuffer(nil) | ||||
| 	enc := setJSONFormatEncoder(term.IsTerminal(int(os.Stdout.Fd())), buf) | ||||
| 	if err := enc.Encode(j.Output); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	data := buf.Bytes() | ||||
| 
 | ||||
| 	// JSON returns a byte array with a literal null [110 117 108 108] in it
 | ||||
| 	// if it is passed empty data.  We used bytes.Compare to see if that is
 | ||||
| 	// the case.
 | ||||
| 	if diff := bytes.Compare(data, []byte("null")); diff == 0 { | ||||
| 		data = []byte("[]") | ||||
| 	} | ||||
| 
 | ||||
| 	// If the we did get NULL back, we should spit out {} which is
 | ||||
| 	// at least valid JSON for the consumer.
 | ||||
| 	fmt.Printf("%s", data) | ||||
| 	humanNewLine() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Out method for Go templates
 | ||||
| func (t StdoutTemplateArray) Out() error { | ||||
| 	w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) | ||||
| 	if strings.HasPrefix(t.Template, "table") { | ||||
| 		// replace any spaces with tabs in template so that tabwriter can align it
 | ||||
| 		t.Template = strings.ReplaceAll(strings.TrimSpace(t.Template[5:]), " ", "\t") | ||||
| 		headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("%v: %w", parsingErrorStr, err) | ||||
| 		} | ||||
| 		err = headerTmpl.Execute(w, t.Fields) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprintln(w, "") | ||||
| 	} | ||||
| 	t.Template = strings.ReplaceAll(t.Template, " ", "\t") | ||||
| 	tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("%v: %w", parsingErrorStr, err) | ||||
| 	} | ||||
| 	for _, raw := range t.Output { | ||||
| 		basicTmpl := tmpl.Funcs(basicFunctions) | ||||
| 		if err := basicTmpl.Execute(w, raw); err != nil { | ||||
| 			return fmt.Errorf("%v: %w", parsingErrorStr, err) | ||||
| 		} | ||||
| 		fmt.Fprintln(w, "") | ||||
| 	} | ||||
| 	return w.Flush() | ||||
| } | ||||
| 
 | ||||
| // Out method for JSON struct
 | ||||
| func (j JSONStruct) Out() error { | ||||
| 	data, err := json.MarshalIndent(j.Output, "", "    ") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Printf("%s", data) | ||||
| 	humanNewLine() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Out method for Go templates
 | ||||
| func (t StdoutTemplate) Out() error { | ||||
| 	tmpl, err := template.New("image").Parse(t.Template) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("template parsing error: %w", err) | ||||
| 	} | ||||
| 	err = tmpl.Execute(os.Stdout, t.Output) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	humanNewLine() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Out method for YAML
 | ||||
| func (y YAMLStruct) Out() error { | ||||
| 	var buf []byte | ||||
| 	var err error | ||||
| 	buf, err = yaml.Marshal(y.Output) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Printf("%s", string(buf)) | ||||
| 	humanNewLine() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // humanNewLine prints a new line at the end of the output only if stdout is the terminal
 | ||||
| func humanNewLine() { | ||||
| 	if term.IsTerminal(int(os.Stdout.Fd())) { | ||||
| 		fmt.Println() | ||||
| 	} | ||||
| } | ||||
| type YAMLStruct = formats.YAMLStruct | ||||
|  |  | |||
|  | @ -1,45 +0,0 @@ | |||
| package formats | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
| 
 | ||||
| type ImageData struct { | ||||
| 	Author string `json:"Author"` | ||||
| } | ||||
| 
 | ||||
| func TestSetJSONFormatEncoder(t *testing.T) { | ||||
| 	t.Parallel() | ||||
| 	tt := []struct { | ||||
| 		name       string | ||||
| 		imageData  *ImageData | ||||
| 		expected   string | ||||
| 		isTerminal bool | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:       "HTML tags are not escaped", | ||||
| 			imageData:  &ImageData{Author: "dave <dave@corp.io>"}, | ||||
| 			expected:   `"Author": "dave <dave@corp.io>"`, | ||||
| 			isTerminal: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:       "HTML tags are escaped", | ||||
| 			imageData:  &ImageData{Author: "dave <dave@corp.io>"}, | ||||
| 			expected:   `"Author": "dave \u003cdave@corp.io\u003e"`, | ||||
| 			isTerminal: false, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for _, tc := range tt { | ||||
| 		buf := bytes.NewBuffer(nil) | ||||
| 		enc := setJSONFormatEncoder(tc.isTerminal, buf) | ||||
| 		if err := enc.Encode(tc.imageData); err != nil { | ||||
| 			t.Errorf("test %#v failed encoding: %s", tc.name, err) | ||||
| 		} | ||||
| 		if !strings.Contains(buf.String(), tc.expected) { | ||||
| 			t.Errorf("test %#v expected output to contain %#v. Output:\n%v\n", tc.name, tc.expected, buf.String()) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | @ -1,82 +1,19 @@ | |||
| package formats | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
| 
 | ||||
| 	"github.com/containers/common/pkg/formats" | ||||
| ) | ||||
| 
 | ||||
| // basicFunctions are the set of initial
 | ||||
| // functions provided to every template.
 | ||||
| var basicFunctions = template.FuncMap{ | ||||
| 	"json": func(v any) string { | ||||
| 		buf := &bytes.Buffer{} | ||||
| 		enc := json.NewEncoder(buf) | ||||
| 		enc.SetEscapeHTML(false) | ||||
| 		_ = enc.Encode(v) | ||||
| 		// Remove the trailing new line added by the encoder
 | ||||
| 		return strings.TrimSpace(buf.String()) | ||||
| 	}, | ||||
| 	"split": strings.Split, | ||||
| 	"join":  strings.Join, | ||||
| 	// strings.Title is deprecated since go 1.18
 | ||||
| 	// However for our use case it is still fine. The recommended replacement
 | ||||
| 	// is adding about 400kb binary size so lets keep using this for now.
 | ||||
| 	//nolint:staticcheck
 | ||||
| 	"title":    strings.Title, | ||||
| 	"lower":    strings.ToLower, | ||||
| 	"upper":    strings.ToUpper, | ||||
| 	"pad":      padWithSpace, | ||||
| 	"truncate": truncateWithLength, | ||||
| } | ||||
| 
 | ||||
| // HeaderFunctions are used to created headers of a table.
 | ||||
| // This is a replacement of basicFunctions for header generation
 | ||||
| // because we want the header to remain intact.
 | ||||
| // Some functions like `split` are irrelevant so not added.
 | ||||
| var headerFunctions = template.FuncMap{ | ||||
| 	"json": func(v string) string { | ||||
| 		return v | ||||
| 	}, | ||||
| 	"title": func(v string) string { | ||||
| 		return v | ||||
| 	}, | ||||
| 	"lower": func(v string) string { | ||||
| 		return v | ||||
| 	}, | ||||
| 	"upper": func(v string) string { | ||||
| 		return v | ||||
| 	}, | ||||
| 	"truncate": func(v string, _ int) string { | ||||
| 		return v | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| // Parse creates a new anonymous template with the basic functions
 | ||||
| // and parses the given format.
 | ||||
| func Parse(format string) (*template.Template, error) { | ||||
| 	return NewParse("", format) | ||||
| 	return formats.Parse(format) | ||||
| } | ||||
| 
 | ||||
| // NewParse creates a new tagged template with the basic functions
 | ||||
| // and parses the given format.
 | ||||
| func NewParse(tag, format string) (*template.Template, error) { | ||||
| 	return template.New(tag).Funcs(basicFunctions).Parse(format) | ||||
| } | ||||
| 
 | ||||
| // padWithSpace adds whitespace to the input if the input is non-empty
 | ||||
| func padWithSpace(source string, prefix, suffix int) string { | ||||
| 	if source == "" { | ||||
| 		return source | ||||
| 	} | ||||
| 	return strings.Repeat(" ", prefix) + source + strings.Repeat(" ", suffix) | ||||
| } | ||||
| 
 | ||||
| // truncateWithLength truncates the source string up to the length provided by the input
 | ||||
| func truncateWithLength(source string, length int) string { | ||||
| 	if len(source) < length { | ||||
| 		return source | ||||
| 	} | ||||
| 	return source[:length] | ||||
| 	return formats.NewParse(tag, format) | ||||
| } | ||||
|  |  | |||
|  | @ -28,8 +28,8 @@ load helpers | |||
|   run_buildah inspect --type image --format '{{.OCIv1.Config}}' alpine-image | ||||
|   inspect_after_commit=$output | ||||
| 
 | ||||
|   # ...except that at some point in November 2019 buildah-inspect started | ||||
|   # including version. Strip it out, | ||||
|   # ...except that in #2510/#3036/#3829 we started adding a label with | ||||
|   # buildah's version. Strip it out for comparison. | ||||
|   run_buildah --version | ||||
|   local -a output_fields=($output) | ||||
|   buildah_version=${output_fields[2]} | ||||
|  |  | |||
|  | @ -0,0 +1,166 @@ | |||
| package formats | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"text/tabwriter" | ||||
| 	"text/template" | ||||
| 
 | ||||
| 	terminal "golang.org/x/term" | ||||
| 	"sigs.k8s.io/yaml" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	// JSONString const to save on duplicate variable names
 | ||||
| 	JSONString = "json" | ||||
| 	// IDString const to save on duplicates for Go templates
 | ||||
| 	IDString = "{{.ID}}" | ||||
| 
 | ||||
| 	parsingErrorStr = "Template parsing error" | ||||
| ) | ||||
| 
 | ||||
| // Writer interface for outputs
 | ||||
| type Writer interface { | ||||
| 	Out() error | ||||
| } | ||||
| 
 | ||||
| // JSONStructArray for JSON output
 | ||||
| type JSONStructArray struct { | ||||
| 	Output []any | ||||
| } | ||||
| 
 | ||||
| // StdoutTemplateArray for Go template output
 | ||||
| type StdoutTemplateArray struct { | ||||
| 	Output   []any | ||||
| 	Template string | ||||
| 	Fields   map[string]string | ||||
| } | ||||
| 
 | ||||
| // JSONStruct for JSON output
 | ||||
| type JSONStruct struct { | ||||
| 	Output any | ||||
| } | ||||
| 
 | ||||
| // StdoutTemplate for Go template output
 | ||||
| type StdoutTemplate struct { | ||||
| 	Output   any | ||||
| 	Template string | ||||
| 	Fields   map[string]string | ||||
| } | ||||
| 
 | ||||
| // YAMLStruct for YAML output
 | ||||
| type YAMLStruct struct { | ||||
| 	Output any | ||||
| } | ||||
| 
 | ||||
| func setJSONFormatEncoder(isTerminal bool, w io.Writer) *json.Encoder { | ||||
| 	enc := json.NewEncoder(w) | ||||
| 	enc.SetIndent("", "    ") | ||||
| 	if isTerminal { | ||||
| 		enc.SetEscapeHTML(false) | ||||
| 	} | ||||
| 	return enc | ||||
| } | ||||
| 
 | ||||
| // Out method for JSON Arrays
 | ||||
| func (j JSONStructArray) Out() error { | ||||
| 	buf := bytes.NewBuffer(nil) | ||||
| 	enc := setJSONFormatEncoder(terminal.IsTerminal(int(os.Stdout.Fd())), buf) | ||||
| 	if err := enc.Encode(j.Output); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	data := buf.Bytes() | ||||
| 
 | ||||
| 	// JSON returns a byte array with a literal null [110 117 108 108] in it
 | ||||
| 	// if it is passed empty data.  We used bytes.Compare to see if that is
 | ||||
| 	// the case.
 | ||||
| 	if diff := bytes.Compare(data, []byte("null")); diff == 0 { | ||||
| 		data = []byte("[]") | ||||
| 	} | ||||
| 
 | ||||
| 	// If the we did get NULL back, we should spit out {} which is
 | ||||
| 	// at least valid JSON for the consumer.
 | ||||
| 	fmt.Printf("%s", data) //nolint:forbidigo
 | ||||
| 	humanNewLine() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Out method for Go templates
 | ||||
| func (t StdoutTemplateArray) Out() error { | ||||
| 	w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) | ||||
| 	if strings.HasPrefix(t.Template, "table") { | ||||
| 		// replace any spaces with tabs in template so that tabwriter can align it
 | ||||
| 		t.Template = strings.ReplaceAll(strings.TrimSpace(t.Template[5:]), " ", "\t") | ||||
| 		headerTmpl, err := template.New("header").Funcs(headerFunctions).Parse(t.Template) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("%s: %w", parsingErrorStr, err) | ||||
| 		} | ||||
| 		err = headerTmpl.Execute(w, t.Fields) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprintln(w, "") | ||||
| 	} | ||||
| 	t.Template = strings.ReplaceAll(t.Template, " ", "\t") | ||||
| 	tmpl, err := template.New("image").Funcs(basicFunctions).Parse(t.Template) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("%s: %w", parsingErrorStr, err) | ||||
| 	} | ||||
| 	for _, raw := range t.Output { | ||||
| 		basicTmpl := tmpl.Funcs(basicFunctions) | ||||
| 		if err := basicTmpl.Execute(w, raw); err != nil { | ||||
| 			return fmt.Errorf("%s: %w", parsingErrorStr, err) | ||||
| 		} | ||||
| 		fmt.Fprintln(w, "") | ||||
| 	} | ||||
| 	return w.Flush() | ||||
| } | ||||
| 
 | ||||
| // Out method for JSON struct
 | ||||
| func (j JSONStruct) Out() error { | ||||
| 	data, err := json.MarshalIndent(j.Output, "", "    ") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Printf("%s", data) //nolint:forbidigo
 | ||||
| 	humanNewLine() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Out method for Go templates
 | ||||
| func (t StdoutTemplate) Out() error { | ||||
| 	tmpl, err := template.New("image").Parse(t.Template) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("template parsing error: %w", err) | ||||
| 	} | ||||
| 	err = tmpl.Execute(os.Stdout, t.Output) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	humanNewLine() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // Out method for YAML
 | ||||
| func (y YAMLStruct) Out() error { | ||||
| 	var buf []byte | ||||
| 	var err error | ||||
| 	buf, err = yaml.Marshal(y.Output) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Printf("%s", string(buf)) //nolint:forbidigo
 | ||||
| 	humanNewLine() | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // humanNewLine prints a new line at the end of the output only if stdout is the terminal
 | ||||
| func humanNewLine() { | ||||
| 	if terminal.IsTerminal(int(os.Stdout.Fd())) { | ||||
| 		fmt.Println() //nolint:forbidigo
 | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,78 @@ | |||
| package formats | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
| ) | ||||
| 
 | ||||
| // basicFunctions are the set of initial
 | ||||
| // functions provided to every template.
 | ||||
| var basicFunctions = template.FuncMap{ | ||||
| 	"json": func(v any) string { | ||||
| 		buf := &bytes.Buffer{} | ||||
| 		enc := json.NewEncoder(buf) | ||||
| 		enc.SetEscapeHTML(false) | ||||
| 		_ = enc.Encode(v) | ||||
| 		// Remove the trailing new line added by the encoder
 | ||||
| 		return strings.TrimSpace(buf.String()) | ||||
| 	}, | ||||
| 	"split":    strings.Split, | ||||
| 	"join":     strings.Join, | ||||
| 	"title":    strings.Title, //nolint:staticcheck
 | ||||
| 	"lower":    strings.ToLower, | ||||
| 	"upper":    strings.ToUpper, | ||||
| 	"pad":      padWithSpace, | ||||
| 	"truncate": truncateWithLength, | ||||
| } | ||||
| 
 | ||||
| // HeaderFunctions are used to created headers of a table.
 | ||||
| // This is a replacement of basicFunctions for header generation
 | ||||
| // because we want the header to remain intact.
 | ||||
| // Some functions like `split` are irrelevant so not added.
 | ||||
| var headerFunctions = template.FuncMap{ | ||||
| 	"json": func(v string) string { | ||||
| 		return v | ||||
| 	}, | ||||
| 	"title": func(v string) string { | ||||
| 		return v | ||||
| 	}, | ||||
| 	"lower": func(v string) string { | ||||
| 		return v | ||||
| 	}, | ||||
| 	"upper": func(v string) string { | ||||
| 		return v | ||||
| 	}, | ||||
| 	"truncate": func(v string, _ int) string { | ||||
| 		return v | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| // Parse creates a new anonymous template with the basic functions
 | ||||
| // and parses the given format.
 | ||||
| func Parse(format string) (*template.Template, error) { | ||||
| 	return NewParse("", format) | ||||
| } | ||||
| 
 | ||||
| // NewParse creates a new tagged template with the basic functions
 | ||||
| // and parses the given format.
 | ||||
| func NewParse(tag, format string) (*template.Template, error) { | ||||
| 	return template.New(tag).Funcs(basicFunctions).Parse(format) | ||||
| } | ||||
| 
 | ||||
| // padWithSpace adds whitespace to the input if the input is non-empty
 | ||||
| func padWithSpace(source string, prefix, suffix int) string { | ||||
| 	if source == "" { | ||||
| 		return source | ||||
| 	} | ||||
| 	return strings.Repeat(" ", prefix) + source + strings.Repeat(" ", suffix) | ||||
| } | ||||
| 
 | ||||
| // truncateWithLength truncates the source string up to the length provided by the input
 | ||||
| func truncateWithLength(source string, length int) string { | ||||
| 	if len(source) < length { | ||||
| 		return source | ||||
| 	} | ||||
| 	return source[:length] | ||||
| } | ||||
|  | @ -131,6 +131,7 @@ github.com/containers/common/pkg/completion | |||
| github.com/containers/common/pkg/config | ||||
| github.com/containers/common/pkg/download | ||||
| github.com/containers/common/pkg/filters | ||||
| github.com/containers/common/pkg/formats | ||||
| github.com/containers/common/pkg/hooks | ||||
| github.com/containers/common/pkg/hooks/0.1.0 | ||||
| github.com/containers/common/pkg/hooks/1.0.0 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue