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
	 rmanach
						rmanach