Коммит 88f1026c создал по автору keewek's avatar keewek
Просмотр файлов

release: v1.0.0

владелец a706ce8c
┌─────────────────────────────────────────────────────┐
│ LongestPlayDescription: play #1 (demo): Stats │
│ LongestPlayDescriptionLength: 21 │
│ LongestPlayTags: [] │
│ LongestPlayTagsLength: 2 │
│ LongestTaskBlock: Task 1.1 │
│ LongestTaskBlockLength: 8 │
│ LongestTaskName: 你好 │
│ LongestTaskNameLength: 4 │
│ LongestTaskDescription: Task 1.3: 你好 │
│ LongestTaskDescriptionLength: 14 │
│ LongestTaskTags: [] │
│ LongestTaskTagsLength: 2 │
└─────────────────────────────────────────────────────┘
playbook: playbooks/demo/playbook_demo.yml
play #1 (demo): Stats TAGS: []
tasks:
Task 1.1: ABC TAGS: []
Task 1.2: АБВ TAGS: []
Task 1.3: 你好 TAGS: []
+-----------------------------------------------------+
| LongestPlayDescription: play #1 (demo): Stats |
| LongestPlayDescriptionLength: 21 |
| LongestPlayTags: [] |
| LongestPlayTagsLength: 2 |
| LongestTaskBlock: Task 1.1 |
| LongestTaskBlockLength: 8 |
| LongestTaskName: ABC |
| LongestTaskNameLength: 3 |
| LongestTaskDescription: Task 1.1: ABC |
| LongestTaskDescriptionLength: 13 |
| LongestTaskTags: [] |
| LongestTaskTagsLength: 2 |
+-----------------------------------------------------+
playbook: playbooks/demo/playbook_demo.yml
play #1 (demo): Stats TAGS: []
tasks:
Task 1.1: ABC TAGS: []
Task 1.2: АБВ TAGS: []
Task 1.3: 你好 TAGS: []
┌─────────────────────────────────────────────────────┐
│ LongestPlayDescription: play #1 (demo): Stats │
│ LongestPlayDescriptionLength: 21 │
│ LongestPlayTags: [] │
│ LongestPlayTagsLength: 2 │
│ LongestTaskBlock: Task 1.1 │
│ LongestTaskBlockLength: 8 │
│ LongestTaskName: ABC │
│ LongestTaskNameLength: 3 │
│ LongestTaskDescription: Task 1.1: ABC │
│ LongestTaskDescriptionLength: 13 │
│ LongestTaskTags: [] │
│ LongestTaskTagsLength: 2 │
└─────────────────────────────────────────────────────┘
playbook: playbooks/demo/playbook_demo.yml
play #1 (demo): Stats TAGS: []
tasks:
Task 1.1: ABC TAGS: []
Task 1.2: АБВ TAGS: []
Task 1.3: 你好 TAGS: []
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package cmn
import (
"fmt"
"os"
"runtime/debug"
)
func WillUseVar(v ...any) {
// Do nothing, just silence UnusedVar error
fmt.Fprintln(os.Stderr, "!!!")
fmt.Fprintln(os.Stderr, "!!! Silencing UnusedVar errors !!!")
fmt.Fprintln(os.Stderr, "!!!")
}
func VcsRevision() (rev string, ok bool) {
if info, ok := debug.ReadBuildInfo(); ok {
for _, setting := range info.Settings {
if setting.Key == "vcs.revision" {
return setting.Value, true
}
}
}
return rev, ok
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package eol
type EndOfLine string
const (
CRLF string = "\r\n"
LF string = "\n"
)
func (e EndOfLine) String() string {
switch e {
case "":
return LF
}
return string(e)
}
func CrLf() EndOfLine {
return EndOfLine(CRLF)
}
func Lf() EndOfLine {
return EndOfLine(LF)
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package eol
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func Test_EndOfLine(t *testing.T) {
t.Run("CRLF", func(t *testing.T) {
want := "\r\n"
got := CRLF
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
})
t.Run("LF", func(t *testing.T) {
want := "\n"
got := LF
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
})
t.Run("CrLf", func(t *testing.T) {
eol := CrLf()
want := "\r\n"
got := eol.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
got2 := string(eol)
if diff := cmp.Diff(want, got2); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
})
t.Run("Lf", func(t *testing.T) {
eol := Lf()
want := "\n"
got := eol.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
got2 := string(eol)
if diff := cmp.Diff(want, got2); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
})
t.Run("Default is LF", func(t *testing.T) {
var eol EndOfLine
want := "\n"
got := eol.String()
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
})
t.Run("Zero value cast to string is empty", func(t *testing.T) {
var eol EndOfLine
want := ""
got := string(eol)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
})
t.Run("Custom value", func(t *testing.T) {
var eol EndOfLine = "#END#"
want := "#END#"
got := string(eol)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
got2 := string(eol)
if diff := cmp.Diff(want, got2); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
})
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package flags
import (
"flag"
)
var activeFlags map[string]struct{} = make(map[string]struct{})
// func init() {
// ReScan()
// }
func ReScan() {
DisableAll()
flag.Visit(func(f *flag.Flag) {
activeFlags[f.Name] = struct{}{}
})
}
func IsSet(name string) bool {
if len(activeFlags) == 0 {
ReScan()
}
_, ok := activeFlags[name]
return ok
}
func Enable(name ...string) {
for _, name := range name {
activeFlags[name] = struct{}{}
}
}
func EnableAll() {
flag.VisitAll(func(f *flag.Flag) {
activeFlags[f.Name] = struct{}{}
})
}
func Disable(name ...string) {
for _, name := range name {
delete(activeFlags, name)
}
}
func DisableAll() {
if len(activeFlags) > 0 {
activeFlags = make(map[string]struct{})
}
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package cmn
type LineWriter interface {
WriteLine(value string) (int, error)
}
type Widther interface {
Width(s string) int
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package cmn
import (
"strings"
"github.com/keewek/ansible-pretty-print/src/cmn/eol"
)
type LineBuilder struct {
strings.Builder
EoL eol.EndOfLine
}
func (lb *LineBuilder) WriteLine(value string) (int, error) {
len1, err := lb.WriteString(value)
if err != nil {
return len1, err
}
len2, err := lb.WriteString(lb.EoL.String())
return len1 + len2, err
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package cmn
import (
"strings"
"testing"
"github.com/keewek/ansible-pretty-print/src/cmn/eol"
"github.com/keewek/ansible-pretty-print/src/cmn/tst"
)
func Test_LineBuilder(t *testing.T) {
t.Run("WriteLine", func(t *testing.T) {
var b strings.Builder
var lb LineBuilder
b.WriteString("1\n")
b.WriteString("2\n")
b.WriteString("3\n")
lb.WriteLine("1")
lb.WriteLine("2")
lb.WriteLine("3")
want := b.String()
got := lb.String()
tst.DiffError(t, want, got)
})
t.Run("EoL", func(t *testing.T) {
t.Run("CRLF", func(t *testing.T) {
var b strings.Builder
var lb LineBuilder
b.WriteString("1\r\n")
b.WriteString("2\r\n")
b.WriteString("3\r\n")
lb.EoL = eol.CrLf()
lb.WriteLine("1")
lb.WriteLine("2")
lb.WriteLine("3")
want := b.String()
got := lb.String()
tst.DiffError(t, want, got)
})
t.Run("Custom", func(t *testing.T) {
var b strings.Builder
var lb LineBuilder
b.WriteString("1###\n")
b.WriteString("2###\n")
b.WriteString("3###\n")
lb.EoL = "###\n"
lb.WriteLine("1")
lb.WriteLine("2")
lb.WriteLine("3")
want := b.String()
got := lb.String()
tst.DiffError(t, want, got)
})
})
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package cmn
func Max(x, y int) int {
if x > y {
return x
}
return y
}
func Min(x, y int) int {
if x < y {
return x
}
return y
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package cmn
import (
"testing"
"github.com/keewek/ansible-pretty-print/src/cmn/tst"
)
func TestMax(t *testing.T) {
tests := []struct {
x int
y int
want int
}{
{1, 2, 2},
{2, 1, 2},
{2, 2, 2},
{-2, 0, 0},
{-2, -4, -2},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := Max(tt.x, tt.y)
tst.DiffError(t, tt.want, got)
})
}
}
func TestMin(t *testing.T) {
tests := []struct {
x int
y int
want int
}{
{1, 2, 1},
{2, 1, 1},
{2, 2, 2},
{-2, 0, -2},
{-2, -4, -4},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := Min(tt.x, tt.y)
tst.DiffError(t, tt.want, got)
})
}
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package cmn
import (
"fmt"
"strings"
"unicode/utf8"
"github.com/rivo/uniseg"
)
type BoxChars struct {
CornerTL string
CornerTR string
CornerBL string
CornerBR string
Left string
Right string
Top string
Bottom string
Cross string
Hor string
Ver string
}
func BoxCharsAscii() BoxChars {
return BoxChars{
CornerTL: "+",
CornerTR: "+",
CornerBL: "+",
CornerBR: "+",
Left: "+",
Right: "+",
Top: "+",
Bottom: "+",
Cross: "+",
Hor: "-",
Ver: "|",
}
}
func BoxCharsDos() BoxChars {
return BoxChars{
CornerTL: "┌",
CornerTR: "┐",
CornerBL: "└",
CornerBR: "┘",
Left: "├",
Right: "┤",
Top: "┬",
Bottom: "┴",
Cross: "┼",
Hor: "─",
Ver: "│",
}
}
// ---
type WidthFunc func(string) int
// WidthBytes returns the number of bytes in s
//
// Equivalent to len(s)
func WidthBytes(s string) int {
return len(s)
}
// WidthRunes returns the number of runes in s
//
// Equivalent to utf8.RuneCountInString(s)
func WidthRunes(s string) int {
return utf8.RuneCountInString(s)
}
// WidthMonospace returns the monospace width for the given string, that is, the number of same-size cells to be
// occupied by the string.
//
// Equivalent to uniseg.StringWidth(s)
func WidthMonospace(s string) int {
return uniseg.StringWidth(s)
}
// ---
func PadLeftFunc(s string, padWith rune, maxWidth int, fnWidth WidthFunc) string {
dW := maxWidth - fnWidth(s)
if dW <= 0 {
return s
}
pad := strings.Repeat(string(padWith), dW)
return pad + s
}
func PadLeft(s string, padWith rune, maxRunes int) string {
return PadLeftFunc(s, padWith, maxRunes, utf8.RuneCountInString)
}
func PadRightFunc(s string, padWith rune, maxWidth int, fnWidth WidthFunc) string {
dW := maxWidth - fnWidth(s)
if dW <= 0 {
return s
}
pad := strings.Repeat(string(padWith), dW)
return s + pad
}
func PadRight(s string, padWith rune, maxRunes int) string {
return PadRightFunc(s, padWith, maxRunes, utf8.RuneCountInString)
}
// ---
type BytesWidther struct{}
type RunesWidther struct{}
type MonospaceWidther struct{}
func (bw BytesWidther) Width(s string) int {
return len(s)
}
func (bw BytesWidther) String() string {
return "BytesWidther{}"
}
func (rw RunesWidther) Width(s string) int {
return utf8.RuneCountInString(s)
}
func (rw RunesWidther) String() string {
return "RunesWidther{}"
}
func (mw MonospaceWidther) Width(s string) int {
return uniseg.StringWidth(s)
}
func (mw MonospaceWidther) String() string {
return "MonospaceWidther{}"
}
// ---
type FnChopMarkLine func(line string, maxWidth int, chopMark string) string
func ChopLineFunc(line string, maxWidth int, fnWidth WidthFunc) string {
result := line
if maxWidth <= 0 {
result = ""
} else if fnWidth(line) > maxWidth {
var b strings.Builder
width := 0
for _, r := range line {
width += fnWidth(string(r))
if width > maxWidth {
break
}
b.WriteRune(r)
}
result = b.String()
}
return result
}
func ChopMarkLineFunc(line string, maxWidth int, chopMark string, fnWidth WidthFunc) string {
result := line
if maxWidth <= 0 {
result = ""
} else if fnWidth(line) > maxWidth {
if chopMark != "" {
chopRune, _ := utf8.DecodeRuneInString(chopMark)
chopMark = string(chopRune)
chopMarkWidth := fnWidth(chopMark)
if chopMarkWidth > maxWidth {
panic(fmt.Errorf("cmn: can't fit `chopMark` with width %d when `maxWidth` is %d", chopMarkWidth, maxWidth))
}
maxWidth -= chopMarkWidth
}
result = ChopLineFunc(line, maxWidth, fnWidth) + chopMark
}
return result
}
func ChopMarkLine(line string, maxWidth int, chopMark string) string {
result := line
if maxWidth <= 0 {
result = ""
} else if WidthRunes(line) > maxWidth {
if chopMark != "" {
result = fmt.Sprintf("%.*s%.1s", maxWidth-1, line, chopMark)
} else {
result = fmt.Sprintf("%.*s", maxWidth, line)
}
}
return result
}
func ChopMarkLineSelector(w Widther) (fn FnChopMarkLine) {
switch w.(type) {
case RunesWidther:
fn = ChopMarkLine
default:
fn = func(line string, maxWidth int, chopMark string) string {
return ChopMarkLineFunc(line, maxWidth, chopMark, w.Width)
}
}
return fn
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package cmn
import (
"fmt"
"testing"
"github.com/keewek/ansible-pretty-print/src/cmn/tst"
)
func Test_WidthFunc(t *testing.T) {
var fn WidthFunc = func(s string) int {
return 0
}
_ = fn
}
func TestWidthBytes(t *testing.T) {
tests := []struct {
value string
want int
}{
{"Hello", 5},
{"Привет", 12},
{"你好", 6},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := WidthBytes(tt.value)
tst.DiffError(t, tt.want, got)
})
}
}
func TestWidthRunes(t *testing.T) {
tests := []struct {
value string
want int
}{
{"Hello", 5},
{"Привет", 6},
{"你好", 2},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := WidthRunes(tt.value)
tst.DiffError(t, tt.want, got)
})
}
}
func TestWidthMonospace(t *testing.T) {
tests := []struct {
value string
want int
}{
{"Hello", 5},
{"Привет", 6},
{"你好", 4},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := WidthMonospace(tt.value)
tst.DiffError(t, tt.want, got)
})
}
}
func Test_BytesWidther(t *testing.T) {
t.Run("Width", func(t *testing.T) {
tests := []struct {
value string
want int
}{
{"Hello", 5},
{"Привет", 12},
{"你好", 6},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := BytesWidther{}.Width(tt.value)
tst.DiffError(t, tt.want, got)
})
}
})
t.Run("String", func(t *testing.T) {
want := "BytesWidther{}"
got := BytesWidther{}.String()
tst.DiffError(t, want, got)
})
}
func Test_RunesWidther(t *testing.T) {
t.Run("Width", func(t *testing.T) {
tests := []struct {
value string
want int
}{
{"Hello", 5},
{"Привет", 6},
{"你好", 2},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := RunesWidther{}.Width(tt.value)
tst.DiffError(t, tt.want, got)
})
}
})
t.Run("String", func(t *testing.T) {
want := "RunesWidther{}"
got := RunesWidther{}.String()
tst.DiffError(t, want, got)
})
}
func Test_MonospaceWidther(t *testing.T) {
t.Run("Width", func(t *testing.T) {
tests := []struct {
value string
want int
}{
{"Hello", 5},
{"Привет", 6},
{"你好", 4},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := MonospaceWidther{}.Width(tt.value)
tst.DiffError(t, tt.want, got)
})
}
})
t.Run("String", func(t *testing.T) {
want := "MonospaceWidther{}"
got := MonospaceWidther{}.String()
tst.DiffError(t, want, got)
})
}
func TestPadLeft(t *testing.T) {
tests := []struct {
value string
want string
}{
{"Hello", "..........Hello"},
{"Привет", ".........Привет"},
{"你好", ".............你好"},
{"###############", "###############"},
{"################", "################"},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := PadLeft(tt.value, '.', 15)
tst.DiffError(t, tt.want, got)
})
}
}
func TestPadLeftFunc(t *testing.T) {
tests := []struct {
name string
value string
want string
fnWidth WidthFunc
}{
{"WidthBytes", "Hello", "..........Hello", WidthBytes},
{"WidthBytes", "Привет", "...Привет", WidthBytes},
{"WidthBytes", "你好", ".........你好", WidthBytes},
{"WidthRunes", "Hello", "..........Hello", WidthRunes},
{"WidthRunes", "Привет", ".........Привет", WidthRunes},
{"WidthRunes", "你好", ".............你好", WidthRunes},
{"WidthMonospace", "Hello", "..........Hello", WidthMonospace},
{"WidthMonospace", "Привет", ".........Привет", WidthMonospace},
{"WidthMonospace", "你好", "...........你好", WidthMonospace},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PadLeftFunc(tt.value, '.', 15, tt.fnWidth)
tst.DiffError(t, tt.want, got)
})
}
}
func TestPadRight(t *testing.T) {
tests := []struct {
value string
want string
}{
{"Hello", "Hello.........."},
{"Привет", "Привет........."},
{"你好", "你好............."},
{"###############", "###############"},
{"################", "################"},
}
for _, tt := range tests {
t.Run("", func(t *testing.T) {
got := PadRight(tt.value, '.', 15)
tst.DiffError(t, tt.want, got)
})
}
}
func TestPadRightFunc(t *testing.T) {
tests := []struct {
name string
value string
want string
fnWidth WidthFunc
}{
{"WidthBytes", "Hello", "Hello..........", WidthBytes},
{"WidthBytes", "Привет", "Привет...", WidthBytes},
{"WidthBytes", "你好", "你好.........", WidthBytes},
{"WidthRunes", "Hello", "Hello..........", WidthRunes},
{"WidthRunes", "Привет", "Привет.........", WidthRunes},
{"WidthRunes", "你好", "你好.............", WidthRunes},
{"WidthMonospace", "Hello", "Hello..........", WidthMonospace},
{"WidthMonospace", "Привет", "Привет.........", WidthMonospace},
{"WidthMonospace", "你好", "你好...........", WidthMonospace},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := PadRightFunc(tt.value, '.', 15, tt.fnWidth)
tst.DiffError(t, tt.want, got)
})
}
}
func TestChopLineFunc(t *testing.T) {
tests := []struct {
name string
value string
want string
maxWidth int
fnWidth WidthFunc
}{
{"maxWidth==0", "#####", "", 0, WidthBytes},
{"maxWidth<0", "#####", "", -1, WidthBytes},
{"WidthBytes", "Hello", "H", 1, WidthBytes},
{"WidthBytes", "Привет", "", 1, WidthBytes},
{"WidthBytes", "你好", "", 1, WidthBytes},
{"WidthBytes", "Hello", "He", 2, WidthBytes},
{"WidthBytes", "Привет", "П", 2, WidthBytes},
{"WidthBytes", "你好", "", 2, WidthBytes},
{"WidthBytes", "Hello", "Hel", 3, WidthBytes},
{"WidthBytes", "Привет", "П", 3, WidthBytes},
{"WidthBytes", "你好", "你", 3, WidthBytes},
{"WidthRunes", "Hello", "H", 1, WidthRunes},
{"WidthRunes", "Привет", "П", 1, WidthRunes},
{"WidthRunes", "你好", "你", 1, WidthRunes},
{"WidthRunes", "Hello", "He", 2, WidthRunes},
{"WidthRunes", "Привет", "Пр", 2, WidthRunes},
{"WidthRunes", "你好", "你好", 2, WidthRunes},
{"WidthRunes", "Hello", "Hel", 3, WidthRunes},
{"WidthRunes", "Привет", "При", 3, WidthRunes},
{"WidthRunes", "你好", "你好", 3, WidthRunes},
{"WidthMonospace", "Hello", "H", 1, WidthMonospace},
{"WidthMonospace", "Привет", "П", 1, WidthMonospace},
{"WidthMonospace", "你好", "", 1, WidthMonospace},
{"WidthMonospace", "Hello", "He", 2, WidthMonospace},
{"WidthMonospace", "Привет", "Пр", 2, WidthMonospace},
{"WidthMonospace", "你好", "你", 2, WidthMonospace},
{"WidthMonospace", "Hello", "Hel", 3, WidthMonospace},
{"WidthMonospace", "Привет", "При", 3, WidthMonospace},
{"WidthMonospace", "你好", "你", 3, WidthMonospace},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ChopLineFunc(tt.value, tt.maxWidth, tt.fnWidth)
tst.DiffError(t, tt.want, got)
})
}
}
func TestChopMarkLineFunc(t *testing.T) {
tests := []struct {
name string
value string
want string
maxWidth int
fnWidth WidthFunc
}{
{"maxWidth==0", "#####", "", 0, WidthBytes},
{"maxWidth<0", "#####", "", -1, WidthBytes},
{"WidthBytes", "Hello Hello", "▒", 3, WidthBytes},
{"WidthBytes", "Привет Привет", "▒", 3, WidthBytes},
{"WidthBytes", "你好 你好", "▒", 3, WidthBytes},
{"WidthBytes", "Hello Hello", "H▒", 4, WidthBytes},
{"WidthBytes", "Привет Привет", "▒", 4, WidthBytes},
{"WidthBytes", "你好 你好", "▒", 4, WidthBytes},
{"WidthBytes", "Hello Hello", "He▒", 5, WidthBytes},
{"WidthBytes", "Привет Привет", "П▒", 5, WidthBytes},
{"WidthBytes", "你好 你好", "▒", 5, WidthBytes},
{"WidthBytes", "Hello Hello", "Hel▒", 6, WidthBytes},
{"WidthBytes", "Привет Привет", "П▒", 6, WidthBytes},
{"WidthBytes", "你好 你好", "你▒", 6, WidthBytes},
{"WidthRunes", "Hello", "▒", 1, WidthRunes},
{"WidthRunes", "Привет", "▒", 1, WidthRunes},
{"WidthRunes", "你好", "▒", 1, WidthRunes},
{"WidthRunes", "Hello", "H▒", 2, WidthRunes},
{"WidthRunes", "Привет", "П▒", 2, WidthRunes},
{"WidthRunes", "你好", "你好", 2, WidthRunes},
{"WidthRunes", "Hello", "He▒", 3, WidthRunes},
{"WidthRunes", "Привет", "Пр▒", 3, WidthRunes},
{"WidthRunes", "你好", "你好", 3, WidthRunes},
{"WidthMonospace", "Hello", "▒", 1, WidthMonospace},
{"WidthMonospace", "Привет", "▒", 1, WidthMonospace},
{"WidthMonospace", "你好", "▒", 1, WidthMonospace},
{"WidthMonospace", "Hello", "H▒", 2, WidthMonospace},
{"WidthMonospace", "Привет", "П▒", 2, WidthMonospace},
{"WidthMonospace", "你好", "▒", 2, WidthMonospace},
{"WidthMonospace", "Hello", "He▒", 3, WidthMonospace},
{"WidthMonospace", "Привет", "Пр▒", 3, WidthMonospace},
{"WidthMonospace", "你好", "你▒", 3, WidthMonospace},
{"WidthMonospace", "Hello", "Hel▒", 4, WidthMonospace},
{"WidthMonospace", "Привет", "При▒", 4, WidthMonospace},
{"WidthMonospace", "你好", "你好", 4, WidthMonospace},
}
t.Run("panic when maxWidth<chopMarkWidt", func(t *testing.T) {
defer func() {
if v := recover(); v != nil {
want := "panic: [cmn: can't fit `chopMark` with width 3 when `maxWidth` is 1]"
got := fmt.Sprintf("panic: [%v]", v)
tst.DiffError(t, want, got)
}
}()
ChopMarkLineFunc("#####", 1, "▒", WidthBytes)
})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ChopMarkLineFunc(tt.value, tt.maxWidth, "▒", tt.fnWidth)
tst.DiffError(t, tt.want, got)
})
}
}
func TestChopMarkLine(t *testing.T) {
tests := []struct {
name string
value string
want string
chopMark string
maxWidth int
fnWidth WidthFunc
}{
{"maxWidth==0", "#####", "", "▒", 0, WidthRunes},
{"maxWidth<0", "#####", "", "▒", -1, WidthRunes},
{"With chopMark", "#####", "###▒", "▒", 4, WidthRunes},
{"Without chopMark", "#####", "####", "", 4, WidthRunes},
{"WidthRunes", "Hello", "▒", "▒", 1, WidthRunes},
{"WidthRunes", "Привет", "▒", "▒", 1, WidthRunes},
{"WidthRunes", "你好", "▒", "▒", 1, WidthRunes},
{"WidthRunes", "Hello", "H▒", "▒", 2, WidthRunes},
{"WidthRunes", "Привет", "П▒", "▒", 2, WidthRunes},
{"WidthRunes", "你好", "你好", "▒", 2, WidthRunes},
{"WidthRunes", "Hello", "He▒", "▒", 3, WidthRunes},
{"WidthRunes", "Привет", "Пр▒", "▒", 3, WidthRunes},
{"WidthRunes", "你好", "你好", "▒", 3, WidthRunes},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ChopMarkLine(tt.value, tt.maxWidth, tt.chopMark)
tst.DiffError(t, tt.want, got)
})
}
}
func TestChopMarkLineSelector(t *testing.T) {
t.Run("returns `ChopMarkLine` function when Widther is `RunesWidther`", func(t *testing.T) {
var fnWant FnChopMarkLine = ChopMarkLine
fnGot := ChopMarkLineSelector(RunesWidther{})
want := fmt.Sprintf("%#v", fnWant)
got := fmt.Sprintf("%#v", fnGot)
tst.DiffError(t, want, got)
})
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package tst
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
)
// ReprValue returns a Go-syntax representation of the value
//
// Equivalent to fmt.Sprintf("%#v", value)
func ReprValue(value any) string {
return fmt.Sprintf("%#v", value)
}
// ReprType returns a Go-syntax representation of the type of the value
//
// Equivalent to fmt.Sprintf("%T, value)
func ReprType(value any) string {
return fmt.Sprintf("%T", value)
}
func DiffError(t *testing.T, want, got any) {
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-want +got): \n%s", diff)
}
}
func DiffErrorBefore(t *testing.T, want, got any, fn func()) {
if diff := cmp.Diff(want, got); diff != "" {
fn()
t.Errorf("(-want +got): \n%s", diff)
}
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package printer
import (
"fmt"
"io"
"os"
"strings"
"github.com/keewek/ansible-pretty-print/src/cmn"
"github.com/keewek/ansible-pretty-print/src/processor"
)
const (
defaultColumnSeparator = " "
defaultBlockSeparator = ": "
defaultIndentPlay = 2
defaultIndentTask = 6
)
type ColumnPrinter struct {
widther cmn.Widther
columnSeparator string
blockSeparator string
indentPlay int
indentTask int
maxLineWidth int
isIndentBlock bool
isChopLines bool
}
func NewColumnPrinter() *ColumnPrinter {
return &ColumnPrinter{
widther: cmn.RunesWidther{},
columnSeparator: defaultColumnSeparator,
blockSeparator: defaultBlockSeparator,
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
}
}
func (cp *ColumnPrinter) calcCol1Width(stats *processor.Stats) int {
var play, task int
play = cp.indentPlay + stats.LongestPlayDescriptionLength
if cp.isIndentBlock {
task = cp.indentTask + stats.LongestTaskBlockLength + cp.widther.Width(cp.blockSeparator) + stats.LongestTaskNameLength
} else {
task = cp.indentTask + stats.LongestTaskDescriptionLength
}
return cmn.Max(play, task)
}
func (cp *ColumnPrinter) SetWidther(value cmn.Widther) *ColumnPrinter {
cp.widther = value
return cp
}
func (cp *ColumnPrinter) SetIsIndentBlock(value bool) *ColumnPrinter {
cp.isIndentBlock = value
return cp
}
func (cp *ColumnPrinter) SetIsChopLines(value bool) *ColumnPrinter {
cp.isChopLines = value
return cp
}
func (cp *ColumnPrinter) SetMaxLineWidth(value int) *ColumnPrinter {
cp.maxLineWidth = value
return cp
}
func (cp *ColumnPrinter) PrintTo(output io.Writer, data *processor.Result) {
var (
col1, col2 string
fnPrintLine func(line string)
fnFormCol1 func(task *processor.Task) string
)
padPlay := strings.Repeat(" ", cp.indentPlay)
padTask := strings.Repeat(" ", cp.indentTask)
fnFormLine := func(col1 string, col2 string) string {
col1Width := cp.calcCol1Width(data.Stats)
col1Padded := cmn.PadRightFunc(col1, ' ', col1Width, cp.widther.Width)
return fmt.Sprint(col1Padded, cp.columnSeparator, col2)
}
if cp.isIndentBlock {
fnFormCol1 = func(t *processor.Task) string {
blockPadded := cmn.PadLeftFunc(t.Block, ' ', data.Stats.LongestTaskBlockLength, cp.widther.Width)
return fmt.Sprint(padTask, blockPadded, cp.blockSeparator, t.Name)
}
} else {
fnFormCol1 = func(t *processor.Task) string {
return padTask + t.Description()
}
}
if cp.isChopLines {
fnChopMarkLine := cmn.ChopMarkLineSelector(cp.widther)
fnPrintLine = func(line string) {
fmt.Fprintln(output, fnChopMarkLine(line, cp.maxLineWidth, "▒"))
}
} else {
fnPrintLine = func(line string) {
fmt.Fprintln(output, line)
}
}
for _, row := range data.Rows {
switch t := row.Data.(type) {
case *processor.Play:
col1 = padPlay + t.Name
col2 = "TAGS: " + t.Tags
fnPrintLine(fnFormLine(col1, col2))
case *processor.Tasks:
for _, task := range t.Tasks {
col1 = fnFormCol1(task)
col2 = "TAGS: " + task.Tags
fnPrintLine(fnFormLine(col1, col2))
}
default:
col1 = t.String()
col2 = ""
fnPrintLine(fnFormLine(col1, col2))
}
}
}
func (cp *ColumnPrinter) Print(data *processor.Result) {
cp.PrintTo(os.Stdout, data)
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package printer
import (
"fmt"
"testing"
"github.com/keewek/ansible-pretty-print/src/cmn"
"github.com/keewek/ansible-pretty-print/src/cmn/tst"
"github.com/keewek/ansible-pretty-print/src/processor"
)
type blockquote string
func (b blockquote) String() string {
return "| " + string(b)
}
func TestNewColumnPrinter(t *testing.T) {
cp := &ColumnPrinter{
widther: cmn.RunesWidther{},
columnSeparator: defaultColumnSeparator,
blockSeparator: defaultBlockSeparator,
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
}
r := NewColumnPrinter()
want := fmt.Sprintf("%#v", cp)
got := fmt.Sprintf("%#v", r)
tst.DiffError(t, want, got)
}
func Test_ColumnPrinterSetWidther(t *testing.T) {
w := cmn.MonospaceWidther{}
cp := NewColumnPrinter()
cp.SetWidther(w)
want := fmt.Sprintf("%#v", w)
got := fmt.Sprintf("%#v", cp.widther)
tst.DiffError(t, want, got)
}
func Test_ColumnPrinterSetIsIndentBlock(t *testing.T) {
isIndentBlock := true
cp := NewColumnPrinter()
cp.SetIsIndentBlock(isIndentBlock)
want := isIndentBlock
got := cp.isIndentBlock
tst.DiffError(t, want, got)
}
func Test_ColumnPrinterSetIsChopLines(t *testing.T) {
isChopLines := true
cp := NewColumnPrinter()
cp.SetIsChopLines(isChopLines)
want := isChopLines
got := cp.isChopLines
tst.DiffError(t, want, got)
}
func Test_ColumnPrinterSetMaxLineWidth(t *testing.T) {
maxLineWidth := 40
cp := NewColumnPrinter()
cp.SetMaxLineWidth(maxLineWidth)
want := maxLineWidth
got := cp.maxLineWidth
tst.DiffError(t, want, got)
}
func Test_ColumnPrinter_calcCol1Witdh(t *testing.T) {
tests := []struct {
name string
indentPlay int
indentTask int
blockSeparator string
isIndentBlock bool
stats processor.Stats
want int
}{
{
name: "default---zero-value-stats---indent_false",
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
blockSeparator: defaultBlockSeparator,
isIndentBlock: false,
stats: processor.Stats{
LongestPlayDescriptionLength: 0,
LongestTaskBlockLength: 0,
LongestTaskNameLength: 0,
LongestTaskDescriptionLength: 0,
},
want: 6,
},
{
name: "default---zero-value-stats---indent_true",
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
blockSeparator: defaultBlockSeparator,
isIndentBlock: true,
stats: processor.Stats{
LongestPlayDescriptionLength: 0,
LongestTaskBlockLength: 0,
LongestTaskNameLength: 0,
LongestTaskDescriptionLength: 0,
},
want: 8,
},
{
name: "",
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
blockSeparator: defaultBlockSeparator,
isIndentBlock: false,
stats: processor.Stats{
LongestPlayDescriptionLength: 40,
LongestTaskBlockLength: 10,
LongestTaskNameLength: 18,
LongestTaskDescriptionLength: 30,
},
want: 42,
},
{
name: "",
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
blockSeparator: defaultBlockSeparator,
isIndentBlock: false,
stats: processor.Stats{
LongestPlayDescriptionLength: 30,
LongestTaskBlockLength: 10,
LongestTaskNameLength: 18,
LongestTaskDescriptionLength: 30,
},
want: 36,
},
{
name: "",
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
blockSeparator: defaultBlockSeparator,
isIndentBlock: true,
stats: processor.Stats{
LongestPlayDescriptionLength: 30,
LongestTaskBlockLength: 10,
LongestTaskNameLength: 18,
LongestTaskDescriptionLength: 30,
},
want: 36,
},
{
name: "",
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
blockSeparator: defaultBlockSeparator,
isIndentBlock: false,
stats: processor.Stats{
LongestPlayDescriptionLength: 41,
LongestTaskBlockLength: 5,
LongestTaskNameLength: 70,
LongestTaskDescriptionLength: 11,
},
want: 43,
},
{
name: "",
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
blockSeparator: defaultBlockSeparator,
isIndentBlock: true,
stats: processor.Stats{
LongestPlayDescriptionLength: 41,
LongestTaskBlockLength: 5,
LongestTaskNameLength: 70,
LongestTaskDescriptionLength: 11,
},
want: 83,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cp := NewColumnPrinter()
cp.indentPlay = tt.indentPlay
cp.indentTask = tt.indentTask
cp.blockSeparator = tt.blockSeparator
cp.isIndentBlock = tt.isIndentBlock
got := cp.calcCol1Width(&tt.stats)
tst.DiffError(t, tt.want, got)
})
}
}
func Test_ColumnPrinterPrintTo(t *testing.T) {
t.Run("row is fmt.Stringer", func(t *testing.T) {
t.Run("nochop", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: blockquote("Indent 0")},
{Indent: 2, Data: blockquote("Indent 2")},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine("| Indent 0 ")
lb.WriteLine("| Indent 2 ")
cp := NewColumnPrinter()
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("chop maxLineWidth is 0", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: blockquote("Indent 0")},
{Indent: 2, Data: blockquote("Indent 2")},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine("")
lb.WriteLine("")
cp := NewColumnPrinter()
cp.isChopLines = true
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("chop maxLineWidth is 5", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: blockquote("Indent 0")},
{Indent: 2, Data: blockquote("Indent 2")},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine("| In▒")
lb.WriteLine("| In▒")
cp := NewColumnPrinter()
cp.isChopLines = true
cp.maxLineWidth = 5
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
})
t.Run("row is processor.Play", func(t *testing.T) {
t.Run("nochop", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: &processor.Play{Name: "play #1 (demo): Demo play", Tags: "[p1, demo]"}},
{Indent: 0, Data: &processor.Play{Name: "play #2 (demo): Demo play", Tags: "[p2, demo]"}},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine(" play #1 (demo): Demo play TAGS: [p1, demo]")
lb.WriteLine(" play #2 (demo): Demo play TAGS: [p2, demo]")
cp := NewColumnPrinter()
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("chop maxLineWidth is 0", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: &processor.Play{Name: "play #1 (demo): Demo play", Tags: "[p1, demo]"}},
{Indent: 0, Data: &processor.Play{Name: "play #2 (demo): Demo play", Tags: "[p2, demo]"}},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine("")
lb.WriteLine("")
cp := NewColumnPrinter()
cp.isChopLines = true
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("chop maxLineWidth is 5", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: &processor.Play{Name: "play #1 (demo): Demo play", Tags: "[p1, demo]"}},
{Indent: 0, Data: &processor.Play{Name: "play #2 (demo): Demo play", Tags: "[p2, demo]"}},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine(" pl▒")
lb.WriteLine(" pl▒")
cp := NewColumnPrinter()
cp.isChopLines = true
cp.maxLineWidth = 5
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
})
t.Run("row is processor.Tasks", func(t *testing.T) {
t.Run("nochop", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: &processor.Tasks{PlayNumber: 1, Tasks: []*processor.Task{
{Block: "Block 1", Name: "Task 1", Tags: "[t1]"},
{Block: "Block 2", Name: "Task 2", Tags: "[t2]"},
}}},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine(" Block 1: Task 1 TAGS: [t1]")
lb.WriteLine(" Block 2: Task 2 TAGS: [t2]")
cp := NewColumnPrinter()
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("chop maxLineWidth is 0", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: &processor.Tasks{PlayNumber: 1, Tasks: []*processor.Task{
{Block: "Block 1", Name: "Task 1", Tags: "[t1]"},
{Block: "Block 2", Name: "Task 2", Tags: "[t2]"},
}}},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine("")
lb.WriteLine("")
cp := NewColumnPrinter()
cp.isChopLines = true
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("chop maxLineWidth is 5", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: &processor.Tasks{PlayNumber: 1, Tasks: []*processor.Task{
{Block: "Block 1", Name: "Task 1", Tags: "[t1]"},
{Block: "Block 2", Name: "Task 2", Tags: "[t2]"},
}}},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine(" ▒")
lb.WriteLine(" ▒")
cp := NewColumnPrinter()
cp.isChopLines = true
cp.maxLineWidth = 5
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("noindent", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: &processor.Tasks{PlayNumber: 1, Tasks: []*processor.Task{
{Block: "", Name: "Task 1", Tags: "[t1]"},
{Block: "Block 2", Name: "Task 2", Tags: "[t2]"},
}}},
},
Stats: &processor.Stats{},
}
var lb, out cmn.LineBuilder
lb.WriteLine(" Task 1 TAGS: [t1]")
lb.WriteLine(" Block 2: Task 2 TAGS: [t2]")
cp := NewColumnPrinter()
cp.isIndentBlock = false
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("indent", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: &processor.Tasks{PlayNumber: 1, Tasks: []*processor.Task{
{Block: "", Name: "Task 1", Tags: "[t1]"},
{Block: "Block 2", Name: "Task 2", Tags: "[t2]"},
}}},
},
Stats: &processor.Stats{
LongestTaskBlockLength: 7,
},
}
var lb, out cmn.LineBuilder
lb.WriteLine(" : Task 1 TAGS: [t1]")
lb.WriteLine(" Block 2: Task 2 TAGS: [t2]")
cp := NewColumnPrinter()
cp.isIndentBlock = true
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
})
t.Run("mixed rows", func(t *testing.T) {
t.Run("nochop", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: processor.Passthru("Passthru")},
{Indent: 0, Data: &processor.Play{Name: "play #1 (demo): Demo play", Tags: "[p1, demo]"}},
{Indent: 0, Data: blockquote(" blockquote")},
{Indent: 0, Data: &processor.Tasks{PlayNumber: 1, Tasks: []*processor.Task{
{Block: "Block 1", Name: "Task 1", Tags: "[t1]"},
{Block: "Block 2", Name: "Task 2", Tags: "[t2]"},
}}},
},
Stats: &processor.Stats{
LongestPlayDescriptionLength: 25,
},
}
var lb, out cmn.LineBuilder
lb.WriteLine("Passthru ")
lb.WriteLine(" play #1 (demo): Demo play TAGS: [p1, demo]")
lb.WriteLine("| blockquote ")
lb.WriteLine(" Block 1: Task 1 TAGS: [t1]")
lb.WriteLine(" Block 2: Task 2 TAGS: [t2]")
cp := NewColumnPrinter()
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
t.Run("chop maxLineWidth is 10", func(t *testing.T) {
r := processor.Result{
Rows: []*processor.Row{
{Indent: 0, Data: processor.Passthru("Passthru")},
{Indent: 0, Data: &processor.Play{Name: "play #1 (demo): Demo play", Tags: "[p1, demo]"}},
{Indent: 0, Data: blockquote(" blockquote")},
{Indent: 0, Data: &processor.Tasks{PlayNumber: 1, Tasks: []*processor.Task{
{Block: "Block 1", Name: "Task 1", Tags: "[t1]"},
{Block: "Block 2", Name: "Task 2", Tags: "[t2]"},
}}},
},
Stats: &processor.Stats{
LongestPlayDescriptionLength: 25,
},
}
var lb, out cmn.LineBuilder
lb.WriteLine("Passthru ▒")
lb.WriteLine(" play #1▒")
lb.WriteLine("| block▒")
lb.WriteLine(" Blo▒")
lb.WriteLine(" Blo▒")
cp := NewColumnPrinter()
cp.isChopLines = true
cp.maxLineWidth = 10
cp.PrintTo(&out, &r)
want := lb.String()
got := out.String()
tst.DiffError(t, want, got)
})
})
}
// SPDX-FileCopyrightText: 2023 Alexander Bugrov <abugrov+dev@gmail.com>
//
// SPDX-License-Identifier: MIT
package printer
import (
"fmt"
"io"
"os"
"strings"
"github.com/keewek/ansible-pretty-print/src/cmn"
"github.com/keewek/ansible-pretty-print/src/processor"
)
type TablePrinter struct {
fnChopMarkLine func(line string, maxWidth int, chopMark string) string
widther cmn.Widther
indentPlay int
indentTask int
padPlay string
padTask string
maxLineWidth int
box cmn.BoxChars
}
type tableWidth struct {
block int
name int
tags int
}
func NewTablePrinter() *TablePrinter {
return &TablePrinter{
fnChopMarkLine: cmn.ChopMarkLine,
widther: cmn.RunesWidther{},
indentPlay: defaultIndentPlay,
indentTask: defaultIndentTask,
padPlay: strings.Repeat(" ", defaultIndentPlay),
padTask: strings.Repeat(" ", defaultIndentTask),
box: cmn.BoxCharsAscii(),
}
}
func (tp *TablePrinter) SetWidther(value cmn.Widther) *TablePrinter {
tp.widther = value
tp.fnChopMarkLine = cmn.ChopMarkLineSelector(value)
return tp
}
func (tp *TablePrinter) SetMaxLineWidth(value int) *TablePrinter {
tp.maxLineWidth = value
return tp
}
func (tp *TablePrinter) SetBoxChars(value cmn.BoxChars) *TablePrinter {
tp.box = value
return tp
}
func (tp *TablePrinter) makeBorders(w *tableWidth) (top string, middle string, bottom string) {
block := strings.Repeat(tp.box.Hor, w.block+2)
name := strings.Repeat(tp.box.Hor, w.name+2)
tags := strings.Repeat(tp.box.Hor, w.tags+2)
top = fmt.Sprint(tp.padTask, tp.box.CornerTL, block, tp.box.Top, name, tp.box.Top, tags, tp.box.CornerTR)
middle = fmt.Sprint(tp.padTask, tp.box.Left, block, tp.box.Cross, name, tp.box.Cross, tags, tp.box.Right)
bottom = fmt.Sprint(tp.padTask, tp.box.CornerBL, block, tp.box.Bottom, name, tp.box.Bottom, tags, tp.box.CornerBR)
return top, middle, bottom
}
func (tp *TablePrinter) fitTable(s *processor.Stats) *tableWidth {
width := &tableWidth{}
fit := func(dW, width, minWidth int) (fit int, r int) {
fit = cmn.Max(minWidth, width-dW)
r = dW - (width - fit)
return fit, r
}
minBlock := len("Block")
minName := len("Name")
minTags := len("Tags")
width.block = cmn.Max(s.LongestTaskBlockLength, minBlock)
width.name = cmn.Max(s.LongestTaskNameLength, minName)
width.tags = cmn.Max(s.LongestTaskTagsLength, minTags)
borderTop, _, _ := tp.makeBorders(width)
lineWidth := tp.widther.Width(borderTop)
if lineWidth > tp.maxLineWidth {
dW := lineWidth - tp.maxLineWidth
width.tags, dW = fit(dW, width.tags, minTags)
if dW > 0 {
width.name, dW = fit(dW, width.name, minName)
if dW > 0 {
width.block, _ = fit(dW, width.block, minBlock)
}
}
}
return width
}
func (tp *TablePrinter) printTable(output io.Writer, t *processor.Tasks, s *processor.Stats) {
width := tp.fitTable(s)
borderTop, borderMiddle, borderBottom := tp.makeBorders(width)
fnPrintHeader := func() {
block := fmt.Sprintf("%-*s", width.block, "Block")
name := fmt.Sprintf("%-*s", width.name, "Name")
tags := fmt.Sprintf("%-*s", width.tags, "Tags")
tp.printLine(output, borderTop)
tp.printLine(output, fmt.Sprintf("%[1]s%[2]s %[3]s %[2]s %[4]s %[2]s %[5]s %[2]s", tp.padTask, tp.box.Ver, block, name, tags))
tp.printLine(output, borderMiddle)
}
fnPrintRow := func(t *processor.Task) {
block := cmn.PadLeftFunc(tp.fnChopMarkLine(t.Block, width.block, "▒"), ' ', width.block, tp.widther.Width)
name := cmn.PadRightFunc(tp.fnChopMarkLine(t.Name, width.name, "▒"), ' ', width.name, tp.widther.Width)
tags := cmn.PadRightFunc(tp.fnChopMarkLine(t.Tags, width.tags, "▒"), ' ', width.tags, tp.widther.Width)
tp.printLine(output, fmt.Sprintf("%[1]s%[2]s %[3]s %[2]s %[4]s %[2]s %[5]s %[2]s", tp.padTask, tp.box.Ver, block, name, tags))
}
fnPrintHeader()
for _, task := range t.Tasks {
fnPrintRow(task)
}
tp.printLine(output, borderBottom)
}
func (tp *TablePrinter) printLine(output io.Writer, value string) {
fmt.Fprintln(output, tp.fnChopMarkLine(value, tp.maxLineWidth, "▒"))
}
func (tp *TablePrinter) PrintTo(output io.Writer, data *processor.Result) {
var line string
padPlay := strings.Repeat(" ", tp.indentPlay)
for _, row := range data.Rows {
switch t := row.Data.(type) {
case *processor.Play:
line = fmt.Sprintf("%s%s TAGS: %s", padPlay, t.Description(), t.Tags)
tp.printLine(output, line)
case *processor.Tasks:
tp.printTable(output, t, data.Stats)
default:
tp.printLine(output, t.String())
}
}
}
func (tp *TablePrinter) Print(data *processor.Result) {
tp.PrintTo(os.Stdout, data)
}
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать