Merge pull request #8077 from jwhonce/wip/report

Refactor podman to use c/common/pkg/report
This commit is contained in:
OpenShift Merge Robot
2020-10-21 17:59:56 -04:00
committed by GitHub
35 changed files with 429 additions and 179 deletions

View File

@ -1,4 +1,4 @@
package report
package common
import (
"fmt"

View File

@ -1,9 +1,9 @@
package containers
import (
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@ -54,10 +54,10 @@ func diff(cmd *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(diffOpts.Format):
return report.ChangesToJSON(results)
case report.IsJSON(diffOpts.Format):
return common.ChangesToJSON(results)
case diffOpts.Format == "":
return report.ChangesToTable(results)
return common.ChangesToTable(results)
default:
return errors.New("only supported value for '--format' is 'json'")
}

View File

@ -6,7 +6,7 @@ import (
"text/tabwriter"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
@ -97,7 +97,7 @@ func mount(_ *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(mountOpts.Format):
case report.IsJSON(mountOpts.Format):
return printJSON(reports)
case mountOpts.Format == "":
break // print defaults

View File

@ -11,9 +11,8 @@ import (
"time"
tm "github.com/buger/goterm"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
@ -92,7 +91,7 @@ func checkFlags(c *cobra.Command) error {
if listOpts.Size || listOpts.Namespace {
return errors.Errorf("quiet conflicts with size and namespace")
}
if c.Flag("format").Changed && !parse.MatchesJSONFormat(listOpts.Format) {
if c.Flag("format").Changed && !report.IsJSON(listOpts.Format) {
// Quiet is overridden by Go template output.
listOpts.Quiet = false
}
@ -179,7 +178,7 @@ func ps(cmd *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(listOpts.Format):
case report.IsJSON(listOpts.Format):
return jsonOut(listContainers)
case listOpts.Quiet:
return quietOut(listContainers)

View File

@ -7,9 +7,8 @@ import (
"text/template"
tm "github.com/buger/goterm"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/cgroups"
@ -157,7 +156,7 @@ func outputStats(reports []define.ContainerStats) error {
for _, r := range reports {
stats = append(stats, containerStats{r})
}
if parse.MatchesJSONFormat(statsOptions.Format) {
if report.IsJSON(statsOptions.Format) {
return outputJSON(stats)
}
format := defaultStatsRow
@ -240,9 +239,9 @@ func combineHumanValues(a, b uint64) string {
func outputJSON(stats []containerStats) error {
type jstat struct {
Id string `json:"id"` //nolint
Id string `json:"id"` // nolint
Name string `json:"name"`
CpuPercent string `json:"cpu_percent"` //nolint
CpuPercent string `json:"cpu_percent"` // nolint
MemUsage string `json:"mem_usage"`
MemPerc string `json:"mem_percent"`
NetIO string `json:"net_io"`

View File

@ -6,7 +6,7 @@ import (
"os"
"path/filepath"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
@ -63,7 +63,7 @@ func systemd(cmd *cobra.Command, args []string) error {
logrus.Warnln("The generated units should be placed on your remote system")
}
report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
reports, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
if err != nil {
return err
}
@ -73,7 +73,7 @@ func systemd(cmd *cobra.Command, args []string) error {
if err != nil {
return errors.Wrap(err, "error getting current working directory")
}
for name, content := range report.Units {
for name, content := range reports.Units {
path := filepath.Join(cwd, fmt.Sprintf("%s.service", name))
f, err := os.Create(path)
if err != nil {
@ -94,15 +94,15 @@ func systemd(cmd *cobra.Command, args []string) error {
}
// modify in place so we can print the
// paths when --files is set
report.Units[name] = path
reports.Units[name] = path
}
}
switch {
case parse.MatchesJSONFormat(format):
return printJSON(report.Units)
case report.IsJSON(format):
return printJSON(reports.Units)
case format == "":
return printDefault(report.Units)
return printDefault(reports.Units)
default:
return errors.Errorf("unknown --format argument: %s", format)
}

View File

@ -1,9 +1,9 @@
package images
import (
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -51,10 +51,10 @@ func diff(cmd *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(diffOpts.Format):
return report.ChangesToJSON(results)
case report.IsJSON(diffOpts.Format):
return common.ChangesToJSON(results)
case diffOpts.Format == "":
return report.ChangesToTable(results)
return common.ChangesToTable(results)
default:
return errors.New("only supported value for '--format' is 'json'")
}

View File

@ -10,9 +10,8 @@ import (
"time"
"unicode"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/docker/go-units"
"github.com/pkg/errors"
@ -81,7 +80,7 @@ func history(cmd *cobra.Command, args []string) error {
return err
}
if parse.MatchesJSONFormat(opts.format) {
if report.IsJSON(opts.format) {
var err error
if len(results.Layers) == 0 {
_, err = fmt.Fprintf(os.Stdout, "[]\n")

View File

@ -10,10 +10,9 @@ import (
"time"
"unicode"
"github.com/containers/common/pkg/report"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/docker/go-units"
"github.com/pkg/errors"
@ -108,7 +107,7 @@ func images(cmd *cobra.Command, args []string) error {
switch {
case listFlag.quiet:
return writeID(imgs)
case parse.MatchesJSONFormat(listFlag.format):
case report.IsJSON(listFlag.format):
return writeJSON(imgs)
default:
if cmd.Flag("format").Changed {

View File

@ -6,7 +6,7 @@ import (
"text/tabwriter"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
@ -80,7 +80,7 @@ func mount(cmd *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(mountOpts.Format):
case report.IsJSON(mountOpts.Format):
return printJSON(reports)
case mountOpts.Format == "":
break // default format

View File

@ -6,9 +6,9 @@ import (
"text/template"
"github.com/containers/common/pkg/auth"
"github.com/containers/common/pkg/report"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"

View File

@ -9,9 +9,8 @@ import (
"text/tabwriter"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@ -143,7 +142,7 @@ func (i *inspector) inspect(namesOrIDs []string) error {
var err error
switch {
case parse.MatchesJSONFormat(i.options.Format) || i.options.Format == "":
case report.IsJSON(i.options.Format) || i.options.Format == "":
err = printJSON(data)
default:
row := inspectNormalize(i.options.Format)

View File

@ -34,9 +34,9 @@ func networkCreateFlags(flags *pflag.FlagSet) {
flags.IPNetVar(&networkCreateOptions.Range, "ip-range", net.IPNet{}, "allocate container IP from range")
flags.StringVar(&networkCreateOptions.MacVLAN, "macvlan", "", "create a Macvlan connection based on this device")
// TODO not supported yet
//flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver")
// flags.StringVar(&networkCreateOptions.IPamDriver, "ipam-driver", "", "IP Address Management Driver")
// TODO enable when IPv6 is working
//flags.BoolVar(&networkCreateOptions.IPV6, "IPv6", false, "enable IPv6 networking")
// flags.BoolVar(&networkCreateOptions.IPV6, "IPv6", false, "enable IPv6 networking")
flags.IPNetVar(&networkCreateOptions.Subnet, "subnet", net.IPNet{}, "subnet in CIDR format")
flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin")
}

View File

@ -7,9 +7,8 @@ import (
"text/tabwriter"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/spf13/cobra"
)
@ -47,7 +46,7 @@ func networkInspect(_ *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(networkInspectOptions.Format) || networkInspectOptions.Format == "":
case report.IsJSON(networkInspectOptions.Format) || networkInspectOptions.Format == "":
b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err

View File

@ -36,7 +36,7 @@ var (
func networkListFlags(flags *pflag.FlagSet) {
// TODO enable filters based on something
//flags.StringSliceVarP(&networklistCommand.Filter, "filter", "f", []string{}, "Pause all running containers")
// flags.StringSliceVarP(&networklistCommand.Filter, "filter", "f", []string{}, "Pause all running containers")
flags.StringVarP(&networkListOptions.Format, "format", "f", "", "Pretty-print networks to JSON or using a Go template")
flags.BoolVarP(&networkListOptions.Quiet, "quiet", "q", false, "display only names")
flags.StringVarP(&networkListOptions.Filter, "filter", "", "", "Provide filter values (e.g. 'name=podman')")

View File

@ -7,9 +7,8 @@ import (
"text/tabwriter"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@ -62,7 +61,7 @@ func inspect(cmd *cobra.Command, args []string) error {
return err
}
if parse.MatchesJSONFormat(inspectOptions.Format) {
if report.IsJSON(inspectOptions.Format) {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(responses)

View File

@ -10,9 +10,8 @@ import (
"text/template"
"time"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/docker/go-units"
@ -85,7 +84,7 @@ func pods(cmd *cobra.Command, _ []string) error {
}
switch {
case parse.MatchesJSONFormat(psInput.Format):
case report.IsJSON(psInput.Format):
b, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return err

View File

@ -9,9 +9,8 @@ import (
"time"
"github.com/buger/goterm"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/spf13/cobra"
@ -66,7 +65,7 @@ func stats(cmd *cobra.Command, args []string) error {
}
row := report.NormalizeFormat(statsOptions.Format)
doJSON := parse.MatchesJSONFormat(row)
doJSON := report.IsJSON(row)
headers := report.Headers(entities.PodStatsReport{}, map[string]string{
"CPU": "CPU %",

View File

@ -1,68 +0,0 @@
package report
import (
"reflect"
"strings"
)
// tableReplacer will remove 'table ' prefix and clean up tabs
var tableReplacer = strings.NewReplacer(
"table ", "",
`\t`, "\t",
`\n`, "\n",
" ", "\t",
)
// escapedReplacer will clean up escaped characters from CLI
var escapedReplacer = strings.NewReplacer(
`\t`, "\t",
`\n`, "\n",
)
// NormalizeFormat reads given go template format provided by CLI and munges it into what we need
func NormalizeFormat(format string) string {
f := format
// two replacers used so we only remove the prefix keyword `table`
if strings.HasPrefix(f, "table ") {
f = tableReplacer.Replace(f)
} else {
f = escapedReplacer.Replace(format)
}
if !strings.HasSuffix(f, "\n") {
f += "\n"
}
return f
}
// Headers queries the interface for field names
func Headers(object interface{}, overrides map[string]string) []map[string]string {
value := reflect.ValueOf(object)
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
// Column header will be field name upper-cased.
headers := make(map[string]string, value.NumField())
for i := 0; i < value.Type().NumField(); i++ {
field := value.Type().Field(i)
// Recurse to find field names from promoted structs
if field.Type.Kind() == reflect.Struct && field.Anonymous {
h := Headers(reflect.New(field.Type).Interface(), nil)
for k, v := range h[0] {
headers[k] = v
}
continue
}
headers[field.Name] = strings.ToUpper(field.Name)
}
if len(overrides) > 0 {
// Override column header as provided
for k, v := range overrides {
headers[k] = strings.ToUpper(v)
}
}
return []map[string]string{headers}
}

View File

@ -1,35 +0,0 @@
package report
import (
"strings"
"testing"
)
func TestNormalizeFormat(t *testing.T) {
cases := []struct {
format string
expected string
}{
{"table {{.ID}}", "{{.ID}}\n"},
{"table {{.ID}} {{.C}}", "{{.ID}}\t{{.C}}\n"},
{"{{.ID}}", "{{.ID}}\n"},
{"{{.ID}}\n", "{{.ID}}\n"},
{"{{.ID}} {{.C}}", "{{.ID}} {{.C}}\n"},
{"\t{{.ID}}", "\t{{.ID}}\n"},
{`\t` + "{{.ID}}", "\t{{.ID}}\n"},
{"table {{.ID}}\t{{.C}}", "{{.ID}}\t{{.C}}\n"},
{"{{.ID}} table {{.C}}", "{{.ID}} table {{.C}}\n"},
}
for _, tc := range cases {
tc := tc
label := strings.ReplaceAll(tc.format, " ", "<sp>")
t.Run("NormalizeFormat/"+label, func(t *testing.T) {
t.Parallel()
actual := NormalizeFormat(tc.format)
if actual != tc.expected {
t.Errorf("Expected %q, actual %q", tc.expected, actual)
}
})
}
}

View File

@ -1,6 +0,0 @@
package report
import "github.com/containers/podman/v2/cmd/podman/registry"
// Pull in configured json library
var json = registry.JSONLibrary()

View File

@ -8,8 +8,8 @@ import (
"text/template"
"time"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/docker/go-units"

View File

@ -6,7 +6,7 @@ import (
"os"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/libpod/events"
@ -65,7 +65,7 @@ func eventsCmd(cmd *cobra.Command, _ []string) error {
)
if cmd.Flags().Changed("format") {
doJSON = parse.MatchesJSONFormat(eventFormat)
doJSON = report.IsJSON(eventFormat)
if !doJSON {
var err error
tmpl, err = template.New("events").Parse(eventFormat)

View File

@ -5,7 +5,7 @@ import (
"os"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
@ -70,7 +70,7 @@ func info(cmd *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(inFormat):
case report.IsJSON(inFormat):
b, err := json.MarshalIndent(info, "", " ")
if err != nil {
return err

View File

@ -8,9 +8,8 @@ import (
"text/tabwriter"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/domain/entities"
@ -42,7 +41,7 @@ func version(cmd *cobra.Command, args []string) error {
return err
}
if parse.MatchesJSONFormat(versionFormat) {
if report.IsJSON(versionFormat) {
s, err := json.MarshalToString(versions)
if err != nil {
return err

View File

@ -5,9 +5,8 @@ import (
"os"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -55,7 +54,7 @@ func inspect(cmd *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(inspectFormat), inspectFormat == "":
case report.IsJSON(inspectFormat), inspectFormat == "":
jsonOut, err := json.MarshalIndent(responses, "", " ")
if err != nil {
return errors.Wrapf(err, "error marshalling inspect JSON")

View File

@ -8,9 +8,8 @@ import (
"text/tabwriter"
"text/template"
"github.com/containers/podman/v2/cmd/podman/parse"
"github.com/containers/common/pkg/report"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/report"
"github.com/containers/podman/v2/cmd/podman/validate"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
@ -75,7 +74,7 @@ func list(cmd *cobra.Command, args []string) error {
}
switch {
case parse.MatchesJSONFormat(cliOpts.Format):
case report.IsJSON(cliOpts.Format):
return outputJSON(responses)
case len(responses) < 1:
return nil

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2015 Fatih Arslan
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,58 @@
# CamelCase [![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/fatih/camelcase) [![Build Status](http://img.shields.io/travis/fatih/camelcase.svg?style=flat-square)](https://travis-ci.org/fatih/camelcase)
CamelCase is a Golang (Go) package to split the words of a camelcase type
string into a slice of words. It can be used to convert a camelcase word (lower
or upper case) into any type of word.
## Splitting rules:
1. If string is not valid UTF-8, return it without splitting as
single item array.
2. Assign all unicode characters into one of 4 sets: lower case
letters, upper case letters, numbers, and all other characters.
3. Iterate through characters of string, introducing splits
between adjacent characters that belong to different sets.
4. Iterate through array of split strings, and if a given string
is upper case:
* if subsequent string is lower case:
* move last character of upper case string to beginning of
lower case string
## Install
```bash
go get github.com/fatih/camelcase
```
## Usage and examples
```go
splitted := camelcase.Split("GolangPackage")
fmt.Println(splitted[0], splitted[1]) // prints: "Golang", "Package"
```
Both lower camel case and upper camel case are supported. For more info please
check: [http://en.wikipedia.org/wiki/CamelCase](http://en.wikipedia.org/wiki/CamelCase)
Below are some example cases:
```
"" => []
"lowercase" => ["lowercase"]
"Class" => ["Class"]
"MyClass" => ["My", "Class"]
"MyC" => ["My", "C"]
"HTML" => ["HTML"]
"PDFLoader" => ["PDF", "Loader"]
"AString" => ["A", "String"]
"SimpleXMLParser" => ["Simple", "XML", "Parser"]
"vimRPCPlugin" => ["vim", "RPC", "Plugin"]
"GL11Version" => ["GL", "11", "Version"]
"99Bottles" => ["99", "Bottles"]
"May5" => ["May", "5"]
"BFG9000" => ["BFG", "9000"]
"BöseÜberraschung" => ["Böse", "Überraschung"]
"Two spaces" => ["Two", " ", "spaces"]
"BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
```

View File

@ -0,0 +1,91 @@
// Package camelcase is a micro package to split the words of a camelcase type
// string into a slice of words.
package camelcase
import (
"unicode"
"unicode/utf8"
)
// Split splits the camelcase word and returns a list of words. It also
// supports digits. Both lower camel case and upper camel case are supported.
// For more info please check: http://en.wikipedia.org/wiki/CamelCase
//
// Examples
//
// "" => [""]
// "lowercase" => ["lowercase"]
// "Class" => ["Class"]
// "MyClass" => ["My", "Class"]
// "MyC" => ["My", "C"]
// "HTML" => ["HTML"]
// "PDFLoader" => ["PDF", "Loader"]
// "AString" => ["A", "String"]
// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
// "GL11Version" => ["GL", "11", "Version"]
// "99Bottles" => ["99", "Bottles"]
// "May5" => ["May", "5"]
// "BFG9000" => ["BFG", "9000"]
// "BöseÜberraschung" => ["Böse", "Überraschung"]
// "Two spaces" => ["Two", " ", "spaces"]
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
//
// Splitting rules
//
// 1) If string is not valid UTF-8, return it without splitting as
// single item array.
// 2) Assign all unicode characters into one of 4 sets: lower case
// letters, upper case letters, numbers, and all other characters.
// 3) Iterate through characters of string, introducing splits
// between adjacent characters that belong to different sets.
// 4) Iterate through array of split strings, and if a given string
// is upper case:
// if subsequent string is lower case:
// move last character of upper case string to beginning of
// lower case string
func Split(src string) (entries []string) {
// don't split invalid utf8
if !utf8.ValidString(src) {
return []string{src}
}
entries = []string{}
var runes [][]rune
lastClass := 0
class := 0
// split into fields based on class of unicode character
for _, r := range src {
switch {
case unicode.IsLower(r):
class = 1
case unicode.IsUpper(r):
class = 2
case unicode.IsDigit(r):
class = 3
default:
class = 4
}
if class == lastClass {
runes[len(runes)-1] = append(runes[len(runes)-1], r)
} else {
runes = append(runes, []rune{r})
}
lastClass = class
}
// handle upper case -> lower case sequences, e.g.
// "PDFL", "oader" -> "PDF", "Loader"
for i := 0; i < len(runes)-1; i++ {
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
runes[i] = runes[i][:len(runes[i])-1]
}
}
// construct []string from results
for _, s := range runes {
if len(s) > 0 {
entries = append(entries, string(s))
}
}
return entries
}

46
vendor/github.com/containers/common/pkg/report/doc.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
/*
Package report provides helper structs/methods/funcs for formatting output
To format output for an array of structs:
w := report.NewWriterDefault(os.Stdout)
defer w.Flush()
headers := report.Headers(struct {
ID string
}{}, nil)
t, _ := report.NewTemplate("command name").Parse("{{range .}}{{.ID}}{{end}}")
t.Execute(t, headers)
t.Execute(t, map[string]string{
"ID":"fa85da03b40141899f3af3de6d27852b",
})
// t.IsTable() == false
or
w := report.NewWriterDefault(os.Stdout)
defer w.Flush()
headers := report.Headers(struct {
CID string
}{}, map[string]string{
"CID":"ID"})
t, _ := report.NewTemplate("command name").Parse("table {{.CID}}")
t.Execute(t, headers)
t.Execute(t,map[string]string{
"CID":"fa85da03b40141899f3af3de6d27852b",
})
// t.IsTable() == true
Helpers:
if report.IsJSON(cmd.Flag("format").Value.String()) {
... process JSON and output
}
and
Note: Your code should not ignore errors
*/
package report

View File

@ -0,0 +1,114 @@
package report
import (
"reflect"
"strings"
"text/template"
"github.com/containers/common/pkg/report/camelcase"
)
// Template embeds template.Template to add functionality to methods
type Template struct {
*template.Template
isTable bool
}
// FuncMap is aliased from template.FuncMap
type FuncMap template.FuncMap
// tableReplacer will remove 'table ' prefix and clean up tabs
var tableReplacer = strings.NewReplacer(
"table ", "",
`\t`, "\t",
`\n`, "\n",
" ", "\t",
)
// escapedReplacer will clean up escaped characters from CLI
var escapedReplacer = strings.NewReplacer(
`\t`, "\t",
`\n`, "\n",
)
// NormalizeFormat reads given go template format provided by CLI and munges it into what we need
func NormalizeFormat(format string) string {
var f string
// two replacers used so we only remove the prefix keyword `table`
if strings.HasPrefix(format, "table ") {
f = tableReplacer.Replace(format)
} else {
f = escapedReplacer.Replace(format)
}
if !strings.HasSuffix(f, "\n") {
f += "\n"
}
return f
}
// Headers queries the interface for field names.
// Array of map is returned to support range templates
// Note: unexported fields can be supported by adding field to overrides
// Note: It is left to the developer to write out said headers
// Podman commands use the general rules of:
// 1) unchanged --format includes headers
// 2) --format '{{.ID}" # no headers
// 3) --format 'table {{.ID}}' # includes headers
func Headers(object interface{}, overrides map[string]string) []map[string]string {
value := reflect.ValueOf(object)
if value.Kind() == reflect.Ptr {
value = value.Elem()
}
// Column header will be field name upper-cased.
headers := make(map[string]string, value.NumField())
for i := 0; i < value.Type().NumField(); i++ {
field := value.Type().Field(i)
// Recurse to find field names from promoted structs
if field.Type.Kind() == reflect.Struct && field.Anonymous {
h := Headers(reflect.New(field.Type).Interface(), nil)
for k, v := range h[0] {
headers[k] = v
}
continue
}
name := strings.Join(camelcase.Split(field.Name), " ")
headers[field.Name] = strings.ToUpper(name)
}
if len(overrides) > 0 {
// Override column header as provided
for k, v := range overrides {
headers[k] = strings.ToUpper(v)
}
}
return []map[string]string{headers}
}
// NewTemplate creates a new template object
func NewTemplate(name string) *Template {
return &Template{template.New(name), false}
}
// Parse parses text as a template body for t
func (t *Template) Parse(text string) (*Template, error) {
if strings.HasPrefix(text, "table ") {
t.isTable = true
text = "{{range .}}" + NormalizeFormat(text) + "{{end}}"
}
tt, err := t.Template.Parse(text)
return &Template{tt, t.isTable}, err
}
// Funcs adds the elements of the argument map to the template's function map
func (t *Template) Funcs(funcMap FuncMap) *Template {
return &Template{t.Template.Funcs(template.FuncMap(funcMap)), t.isTable}
}
// IsTable returns true if format string defines a "table"
func (t *Template) IsTable() bool {
return t.isTable
}

View File

@ -0,0 +1,13 @@
package report
import "regexp"
var jsonRegex = regexp.MustCompile(`^\s*(json|{{\s*json\s*(\.)?\s*}})\s*$`)
// JSONFormat test CLI --format string to be a JSON request
// if report.IsJSON(cmd.Flag("format").Value.String()) {
// ... process JSON and output
// }
func IsJSON(s string) bool {
return jsonRegex.MatchString(s)
}

View File

@ -0,0 +1,27 @@
package report
import (
"io"
"text/tabwriter"
)
// Writer aliases tabwriter.Writer to provide Podman defaults
type Writer struct {
*tabwriter.Writer
}
// NewWriter initializes a new report.Writer with given values
func NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) (*Writer, error) {
t := tabwriter.NewWriter(output, minwidth, tabwidth, padding, padchar, flags)
return &Writer{t}, nil
}
// NewWriterDefault initializes a new report.Writer with Podman defaults
func NewWriterDefault(output io.Writer) (*Writer, error) {
return NewWriter(output, 12, 2, 2, ' ', 0)
}
// Flush any output left in buffers
func (w *Writer) Flush() error {
return w.Writer.Flush()
}

2
vendor/modules.txt vendored
View File

@ -94,6 +94,8 @@ github.com/containers/common/pkg/capabilities
github.com/containers/common/pkg/cgroupv2
github.com/containers/common/pkg/completion
github.com/containers/common/pkg/config
github.com/containers/common/pkg/report
github.com/containers/common/pkg/report/camelcase
github.com/containers/common/pkg/retry
github.com/containers/common/pkg/seccomp
github.com/containers/common/pkg/sysinfo