format table + add extra fields
This commit is contained in:
parent
64f7f4c131
commit
bfaf6f10c6
218
docker/models.go
218
docker/models.go
@ -12,7 +12,7 @@ const nbImageParts = 2
|
|||||||
|
|
||||||
type ServiceStatus string
|
type ServiceStatus string
|
||||||
|
|
||||||
// docker engine service status
|
// Docker engine service status
|
||||||
const (
|
const (
|
||||||
Pending ServiceStatus = "pending"
|
Pending ServiceStatus = "pending"
|
||||||
Restarting ServiceStatus = "preparing"
|
Restarting ServiceStatus = "preparing"
|
||||||
@ -80,146 +80,19 @@ type ServicePort struct {
|
|||||||
Published int `json:"PublishedPort"`
|
Published int `json:"PublishedPort"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type serviceCommandInput struct {
|
|
||||||
States []serviceState `json:"services"`
|
|
||||||
Details []serviceDetails `json:"details"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
ID string
|
|
||||||
App string
|
|
||||||
Name string
|
|
||||||
Image struct {
|
|
||||||
Name string
|
|
||||||
Tag string
|
|
||||||
}
|
|
||||||
Replicas []Replicas
|
|
||||||
Ports []ServicePort
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) UnmarshalJSON(data []byte) error {
|
|
||||||
ci := serviceCommandInput{}
|
|
||||||
if err := json.Unmarshal(data, &ci); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ci.Details) == 0 {
|
|
||||||
return fmt.Errorf("service details must no be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
nbReplicas := ci.Details[0].Spec.Mode.Replicated.Replicas
|
|
||||||
if len(ci.States) < nbReplicas {
|
|
||||||
return fmt.Errorf("must have %d replicas but have %d", nbReplicas, len(ci.States))
|
|
||||||
}
|
|
||||||
|
|
||||||
replicas := getReplicas(ci.States, nbReplicas)
|
|
||||||
|
|
||||||
imageName := ci.Details[0].Spec.Labels.Image
|
|
||||||
imageNameParts := strings.Split(imageName, ":")
|
|
||||||
if len(imageNameParts) == nbImageParts {
|
|
||||||
imageName = imageNameParts[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
appName := ci.Details[0].Spec.Labels.Namespace
|
|
||||||
|
|
||||||
*s = Service{
|
|
||||||
ID: ci.Details[0].ID,
|
|
||||||
App: appName,
|
|
||||||
Name: strings.TrimPrefix(ci.Details[0].Spec.Name, appName+"_"),
|
|
||||||
Image: struct {
|
|
||||||
Name string
|
|
||||||
Tag string
|
|
||||||
}{
|
|
||||||
Name: imageName,
|
|
||||||
Tag: imageNameParts[1],
|
|
||||||
},
|
|
||||||
Ports: ci.Details[0].Endpoint.Ports,
|
|
||||||
Replicas: replicas,
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Service) String() string {
|
|
||||||
data := []string{}
|
|
||||||
fl := fmt.Sprintf(
|
|
||||||
"%-20s | %-20s | %-30s | %-10s | %-5d",
|
|
||||||
s.App,
|
|
||||||
s.Name,
|
|
||||||
s.Image.Name,
|
|
||||||
s.Image.Tag,
|
|
||||||
len(s.Replicas),
|
|
||||||
)
|
|
||||||
data = append(data, fl)
|
|
||||||
|
|
||||||
for idx := range s.Replicas {
|
|
||||||
data = append(data, fmt.Sprintf(
|
|
||||||
"%-20s \\ %-20d | %-30s | %-30s",
|
|
||||||
"",
|
|
||||||
s.Replicas[idx].Pos,
|
|
||||||
s.Replicas[idx].State,
|
|
||||||
s.Replicas[idx].Error,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(data, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Services []Service
|
|
||||||
|
|
||||||
func (s Services) GetIDs() []string {
|
|
||||||
ids := []string{}
|
|
||||||
for idx := range s {
|
|
||||||
ids = append(ids, s[idx].ID)
|
|
||||||
}
|
|
||||||
return ids
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Services) String() string {
|
|
||||||
data := []string{}
|
|
||||||
header := fmt.Sprintf(
|
|
||||||
"%-20s | %-20s | %-30s | %-10s | %-5s",
|
|
||||||
"Service name",
|
|
||||||
"Name",
|
|
||||||
"Image name",
|
|
||||||
"Image Tag",
|
|
||||||
"Replicas",
|
|
||||||
)
|
|
||||||
subheader := fmt.Sprintf(
|
|
||||||
"%-20s \\ %-20s | %-30s | %-30s",
|
|
||||||
"",
|
|
||||||
"Replicas",
|
|
||||||
"Status",
|
|
||||||
"Error",
|
|
||||||
)
|
|
||||||
hr := ""
|
|
||||||
for i := 0; i < len(header); i++ {
|
|
||||||
hr += "*"
|
|
||||||
}
|
|
||||||
subhr := ""
|
|
||||||
for i := 0; i < len(header); i++ {
|
|
||||||
subhr += "-"
|
|
||||||
}
|
|
||||||
data = append(data, hr)
|
|
||||||
data = append(data, header)
|
|
||||||
data = append(data, subheader)
|
|
||||||
data = append(data, hr)
|
|
||||||
for idx := range s {
|
|
||||||
data = append(data, s[idx].String())
|
|
||||||
data = append(data, subhr)
|
|
||||||
}
|
|
||||||
return strings.Join(data, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
type serviceDetails struct {
|
type serviceDetails struct {
|
||||||
ID string `json:"ID"`
|
ID string `json:"ID"`
|
||||||
CreateAt time.Time `json:"CreatedAt"`
|
CreateAt time.Time `json:"CreatedAt"`
|
||||||
UpdatedAt time.Time `json:"UpdatedAt"`
|
UpdatedAt time.Time `json:"UpdatedAt"`
|
||||||
Spec struct {
|
Spec struct { //nolint:govet // fieldalignment
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
|
TaskTemplate struct {
|
||||||
|
Networks []struct {
|
||||||
|
ID string `json:"Target"`
|
||||||
|
} `json:"Networks"`
|
||||||
|
} `json:"TaskTemplate"`
|
||||||
Labels struct {
|
Labels struct {
|
||||||
Image string `json:"com.docker.stack.image"`
|
Image string `json:"com.docker.stack.image"`
|
||||||
Tag string
|
|
||||||
Namespace string `json:"com.docker.stack.namespace"`
|
Namespace string `json:"com.docker.stack.namespace"`
|
||||||
} `json:"Labels"`
|
} `json:"Labels"`
|
||||||
Mode struct {
|
Mode struct {
|
||||||
@ -266,3 +139,80 @@ func (st *serviceState) UnmarshalJSON(data []byte) error {
|
|||||||
*st = (serviceState)(state)
|
*st = (serviceState)(state)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type serviceCommandInput struct {
|
||||||
|
States []serviceState `json:"services"`
|
||||||
|
Details []serviceDetails `json:"details"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
ID string
|
||||||
|
App string
|
||||||
|
Name string
|
||||||
|
Image struct {
|
||||||
|
Name string
|
||||||
|
Tag string
|
||||||
|
}
|
||||||
|
Networks []string
|
||||||
|
Replicas []Replicas
|
||||||
|
Ports []ServicePort
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UnmarshalJSON(data []byte) error {
|
||||||
|
ci := serviceCommandInput{}
|
||||||
|
if err := json.Unmarshal(data, &ci); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ci.Details) == 0 {
|
||||||
|
return fmt.Errorf("service details must no be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
nbReplicas := ci.Details[0].Spec.Mode.Replicated.Replicas
|
||||||
|
if len(ci.States) < nbReplicas {
|
||||||
|
return fmt.Errorf("must have %d replicas but have %d", nbReplicas, len(ci.States))
|
||||||
|
}
|
||||||
|
|
||||||
|
networks := []string{}
|
||||||
|
for idx := range ci.Details[0].Spec.TaskTemplate.Networks {
|
||||||
|
networks = append(networks, ci.Details[0].Spec.TaskTemplate.Networks[idx].ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
replicas := getReplicas(ci.States, nbReplicas)
|
||||||
|
|
||||||
|
imageName := ci.Details[0].Spec.Labels.Image
|
||||||
|
imageNameParts := strings.Split(imageName, ":")
|
||||||
|
if len(imageNameParts) == nbImageParts {
|
||||||
|
imageName = imageNameParts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
appName := ci.Details[0].Spec.Labels.Namespace
|
||||||
|
|
||||||
|
*s = Service{
|
||||||
|
ID: ci.Details[0].ID,
|
||||||
|
App: appName,
|
||||||
|
Name: strings.TrimPrefix(ci.Details[0].Spec.Name, appName+"_"),
|
||||||
|
Image: struct {
|
||||||
|
Name string
|
||||||
|
Tag string
|
||||||
|
}{
|
||||||
|
Name: imageName,
|
||||||
|
Tag: imageNameParts[1],
|
||||||
|
},
|
||||||
|
Networks: networks,
|
||||||
|
Ports: ci.Details[0].Endpoint.Ports,
|
||||||
|
Replicas: replicas,
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Services []Service
|
||||||
|
|
||||||
|
func (s Services) GetIDs() []string {
|
||||||
|
ids := []string{}
|
||||||
|
for idx := range s {
|
||||||
|
ids = append(ids, s[idx].ID)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|||||||
74
main.go
74
main.go
@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -240,6 +241,7 @@ func initDeployers(
|
|||||||
return deps, nil
|
return deps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:funlen,mnd // TODO(rmanach): could be splitted
|
||||||
func getSwarmServicesDetails(hm *models.HMMap) error {
|
func getSwarmServicesDetails(hm *models.HMMap) error {
|
||||||
swarmNet := hm.GetSwarmNetInfo()
|
swarmNet := hm.GetSwarmNetInfo()
|
||||||
if swarmNet == nil {
|
if swarmNet == nil {
|
||||||
@ -260,35 +262,26 @@ func getSwarmServicesDetails(hm *models.HMMap) error {
|
|||||||
utils.WithColSeparator(" | "),
|
utils.WithColSeparator(" | "),
|
||||||
utils.WithHeaderBorderStyle("*"),
|
utils.WithHeaderBorderStyle("*"),
|
||||||
utils.WithRowSeparator("-"),
|
utils.WithRowSeparator("-"),
|
||||||
utils.WithHeader("App", 20),
|
utils.WithHeader("App", 15),
|
||||||
utils.WithHeader("Name", 20),
|
utils.WithHeader("Name", 15),
|
||||||
utils.WithHeader("Image", 20),
|
utils.WithHeader("Image", 25),
|
||||||
utils.WithHeader("Tag", 20),
|
utils.WithHeader("Tag", 10),
|
||||||
utils.WithHeader("Target-Published", 40),
|
utils.WithHeader("Target->Published", 30),
|
||||||
utils.WithHeader("Replicas", 2),
|
utils.WithHeader("Networks", 20),
|
||||||
|
utils.WithHeader("Replicas", 10),
|
||||||
utils.WithHeader("Status", 10),
|
utils.WithHeader("Status", 10),
|
||||||
|
utils.WithHeader("Error", 20),
|
||||||
)
|
)
|
||||||
|
|
||||||
for idx := range services {
|
for idx := range services {
|
||||||
columns := []utils.Column{}
|
columns := []utils.Column{}
|
||||||
columns = append(
|
columns = append(
|
||||||
columns,
|
columns,
|
||||||
utils.Column{
|
utils.NewColumn("app", services[idx].App),
|
||||||
Name: "app",
|
utils.NewColumn("name", services[idx].Name),
|
||||||
Value: services[idx].App,
|
utils.NewColumn("image", services[idx].Image.Name),
|
||||||
},
|
utils.NewColumn("tag", services[idx].Image.Tag),
|
||||||
utils.Column{
|
utils.NewColumn("networks", strings.Join(services[idx].Networks, ", ")),
|
||||||
Name: "name",
|
|
||||||
Value: services[idx].Name,
|
|
||||||
},
|
|
||||||
utils.Column{
|
|
||||||
Name: "image",
|
|
||||||
Value: services[idx].Image.Name,
|
|
||||||
},
|
|
||||||
utils.Column{
|
|
||||||
Name: "tag",
|
|
||||||
Value: services[idx].Image.Tag,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ports := []string{}
|
ports := []string{}
|
||||||
@ -296,22 +289,45 @@ func getSwarmServicesDetails(hm *models.HMMap) error {
|
|||||||
ports = append(
|
ports = append(
|
||||||
ports,
|
ports,
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"%d-%d",
|
"%d->%d",
|
||||||
services[idx].Ports[idy].Target,
|
services[idx].Ports[idy].Target,
|
||||||
services[idx].Ports[idy].Published,
|
services[idx].Ports[idy].Published,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
columns = append(columns, utils.Column{
|
columns = append(columns, utils.NewColumn("target->published", strings.Join(ports, ", ")))
|
||||||
Name: "target-published",
|
|
||||||
Value: strings.Join(ports, ","),
|
|
||||||
})
|
|
||||||
|
|
||||||
tb.AddRow(columns...)
|
colSubLines := []utils.Column{}
|
||||||
|
for idy := range services[idx].Replicas {
|
||||||
|
nbCol := utils.NewColumn("replicas", strconv.Itoa(services[idx].Replicas[idy].Pos))
|
||||||
|
statusCol := utils.NewColumn("status", string(services[idx].Replicas[idy].State))
|
||||||
|
errorCol := utils.NewColumn("error", services[idx].Replicas[idy].Error)
|
||||||
|
|
||||||
|
if idy == 0 {
|
||||||
|
columns = append(columns, nbCol, statusCol, errorCol)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
tb.Render()
|
|
||||||
|
|
||||||
|
colSubLines = append(colSubLines, nbCol, statusCol, errorCol)
|
||||||
|
}
|
||||||
|
|
||||||
|
mainRow, err := tb.AddRow(columns...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
subRow, err := tb.AddRow(colSubLines...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(colSubLines) > 0 {
|
||||||
|
mainRow.AddNext(subRow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tb.Render()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
125
utils/utils.go
125
utils/utils.go
@ -121,67 +121,80 @@ func CatchAndConvertNumber(value string) (int, error) {
|
|||||||
return strconv.Atoi(string(buf))
|
return strconv.Atoi(string(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
type HeaderColumn struct {
|
type headerColumn struct {
|
||||||
Name string
|
name string
|
||||||
Length int
|
length int
|
||||||
pos int
|
pos int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Header struct {
|
type header struct {
|
||||||
BorderStyle string
|
borderStyle string
|
||||||
headerMeta map[string]*HeaderColumn
|
headerMeta map[string]*headerColumn
|
||||||
Columns []HeaderColumn
|
columns []headerColumn
|
||||||
}
|
}
|
||||||
|
|
||||||
type TableOption func(*Table)
|
type TableOption func(*Table)
|
||||||
|
|
||||||
func WithHeaderBorderStyle(style string) TableOption {
|
func WithHeaderBorderStyle(style string) TableOption {
|
||||||
return func(t *Table) {
|
return func(t *Table) {
|
||||||
t.Header.BorderStyle = style
|
t.header.borderStyle = style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithRowSeparator(separator string) TableOption {
|
func WithRowSeparator(separator string) TableOption {
|
||||||
return func(t *Table) {
|
return func(t *Table) {
|
||||||
t.RowSeparator = separator
|
t.rowSeparator = separator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithColSeparator(separator string) TableOption {
|
func WithColSeparator(separator string) TableOption {
|
||||||
return func(t *Table) {
|
return func(t *Table) {
|
||||||
t.ColSeparator = separator
|
t.colSeparator = separator
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithHeader(name string, length int) TableOption {
|
func WithHeader(name string, length int) TableOption {
|
||||||
return func(t *Table) {
|
return func(t *Table) {
|
||||||
if t.Header.Columns == nil {
|
if t.header.columns == nil {
|
||||||
t.Header.Columns = []HeaderColumn{}
|
t.header.columns = []headerColumn{}
|
||||||
}
|
}
|
||||||
pos := len(t.Header.Columns)
|
pos := len(t.header.columns)
|
||||||
t.Header.Columns = append(
|
t.header.columns = append(
|
||||||
t.Header.Columns,
|
t.header.columns,
|
||||||
HeaderColumn{Name: name, Length: length, pos: pos},
|
headerColumn{name: name, length: length, pos: pos},
|
||||||
)
|
)
|
||||||
|
|
||||||
if t.Header.headerMeta == nil {
|
if t.header.headerMeta == nil {
|
||||||
t.Header.headerMeta = map[string]*HeaderColumn{}
|
t.header.headerMeta = map[string]*headerColumn{}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Header.headerMeta[strings.ToLower(name)] = &t.Header.Columns[pos]
|
t.header.headerMeta[strings.ToLower(name)] = &t.header.columns[pos]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Column struct {
|
type Column struct {
|
||||||
Name string
|
name string
|
||||||
Value string
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewColumn(name, value string) Column {
|
||||||
|
return Column{name: name, value: value}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Row struct {
|
||||||
|
next *Row // useful for grouping rows
|
||||||
|
columns []Column
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Row) AddNext(row *Row) {
|
||||||
|
r.next = row
|
||||||
}
|
}
|
||||||
|
|
||||||
type Table struct {
|
type Table struct {
|
||||||
RowSeparator string
|
rowSeparator string
|
||||||
ColSeparator string
|
colSeparator string
|
||||||
data [][]string
|
rows []*Row
|
||||||
Header Header
|
header header
|
||||||
cursor int
|
cursor int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,40 +204,46 @@ func NewTable(options ...TableOption) Table {
|
|||||||
o(&table)
|
o(&table)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.data = [][]string{}
|
table.rows = []*Row{}
|
||||||
|
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) AddRow(columns ...Column) error {
|
func (t *Table) AddRow(columns ...Column) (*Row, error) {
|
||||||
maxNbCols := len(t.Header.Columns)
|
if len(columns) == 0 {
|
||||||
if len(columns) > maxNbCols {
|
return &Row{}, nil
|
||||||
return fmt.Errorf("invalid column number, should be %d", maxNbCols)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rowData := make([]string, maxNbCols)
|
maxNbCols := len(t.header.columns)
|
||||||
t.data = append(t.data, rowData)
|
if len(columns) > maxNbCols {
|
||||||
|
return nil, fmt.Errorf("invalid column number, should be %d", maxNbCols)
|
||||||
|
}
|
||||||
|
|
||||||
|
row := &Row{columns: make([]Column, maxNbCols)}
|
||||||
|
t.rows = append(t.rows, row)
|
||||||
|
|
||||||
for idx := range columns {
|
for idx := range columns {
|
||||||
header, ok := t.Header.headerMeta[strings.ToLower(columns[idx].Name)]
|
header, ok := t.header.headerMeta[strings.ToLower(columns[idx].name)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("no corresponding %s column exist", columns[idx].Name)
|
return nil, fmt.Errorf("no corresponding %s column exist", columns[idx].name)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := columns[idx].Value
|
value := columns[idx].value
|
||||||
if len(value) > header.Length {
|
if len(value) > header.length {
|
||||||
log.Debug().
|
log.Debug().
|
||||||
Str("column", columns[idx].Name).
|
Str("column", columns[idx].name).
|
||||||
Str("value", value).
|
Str("value", value).
|
||||||
Msg("col value too long, trimming...")
|
Msg("col value too long, trimming...")
|
||||||
value = value[:header.Length-3] + "..."
|
value = value[:header.length-3] + "..."
|
||||||
}
|
}
|
||||||
|
|
||||||
t.data[t.cursor][header.pos] = value
|
row.columns[header.pos].value = value
|
||||||
|
row.columns[header.pos].name = columns[idx].name
|
||||||
|
|
||||||
}
|
}
|
||||||
t.cursor++
|
t.cursor++
|
||||||
|
|
||||||
return nil
|
return row, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) Render() {
|
func (t *Table) Render() {
|
||||||
@ -232,39 +251,43 @@ func (t *Table) Render() {
|
|||||||
|
|
||||||
// header
|
// header
|
||||||
headerParts := []string{}
|
headerParts := []string{}
|
||||||
for idx := range t.Header.Columns {
|
for idx := range t.header.columns {
|
||||||
headerParts = append(
|
headerParts = append(
|
||||||
headerParts,
|
headerParts,
|
||||||
fmt.Sprintf("%-*s", t.Header.Columns[idx].Length, t.Header.Columns[idx].Name),
|
fmt.Sprintf("%-*s", t.header.columns[idx].length, t.header.columns[idx].name),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
header := strings.Join(headerParts, t.ColSeparator)
|
header := strings.Join(headerParts, t.colSeparator)
|
||||||
border := ""
|
border := ""
|
||||||
for i := 0; i < len(header); i++ {
|
for i := 0; i < len(header); i++ {
|
||||||
border += t.Header.BorderStyle
|
border += t.header.borderStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
rowHr := ""
|
rowHr := ""
|
||||||
for i := 0; i < len(header); i++ {
|
for i := 0; i < len(header); i++ {
|
||||||
rowHr += t.RowSeparator
|
rowHr += t.rowSeparator
|
||||||
}
|
}
|
||||||
|
|
||||||
table = append(table, border, header, border)
|
table = append(table, border, header, border)
|
||||||
|
|
||||||
// data
|
// data
|
||||||
for idx := range t.data {
|
for idx := range t.rows {
|
||||||
lineParts := []string{}
|
lineParts := []string{}
|
||||||
for idy := range t.data[idx] {
|
for idy := range t.rows[idx].columns {
|
||||||
lineParts = append(lineParts, fmt.Sprintf(
|
lineParts = append(lineParts, fmt.Sprintf(
|
||||||
"%-*s",
|
"%-*s",
|
||||||
t.Header.Columns[idy].Length,
|
t.header.columns[idy].length,
|
||||||
t.data[idx][idy]),
|
t.rows[idx].columns[idy].value),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if idx+1 < len(t.data) {
|
if idx+1 < len(t.rows) {
|
||||||
table = append(table, strings.Join(lineParts, t.ColSeparator), rowHr)
|
if t.rows[idx].next != nil {
|
||||||
|
table = append(table, strings.Join(lineParts, t.colSeparator))
|
||||||
|
} else {
|
||||||
|
table = append(table, strings.Join(lineParts, t.colSeparator), rowHr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user