mirror of
https://github.com/BishopFox/sliver.git
synced 2026-11-10 09:51:30 -10:00
Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1624c84eb6 | ||
|
|
44368b26ad | ||
|
|
0eb93d51c0 | ||
|
|
c7cd14eae2 | ||
|
|
9c9caba8d8 | ||
|
|
4b6219d860 | ||
|
|
a47aeb7e73 | ||
|
|
4c0e6cb585 | ||
|
|
cd711c1e2f | ||
|
|
187394439e | ||
|
|
143246a9fc | ||
|
|
7a7f169e0a |
No files matched your search
@@ -269,20 +269,20 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
return
|
||||
}
|
||||
// args := ctx.Args.StringList("arguments")
|
||||
var extArgs string
|
||||
var extArgsStr string
|
||||
if len(aliasManifest.DefaultArgs) != 0 && len(args) == 0 {
|
||||
extArgs = aliasManifest.DefaultArgs
|
||||
extArgsStr = aliasManifest.DefaultArgs
|
||||
} else {
|
||||
extArgs = strings.Join(args, " ")
|
||||
extArgsStr = strings.Join(args, " ")
|
||||
}
|
||||
|
||||
extArgs = strings.TrimSpace(extArgs)
|
||||
extArgsStr = strings.TrimSpace(extArgsStr)
|
||||
entryPoint := aliasManifest.Entrypoint
|
||||
processArgsStr, _ := cmd.Flags().GetString("process-arguments")
|
||||
// Special case for payloads with pass to Donut (.NET assemblies and sideloaded payloads):
|
||||
// The Donut loader has a hard limit of 256 characters for the command line arguments, so
|
||||
// we're alerting the user that the arguments will be truncated.
|
||||
if len(extArgs) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
|
||||
if len(extArgsStr) > 256 && (aliasManifest.IsAssembly || !aliasManifest.IsReflective) {
|
||||
msgStr := ""
|
||||
// The --in-process flag only exists for .NET assemblies (aliasManifest.IsAssembly == true).
|
||||
// Groupping the two conditions together could crash the client since ctx.Flags.Type panics
|
||||
@@ -354,13 +354,13 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
|
||||
// Execute Assembly
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
executeAssemblyResp, err := con.Rpc.ExecuteAssembly(context.Background(), &sliverpb.ExecuteAssemblyReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
IsDLL: isDLL,
|
||||
Process: processName,
|
||||
Arguments: extArgs,
|
||||
Arguments: args,
|
||||
Assembly: binData,
|
||||
Arch: arch,
|
||||
Method: method,
|
||||
@@ -400,11 +400,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
|
||||
// Spawn DLL
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
spawnDllResp, err := con.Rpc.SpawnDll(context.Background(), &sliverpb.InvokeSpawnDllReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Args: strings.Trim(extArgs, " "),
|
||||
Args: args,
|
||||
Data: binData,
|
||||
ProcessName: processName,
|
||||
EntryPoint: aliasManifest.Entrypoint,
|
||||
@@ -439,11 +439,11 @@ func runAliasCommand(cmd *cobra.Command, con *console.SliverClient, args []strin
|
||||
|
||||
// Sideload
|
||||
ctrl := make(chan bool)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgs)
|
||||
msg := fmt.Sprintf("Executing %s %s ...", cmd.Name(), extArgsStr)
|
||||
con.SpinUntil(msg, ctrl)
|
||||
sideloadResp, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Args: extArgs,
|
||||
Args: args,
|
||||
Data: binData,
|
||||
EntryPoint: entryPoint,
|
||||
ProcessName: processName,
|
||||
|
||||
@@ -95,7 +95,7 @@ func ExecuteAssemblyCmd(cmd *cobra.Command, con *console.SliverClient, args []st
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
IsDLL: isDLL,
|
||||
Process: process,
|
||||
Arguments: assemblyArgsStr,
|
||||
Arguments: assemblyArgs,
|
||||
Assembly: assemblyBytes,
|
||||
Arch: arch,
|
||||
Method: method,
|
||||
|
||||
@@ -39,8 +39,16 @@ func SideloadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(args) < 1 {
|
||||
cmd.Usage()
|
||||
return
|
||||
}
|
||||
|
||||
binPath := args[0]
|
||||
binArgs := strings.Join(args[1:], " ")
|
||||
var binArgs []string
|
||||
if len(args) > 1 {
|
||||
binArgs = args[1:]
|
||||
}
|
||||
|
||||
entryPoint, _ := cmd.Flags().GetString("entry-point")
|
||||
processName, _ := cmd.Flags().GetString("process")
|
||||
@@ -57,7 +65,7 @@ func SideloadCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
processArgs := strings.Split(processArgsStr, " ")
|
||||
isDLL := (filepath.Ext(binPath) == ".dll")
|
||||
ctrl := make(chan bool)
|
||||
con.SpinUntil(fmt.Sprintf("Sideloading %s ...", binPath), ctrl)
|
||||
con.SpinUntil(fmt.Sprintf("Sideloading %s %v...", binPath, binArgs), ctrl)
|
||||
sideload, err := con.Rpc.Sideload(context.Background(), &sliverpb.SideloadReq{
|
||||
Request: con.ActiveTarget.Request(cmd),
|
||||
Args: binArgs,
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bishopfox/sliver/client/console"
|
||||
"github.com/bishopfox/sliver/protobuf/clientpb"
|
||||
@@ -38,7 +37,10 @@ func SpawnDllCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
|
||||
return
|
||||
}
|
||||
binPath := args[0]
|
||||
dllArgs := strings.Join(args[1:], " ")
|
||||
var dllArgs []string
|
||||
if len(args) > 1 {
|
||||
dllArgs = args[1:]
|
||||
}
|
||||
|
||||
processName, _ := cmd.Flags().GetString("process")
|
||||
exportName, _ := cmd.Flags().GetString("export")
|
||||
|
||||
@@ -25,7 +25,7 @@ require (
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8
|
||||
github.com/kbinani/screenshot v0.0.0-20191211154542-3a185f1ce18f
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/lesnuages/go-winio v0.4.19
|
||||
@@ -137,7 +137,7 @@ require (
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mdlayher/genetlink v1.3.2 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/sdnotify v1.0.0 // indirect
|
||||
|
||||
@@ -232,8 +232,8 @@ github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
|
||||
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw=
|
||||
github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8 h1:8BCzJdSvUbaDuRba4YVh+SKMGcAAKdkcF3SVFbrHAtQ=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.8/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
@@ -297,9 +297,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
|
||||
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdlayher/genetlink v1.2.0/go.mod h1:ra5LDov2KrUCZJiAtEvXXZBxGMInICMXIwshlJ+qRxQ=
|
||||
@@ -344,7 +343,6 @@ github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -377,15 +375,12 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tailscale/certstore v0.1.1-0.20231020161753-77811a65f4ff h1:vnxdYZUJbsSRcIcduDW3DcQqfqaiv4FUgy25q8X+vfI=
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
//{{if .Config.Debug}}
|
||||
@@ -58,7 +59,7 @@ func RemoteTask(processID int, data []byte, rwxPages bool) error {
|
||||
}
|
||||
|
||||
// Sideload - Side load a library and return its output
|
||||
func Sideload(procName string, procArgs []string, _ uint32, data []byte, args string, kill bool) (string, error) {
|
||||
func Sideload(procName string, procArgs []string, _ uint32, data []byte, args []string, kill bool) (string, error) {
|
||||
var (
|
||||
stdOut bytes.Buffer
|
||||
stdErr bytes.Buffer
|
||||
@@ -72,7 +73,7 @@ func Sideload(procName string, procArgs []string, _ uint32, data []byte, args st
|
||||
}
|
||||
env := os.Environ()
|
||||
newEnv := []string{
|
||||
fmt.Sprintf("LD_PARAMS=%s", args),
|
||||
fmt.Sprintf("LD_PARAMS=%s", strings.Join(args, " ")),
|
||||
fmt.Sprintf("DYLD_INSERT_LIBRARIES=%s", fdPath),
|
||||
}
|
||||
env = append(env, newEnv...)
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
@@ -55,7 +56,7 @@ func RemoteTask(processID int, data []byte, rwxPages bool) error {
|
||||
}
|
||||
|
||||
// Sideload - Side load a library and return its output
|
||||
func Sideload(procName string, procArgs []string, _ uint32, data []byte, args string, kill bool) (string, error) {
|
||||
func Sideload(procName string, procArgs []string, _ uint32, data []byte, args []string, kill bool) (string, error) {
|
||||
var (
|
||||
nrMemfdCreate int
|
||||
stdOut bytes.Buffer
|
||||
@@ -91,7 +92,7 @@ func Sideload(procName string, procArgs []string, _ uint32, data []byte, args st
|
||||
//{{end}}
|
||||
env := os.Environ()
|
||||
newEnv := []string{
|
||||
fmt.Sprintf("LD_PARAMS=%s", args),
|
||||
fmt.Sprintf("LD_PARAMS=%s", strings.Join(args, " ")),
|
||||
fmt.Sprintf("LD_PRELOAD=%s", fdPath),
|
||||
}
|
||||
env = append(env, newEnv...)
|
||||
|
||||
@@ -31,8 +31,10 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/spoof"
|
||||
"syscall"
|
||||
|
||||
"github.com/bishopfox/sliver/implant/sliver/spoof"
|
||||
|
||||
// {{if .Config.Evasion}}
|
||||
"github.com/bishopfox/sliver/implant/sliver/evasion"
|
||||
"github.com/bishopfox/sliver/implant/sliver/version"
|
||||
@@ -343,7 +345,7 @@ func ExecuteAssembly(data []byte, process string, processArgs []string, ppid uin
|
||||
return stdoutBuf.String() + stderrBuf.String(), nil
|
||||
}
|
||||
|
||||
func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, offset uint32, args string, kill bool) (string, error) {
|
||||
func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, offset uint32, args []string, kill bool) (string, error) {
|
||||
var lpTargetHandle windows.Handle
|
||||
err := refresh()
|
||||
if err != nil {
|
||||
@@ -382,11 +384,12 @@ func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, o
|
||||
defer windows.CloseHandle(lpTargetHandle)
|
||||
dataAddr, err := allocAndWrite(data, lpTargetHandle, uint32(len(data)))
|
||||
argAddr := uintptr(0)
|
||||
if len(args) > 0 {
|
||||
argsStr := strings.Join(args, " ")
|
||||
if len(argsStr) > 0 {
|
||||
//{{if .Config.Debug}}
|
||||
log.Printf("Args: %s\n", args)
|
||||
log.Printf("Args: %s\n", argsStr)
|
||||
//{{end}}
|
||||
argsArray := []byte(args)
|
||||
argsArray := []byte(argsStr)
|
||||
argAddr, err = allocAndWrite(argsArray, lpTargetHandle, uint32(len(argsArray)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -419,8 +422,8 @@ func SpawnDll(procName string, processArgs []string, ppid uint32, data []byte, o
|
||||
}
|
||||
|
||||
// SideLoad - Side load a binary as shellcode and returns its output
|
||||
func Sideload(procName string, procArgs []string, ppid uint32, data []byte, args string, kill bool) (string, error) {
|
||||
return SpawnDll(procName, procArgs, ppid, data, 0, "", kill)
|
||||
func Sideload(procName string, procArgs []string, ppid uint32, data []byte, args []string, kill bool) (string, error) {
|
||||
return SpawnDll(procName, procArgs, ppid, data, 0, []string{""}, kill)
|
||||
}
|
||||
|
||||
// Util functions
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// source: clientpb/client.proto
|
||||
|
||||
package clientpb
|
||||
@@ -5752,7 +5752,8 @@ func (x *MsfStager) GetFile() *commonpb.File {
|
||||
}
|
||||
|
||||
// GetSystemReq - Client request to the server which is translated into
|
||||
// InvokeSystemReq when sending to the implant.
|
||||
//
|
||||
// InvokeSystemReq when sending to the implant.
|
||||
type GetSystemReq struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
@@ -5825,7 +5826,8 @@ func (x *GetSystemReq) GetRequest() *commonpb.Request {
|
||||
}
|
||||
|
||||
// MigrateReq - Client request to the server which is translated into
|
||||
// InvokeMigrateReq when sending to the implant.
|
||||
//
|
||||
// InvokeMigrateReq when sending to the implant.
|
||||
type MigrateReq struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// source: commonpb/common.proto
|
||||
|
||||
package commonpb
|
||||
@@ -131,8 +131,9 @@ func (x *Request) GetSessionID() string {
|
||||
}
|
||||
|
||||
// Response - Common fields used in all gRPC responses. Note that the Err field
|
||||
// only used when the implant needs to return an error to the server.
|
||||
// Client<->Server comms should use normal gRPC error handling.
|
||||
//
|
||||
// only used when the implant needs to return an error to the server.
|
||||
// Client<->Server comms should use normal gRPC error handling.
|
||||
type Response struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// source: dnspb/dns.proto
|
||||
|
||||
package dnspb
|
||||
@@ -87,12 +87,10 @@ func (DNSMessageType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_dnspb_dns_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
// NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
// depending on the DNSMessageType as noted below:
|
||||
//
|
||||
//NOTE: DNS is very space sensitive so certain fields are re-purposed
|
||||
//depending on the DNSMessageType as noted below:
|
||||
//
|
||||
//[Type TOTP]: ID field is used for the TOTP code
|
||||
//
|
||||
// [Type TOTP]: ID field is used for the TOTP code
|
||||
type DNSMessage struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.15.8
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v5.26.1
|
||||
// source: rpcpb/services.proto
|
||||
|
||||
package rpcpb
|
||||
|
||||
+542
-358
File diff suppressed because it is too large.
Load diff
+1461
-1664
File diff suppressed because it is too large.
Load diff
@@ -451,7 +451,7 @@ message Task {
|
||||
|
||||
message ExecuteAssemblyReq {
|
||||
bytes Assembly = 1;
|
||||
string Arguments = 2;
|
||||
repeated string Arguments = 2;
|
||||
string Process = 3;
|
||||
bool IsDLL = 4;
|
||||
string Arch = 5;
|
||||
@@ -543,7 +543,7 @@ message Execute {
|
||||
message SideloadReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
string Args = 3;
|
||||
repeated string Args = 3;
|
||||
string EntryPoint = 4;
|
||||
bool Kill = 5;
|
||||
bool isDLL = 6;
|
||||
@@ -563,7 +563,7 @@ message Sideload {
|
||||
message InvokeSpawnDllReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
string Args = 3;
|
||||
repeated string Args = 3;
|
||||
string EntryPoint = 4;
|
||||
bool Kill = 5;
|
||||
uint32 PPid = 10;
|
||||
@@ -576,7 +576,7 @@ message SpawnDllReq {
|
||||
bytes Data = 1;
|
||||
string ProcessName = 2;
|
||||
uint32 Offset = 3;
|
||||
string Args = 4;
|
||||
repeated string Args = 4;
|
||||
bool Kill = 5;
|
||||
uint32 PPid = 10;
|
||||
repeated string ProcessArgs = 11;
|
||||
@@ -647,20 +647,6 @@ message UnsetEnv {
|
||||
commonpb.Response Response = 9;
|
||||
}
|
||||
|
||||
// DNS Specific messages
|
||||
message DNSSessionInit {
|
||||
bytes Key = 1;
|
||||
}
|
||||
|
||||
message DNSPoll {
|
||||
repeated DNSBlockHeader blocks = 1;
|
||||
}
|
||||
|
||||
message DNSBlockHeader {
|
||||
string ID = 1;
|
||||
uint32 Size = 2;
|
||||
}
|
||||
|
||||
// HTTP Sepecific message
|
||||
message HTTPSessionInit {
|
||||
bytes Key = 1;
|
||||
|
||||
@@ -72,11 +72,8 @@ func (b *Builder) Start() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-events:
|
||||
go b.handleBuildEvent(event)
|
||||
}
|
||||
for event := range events {
|
||||
go b.handleBuildEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +209,7 @@ func (b *Builder) handleBuildEvent(event *clientpb.Event) {
|
||||
builderLog.Errorf("invalid output format: %s", extConfig.Config.Format)
|
||||
b.rpc.BuilderTrigger(context.Background(), &clientpb.Event{
|
||||
EventType: consts.ExternalBuildFailedEvent,
|
||||
Data: []byte(fmt.Sprintf("%s:%s", implantBuildID, err.Error())),
|
||||
Data: []byte(fmt.Sprintf("%s:%s", implantBuildID, err)),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -198,7 +198,7 @@ func (rpc *Server) ExecuteAssembly(ctx context.Context, req *sliverpb.ExecuteAss
|
||||
req.Assembly,
|
||||
req.IsDLL,
|
||||
req.Arch,
|
||||
req.Arguments,
|
||||
strings.Join(req.Arguments, " "),
|
||||
req.Method,
|
||||
req.ClassName,
|
||||
req.AppDomain,
|
||||
@@ -214,7 +214,7 @@ func (rpc *Server) ExecuteAssembly(ctx context.Context, req *sliverpb.ExecuteAss
|
||||
invokeInProcExecAssembly := &sliverpb.InvokeInProcExecuteAssemblyReq{
|
||||
Data: req.Assembly,
|
||||
Runtime: req.Runtime,
|
||||
Arguments: strings.Split(req.Arguments, " "),
|
||||
Arguments: req.Arguments,
|
||||
AmsiBypass: req.AmsiBypass,
|
||||
EtwBypass: req.EtwBypass,
|
||||
Request: req.Request,
|
||||
@@ -266,7 +266,7 @@ func (rpc *Server) Sideload(ctx context.Context, req *sliverpb.SideloadReq) (*sl
|
||||
}
|
||||
|
||||
if getOS(session, beacon) == "windows" {
|
||||
shellcode, err := generate.DonutShellcodeFromPE(req.Data, arch, false, req.Args, "", req.EntryPoint, req.IsDLL, req.IsUnicode, false)
|
||||
shellcode, err := generate.DonutShellcodeFromPE(req.Data, arch, false, strings.Join(req.Args, " "), "", req.EntryPoint, req.IsDLL, req.IsUnicode, false)
|
||||
if err != nil {
|
||||
tasksLog.Errorf("Sideload failed: %s", err)
|
||||
return nil, err
|
||||
|
||||
+4
-3
@@ -7,7 +7,7 @@ Pretty-print tables into ASCII/Unicode strings.
|
||||
- Add Header(s) and Footer(s) (`AppendHeader`/`AppendFooter`)
|
||||
- Add a Separator manually after any Row (`AppendSeparator`)
|
||||
- Auto Index Rows (1, 2, 3 ...) and Columns (A, B, C, ...) (`SetAutoIndex`)
|
||||
- Auto Merge
|
||||
- Auto Merge (_not supported in CSV/HTML/Markdown/TSV modes_)
|
||||
- Cells in a Row (`RowConfig.AutoMerge`)
|
||||
- Columns (`ColumnConfig.AutoMerge`)
|
||||
- Limit the length of
|
||||
@@ -21,6 +21,7 @@ Pretty-print tables into ASCII/Unicode strings.
|
||||
- Mirror output to an `io.Writer` (ex. `os.StdOut`) (`SetOutputMirror`)
|
||||
- Sort by one or more Columns (`SortBy`)
|
||||
- Suppress/hide columns with no content (`SuppressEmptyColumns`)
|
||||
- Suppress trailing spaces in the last column (`SupressTrailingSpaces`)
|
||||
- Customizable Cell rendering per Column (`ColumnConfig.Transformer*`)
|
||||
- Hide any columns that you don't want displayed (`ColumnConfig.Hidden`)
|
||||
- Reset Headers/Rows/Footers at will to reuse the same Table Writer (`Reset*`)
|
||||
@@ -36,6 +37,7 @@ Pretty-print tables into ASCII/Unicode strings.
|
||||
- CSV
|
||||
- HTML Table (with custom CSS Class)
|
||||
- Markdown Table
|
||||
- TSV
|
||||
|
||||
```
|
||||
+---------------------------------------------------------------------+
|
||||
@@ -205,7 +207,7 @@ it specifically for each row/column using `RowConfig` or `ColumnConfig`.
|
||||
|
||||
t := table.NewWriter()
|
||||
t.AppendHeader(table.Row{"Node IP", "Pods", "Namespace", "Container", "RCE", "RCE"}, rowConfigAutoMerge)
|
||||
t.AppendHeader(table.Row{"", "", "", "", "EXE", "RUN"})
|
||||
t.AppendHeader(table.Row{"Node IP", "Pods", "Namespace", "Container", "EXE", "RUN"})
|
||||
t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 1", "Y", "Y"}, rowConfigAutoMerge)
|
||||
t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1A", "C 2", "Y", "N"}, rowConfigAutoMerge)
|
||||
t.AppendRow(table.Row{"1.1.1.1", "Pod 1A", "NS 1B", "C 3", "N", "N"}, rowConfigAutoMerge)
|
||||
@@ -223,7 +225,6 @@ it specifically for each row/column using `RowConfig` or `ColumnConfig`.
|
||||
{Number: 5, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
|
||||
{Number: 6, Align: text.AlignCenter, AlignFooter: text.AlignCenter, AlignHeader: text.AlignCenter},
|
||||
})
|
||||
t.SetOutputMirror(os.Stdout)
|
||||
t.SetStyle(table.StyleLight)
|
||||
t.Style().Options.SeparateRows = true
|
||||
fmt.Println(t.Render())
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ type ColumnConfig struct {
|
||||
}
|
||||
|
||||
func (c ColumnConfig) getWidthMaxEnforcer() WidthEnforcer {
|
||||
if c.WidthMax == 0 {
|
||||
if c.WidthMax <= 0 {
|
||||
return widthEnforcerNone
|
||||
}
|
||||
if c.WidthMaxEnforcer != nil {
|
||||
|
||||
+34
-16
@@ -9,15 +9,16 @@ import (
|
||||
)
|
||||
|
||||
// Render renders the Table in a human-readable "pretty" format. Example:
|
||||
// ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
|
||||
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
|
||||
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
|
||||
// │ 1 │ Arya │ Stark │ 3000 │ │
|
||||
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
|
||||
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
|
||||
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
|
||||
// │ │ │ TOTAL │ 10000 │ │
|
||||
// └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
|
||||
//
|
||||
// ┌─────┬────────────┬───────────┬────────┬─────────────────────────────┐
|
||||
// │ # │ FIRST NAME │ LAST NAME │ SALARY │ │
|
||||
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
|
||||
// │ 1 │ Arya │ Stark │ 3000 │ │
|
||||
// │ 20 │ Jon │ Snow │ 2000 │ You know nothing, Jon Snow! │
|
||||
// │ 300 │ Tyrion │ Lannister │ 5000 │ │
|
||||
// ├─────┼────────────┼───────────┼────────┼─────────────────────────────┤
|
||||
// │ │ │ TOTAL │ 10000 │ │
|
||||
// └─────┴────────────┴───────────┴────────┴─────────────────────────────┘
|
||||
func (t *Table) Render() string {
|
||||
t.initForRender()
|
||||
|
||||
@@ -206,11 +207,12 @@ func (t *Table) renderLine(out *strings.Builder, row rowStr, hint renderHint) {
|
||||
if outLine != out {
|
||||
t.renderLineMergeOutputs(out, outLine)
|
||||
}
|
||||
t.firstRowOfPage = false
|
||||
|
||||
// if a page size has been set, and said number of lines has already
|
||||
// been rendered, and the header is not being rendered right now, render
|
||||
// the header all over again with a spacing line
|
||||
if hint.isRegularRow() {
|
||||
if hint.isRegularNonSeparatorRow() {
|
||||
t.numLinesRendered++
|
||||
if t.pageSize > 0 && t.numLinesRendered%t.pageSize == 0 && !hint.isLastLineOfLastRow() {
|
||||
t.renderRowsFooter(out)
|
||||
@@ -218,6 +220,7 @@ func (t *Table) renderLine(out *strings.Builder, row rowStr, hint renderHint) {
|
||||
out.WriteString(t.style.Box.PageSeparator)
|
||||
t.renderRowsBorderTop(out)
|
||||
t.renderRowsHeader(out)
|
||||
t.firstRowOfPage = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -312,8 +315,7 @@ func (t *Table) renderRows(out *strings.Builder, rows []rowStr, hint renderHint)
|
||||
hint.rowNumber = rowIdx + 1
|
||||
t.renderRow(out, row, hint)
|
||||
|
||||
if (t.style.Options.SeparateRows && rowIdx < len(rows)-1) || // last row before footer
|
||||
(t.separators[rowIdx] && rowIdx != len(rows)-1) { // manually added separator not after last row
|
||||
if t.shouldSeparateRows(rowIdx, len(rows)) {
|
||||
hint.isFirstRow = false
|
||||
t.renderRowSeparator(out, hint)
|
||||
}
|
||||
@@ -322,17 +324,33 @@ func (t *Table) renderRows(out *strings.Builder, rows []rowStr, hint renderHint)
|
||||
|
||||
func (t *Table) renderRowsBorderBottom(out *strings.Builder) {
|
||||
if len(t.rowsFooter) > 0 {
|
||||
t.renderRowSeparator(out, renderHint{isBorderBottom: true, isFooterRow: true, rowNumber: len(t.rowsFooter)})
|
||||
t.renderRowSeparator(out, renderHint{
|
||||
isBorderBottom: true,
|
||||
isFooterRow: true,
|
||||
rowNumber: len(t.rowsFooter),
|
||||
})
|
||||
} else {
|
||||
t.renderRowSeparator(out, renderHint{isBorderBottom: true, isFooterRow: false, rowNumber: len(t.rows)})
|
||||
t.renderRowSeparator(out, renderHint{
|
||||
isBorderBottom: true,
|
||||
isFooterRow: false,
|
||||
rowNumber: len(t.rows),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) renderRowsBorderTop(out *strings.Builder) {
|
||||
if len(t.rowsHeader) > 0 || t.autoIndex {
|
||||
t.renderRowSeparator(out, renderHint{isBorderTop: true, isHeaderRow: true, rowNumber: 0})
|
||||
t.renderRowSeparator(out, renderHint{
|
||||
isBorderTop: true,
|
||||
isHeaderRow: true,
|
||||
rowNumber: 0,
|
||||
})
|
||||
} else {
|
||||
t.renderRowSeparator(out, renderHint{isBorderTop: true, isHeaderRow: false, rowNumber: 0})
|
||||
t.renderRowSeparator(out, renderHint{
|
||||
isBorderTop: true,
|
||||
isHeaderRow: false,
|
||||
rowNumber: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -7,11 +7,12 @@ import (
|
||||
)
|
||||
|
||||
// RenderCSV renders the Table in CSV format. Example:
|
||||
// #,First Name,Last Name,Salary,
|
||||
// 1,Arya,Stark,3000,
|
||||
// 20,Jon,Snow,2000,"You know nothing\, Jon Snow!"
|
||||
// 300,Tyrion,Lannister,5000,
|
||||
// ,,Total,10000,
|
||||
//
|
||||
// #,First Name,Last Name,Salary,
|
||||
// 1,Arya,Stark,3000,
|
||||
// 20,Jon,Snow,2000,"You know nothing\, Jon Snow!"
|
||||
// 300,Tyrion,Lannister,5000,
|
||||
// ,,Total,10000,
|
||||
func (t *Table) RenderCSV() string {
|
||||
t.initForRender()
|
||||
|
||||
|
||||
+46
-45
@@ -13,49 +13,50 @@ const (
|
||||
)
|
||||
|
||||
// RenderHTML renders the Table in HTML format. Example:
|
||||
// <table class="go-pretty-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th align="right">#</th>
|
||||
// <th>First Name</th>
|
||||
// <th>Last Name</th>
|
||||
// <th align="right">Salary</th>
|
||||
// <th> </th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// <tr>
|
||||
// <td align="right">1</td>
|
||||
// <td>Arya</td>
|
||||
// <td>Stark</td>
|
||||
// <td align="right">3000</td>
|
||||
// <td> </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td align="right">20</td>
|
||||
// <td>Jon</td>
|
||||
// <td>Snow</td>
|
||||
// <td align="right">2000</td>
|
||||
// <td>You know nothing, Jon Snow!</td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td align="right">300</td>
|
||||
// <td>Tyrion</td>
|
||||
// <td>Lannister</td>
|
||||
// <td align="right">5000</td>
|
||||
// <td> </td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// <tfoot>
|
||||
// <tr>
|
||||
// <td align="right"> </td>
|
||||
// <td> </td>
|
||||
// <td>Total</td>
|
||||
// <td align="right">10000</td>
|
||||
// <td> </td>
|
||||
// </tr>
|
||||
// </tfoot>
|
||||
// </table>
|
||||
//
|
||||
// <table class="go-pretty-table">
|
||||
// <thead>
|
||||
// <tr>
|
||||
// <th align="right">#</th>
|
||||
// <th>First Name</th>
|
||||
// <th>Last Name</th>
|
||||
// <th align="right">Salary</th>
|
||||
// <th> </th>
|
||||
// </tr>
|
||||
// </thead>
|
||||
// <tbody>
|
||||
// <tr>
|
||||
// <td align="right">1</td>
|
||||
// <td>Arya</td>
|
||||
// <td>Stark</td>
|
||||
// <td align="right">3000</td>
|
||||
// <td> </td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td align="right">20</td>
|
||||
// <td>Jon</td>
|
||||
// <td>Snow</td>
|
||||
// <td align="right">2000</td>
|
||||
// <td>You know nothing, Jon Snow!</td>
|
||||
// </tr>
|
||||
// <tr>
|
||||
// <td align="right">300</td>
|
||||
// <td>Tyrion</td>
|
||||
// <td>Lannister</td>
|
||||
// <td align="right">5000</td>
|
||||
// <td> </td>
|
||||
// </tr>
|
||||
// </tbody>
|
||||
// <tfoot>
|
||||
// <tr>
|
||||
// <td align="right"> </td>
|
||||
// <td> </td>
|
||||
// <td>Total</td>
|
||||
// <td align="right">10000</td>
|
||||
// <td> </td>
|
||||
// </tr>
|
||||
// </tfoot>
|
||||
// </table>
|
||||
func (t *Table) RenderHTML() string {
|
||||
t.initForRender()
|
||||
|
||||
@@ -112,7 +113,7 @@ func (t *Table) htmlRenderColumn(out *strings.Builder, colStr string) {
|
||||
out.WriteString(colStr)
|
||||
}
|
||||
|
||||
func (t *Table) htmlRenderColumnAttributes(out *strings.Builder, row rowStr, colIdx int, hint renderHint) {
|
||||
func (t *Table) htmlRenderColumnAttributes(out *strings.Builder, colIdx int, hint renderHint) {
|
||||
// determine the HTML "align"/"valign" property values
|
||||
align := t.getAlign(colIdx, hint).HTMLProperty()
|
||||
vAlign := t.getVAlign(colIdx, hint).HTMLProperty()
|
||||
@@ -161,7 +162,7 @@ func (t *Table) htmlRenderRow(out *strings.Builder, row rowStr, hint renderHint)
|
||||
// write the row
|
||||
out.WriteString(" <")
|
||||
out.WriteString(colTagName)
|
||||
t.htmlRenderColumnAttributes(out, row, colIdx, hint)
|
||||
t.htmlRenderColumnAttributes(out, colIdx, hint)
|
||||
out.WriteString(">")
|
||||
if len(colStr) == 0 {
|
||||
out.WriteString(t.style.HTML.EmptyColumn)
|
||||
|
||||
+3
-6
@@ -43,12 +43,8 @@ func (t *Table) analyzeAndStringifyColumn(colIdx int, col interface{}, hint rend
|
||||
} else {
|
||||
colStr = fmt.Sprint(col)
|
||||
}
|
||||
if strings.Contains(colStr, "\t") {
|
||||
colStr = strings.Replace(colStr, "\t", " ", -1)
|
||||
}
|
||||
if strings.Contains(colStr, "\r") {
|
||||
colStr = strings.Replace(colStr, "\r", "", -1)
|
||||
}
|
||||
colStr = strings.ReplaceAll(colStr, "\t", " ")
|
||||
colStr = text.ProcessCRLF(colStr)
|
||||
return fmt.Sprintf("%s%s", t.style.Format.Direction.Modifier(), colStr)
|
||||
}
|
||||
|
||||
@@ -282,6 +278,7 @@ func (t *Table) reset() {
|
||||
t.autoIndexVIndexMaxLength = 0
|
||||
t.columnConfigMap = nil
|
||||
t.columnIsNonNumeric = nil
|
||||
t.firstRowOfPage = true
|
||||
t.maxColumnLengths = nil
|
||||
t.maxRowLength = 0
|
||||
t.numColumns = 0
|
||||
|
||||
+9
-13
@@ -6,12 +6,13 @@ import (
|
||||
)
|
||||
|
||||
// RenderMarkdown renders the Table in Markdown format. Example:
|
||||
// | # | First Name | Last Name | Salary | |
|
||||
// | ---:| --- | --- | ---:| --- |
|
||||
// | 1 | Arya | Stark | 3000 | |
|
||||
// | 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
|
||||
// | 300 | Tyrion | Lannister | 5000 | |
|
||||
// | | | Total | 10000 | |
|
||||
//
|
||||
// | # | First Name | Last Name | Salary | |
|
||||
// | ---:| --- | --- | ---:| --- |
|
||||
// | 1 | Arya | Stark | 3000 | |
|
||||
// | 20 | Jon | Snow | 2000 | You know nothing, Jon Snow! |
|
||||
// | 300 | Tyrion | Lannister | 5000 | |
|
||||
// | | | Total | 10000 | |
|
||||
func (t *Table) RenderMarkdown() string {
|
||||
t.initForRender()
|
||||
|
||||
@@ -54,12 +55,8 @@ func (t *Table) markdownRenderRow(out *strings.Builder, row rowStr, hint renderH
|
||||
colStr = row[colIdx]
|
||||
}
|
||||
out.WriteRune(' ')
|
||||
if strings.Contains(colStr, "|") {
|
||||
colStr = strings.Replace(colStr, "|", "\\|", -1)
|
||||
}
|
||||
if strings.Contains(colStr, "\n") {
|
||||
colStr = strings.Replace(colStr, "\n", "<br/>", -1)
|
||||
}
|
||||
colStr = strings.ReplaceAll(colStr, "|", "\\|")
|
||||
colStr = strings.ReplaceAll(colStr, "\n", "<br/>")
|
||||
out.WriteString(colStr)
|
||||
out.WriteRune(' ')
|
||||
}
|
||||
@@ -94,7 +91,6 @@ func (t *Table) markdownRenderRows(out *strings.Builder, rows []rowStr, hint ren
|
||||
|
||||
func (t *Table) markdownRenderRowsFooter(out *strings.Builder) {
|
||||
t.markdownRenderRows(out, t.rowsFooter, renderHint{isFooterRow: true})
|
||||
|
||||
}
|
||||
|
||||
func (t *Table) markdownRenderRowsHeader(out *strings.Builder) {
|
||||
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (t *Table) RenderTSV() string {
|
||||
t.initForRender()
|
||||
|
||||
var out strings.Builder
|
||||
|
||||
if t.numColumns > 0 {
|
||||
if t.title != "" {
|
||||
out.WriteString(t.title)
|
||||
}
|
||||
|
||||
if t.autoIndex && len(t.rowsHeader) == 0 {
|
||||
t.tsvRenderRow(&out, t.getAutoIndexColumnIDs(), renderHint{isAutoIndexRow: true, isHeaderRow: true})
|
||||
}
|
||||
|
||||
t.tsvRenderRows(&out, t.rowsHeader, renderHint{isHeaderRow: true})
|
||||
t.tsvRenderRows(&out, t.rows, renderHint{})
|
||||
t.tsvRenderRows(&out, t.rowsFooter, renderHint{isFooterRow: true})
|
||||
|
||||
if t.caption != "" {
|
||||
out.WriteRune('\n')
|
||||
out.WriteString(t.caption)
|
||||
}
|
||||
}
|
||||
|
||||
return t.render(&out)
|
||||
}
|
||||
|
||||
func (t *Table) tsvRenderRow(out *strings.Builder, row rowStr, hint renderHint) {
|
||||
if out.Len() > 0 {
|
||||
out.WriteRune('\n')
|
||||
}
|
||||
|
||||
for idx, col := range row {
|
||||
if idx == 0 && t.autoIndex {
|
||||
if hint.isRegularRow() {
|
||||
out.WriteString(fmt.Sprint(hint.rowNumber))
|
||||
}
|
||||
out.WriteRune('\t')
|
||||
}
|
||||
|
||||
if idx > 0 {
|
||||
out.WriteRune('\t')
|
||||
}
|
||||
|
||||
if strings.ContainsAny(col, "\t\n\"") || strings.Contains(col, " ") {
|
||||
out.WriteString(fmt.Sprintf("\"%s\"", t.tsvFixDoubleQuotes(col)))
|
||||
} else {
|
||||
out.WriteString(col)
|
||||
}
|
||||
}
|
||||
|
||||
for colIdx := len(row); colIdx < t.numColumns; colIdx++ {
|
||||
out.WriteRune('\t')
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Table) tsvFixDoubleQuotes(str string) string {
|
||||
return strings.Replace(str, "\"", "\"\"", -1)
|
||||
}
|
||||
|
||||
func (t *Table) tsvRenderRows(out *strings.Builder, rows []rowStr, hint renderHint) {
|
||||
for idx, row := range rows {
|
||||
hint.rowNumber = idx + 1
|
||||
t.tsvRenderRow(out, row, hint)
|
||||
}
|
||||
}
|
||||
+126
-25
@@ -3,6 +3,7 @@ package table
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SortBy defines What to sort (Column Name or Number), and How to sort (Mode).
|
||||
@@ -17,6 +18,9 @@ type SortBy struct {
|
||||
|
||||
// Mode tells the Writer how to Sort. Asc/Dsc/etc.
|
||||
Mode SortMode
|
||||
|
||||
// IgnoreCase makes sorting case-insensitive
|
||||
IgnoreCase bool
|
||||
}
|
||||
|
||||
// SortMode defines How to sort.
|
||||
@@ -25,12 +29,24 @@ type SortMode int
|
||||
const (
|
||||
// Asc sorts the column in Ascending order alphabetically.
|
||||
Asc SortMode = iota
|
||||
// AscAlphaNumeric sorts the column in Ascending order alphabetically and
|
||||
// then numerically.
|
||||
AscAlphaNumeric
|
||||
// AscNumeric sorts the column in Ascending order numerically.
|
||||
AscNumeric
|
||||
// AscNumericAlpha sorts the column in Ascending order numerically and
|
||||
// then alphabetically.
|
||||
AscNumericAlpha
|
||||
// Dsc sorts the column in Descending order alphabetically.
|
||||
Dsc
|
||||
// DscAlphaNumeric sorts the column in Descending order alphabetically and
|
||||
// then numerically.
|
||||
DscAlphaNumeric
|
||||
// DscNumeric sorts the column in Descending order numerically.
|
||||
DscNumeric
|
||||
// DscNumericAlpha sorts the column in Descending order numerically and
|
||||
// then alphabetically.
|
||||
DscNumericAlpha
|
||||
)
|
||||
|
||||
type rowsSorter struct {
|
||||
@@ -74,9 +90,10 @@ func (t *Table) parseSortBy(sortBy []SortBy) []SortBy {
|
||||
}
|
||||
if colNum > 0 {
|
||||
resSortBy = append(resSortBy, SortBy{
|
||||
Name: col.Name,
|
||||
Number: colNum,
|
||||
Mode: col.Mode,
|
||||
Name: col.Name,
|
||||
Number: colNum,
|
||||
Mode: col.Mode,
|
||||
IgnoreCase: col.IgnoreCase,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -92,36 +109,120 @@ func (rs rowsSorter) Swap(i, j int) {
|
||||
}
|
||||
|
||||
func (rs rowsSorter) Less(i, j int) bool {
|
||||
shouldContinue, returnValue := false, false
|
||||
realI, realJ := rs.sortedIndices[i], rs.sortedIndices[j]
|
||||
for _, col := range rs.sortBy {
|
||||
rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], col.Number-1
|
||||
if colIdx < len(rowI) && colIdx < len(rowJ) {
|
||||
shouldContinue, returnValue := rs.lessColumns(rowI, rowJ, colIdx, col)
|
||||
if !shouldContinue {
|
||||
return returnValue
|
||||
}
|
||||
for _, sortBy := range rs.sortBy {
|
||||
// extract the values/cells from the rows for comparison
|
||||
rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], sortBy.Number-1
|
||||
iVal, jVal := "", ""
|
||||
if colIdx < len(rowI) {
|
||||
iVal = rowI[colIdx]
|
||||
}
|
||||
if colIdx < len(rowJ) {
|
||||
jVal = rowJ[colIdx]
|
||||
}
|
||||
|
||||
// compare and choose whether to continue
|
||||
shouldContinue, returnValue = less(iVal, jVal, sortBy)
|
||||
if !shouldContinue {
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
return returnValue
|
||||
}
|
||||
|
||||
func (rs rowsSorter) lessColumns(rowI rowStr, rowJ rowStr, colIdx int, col SortBy) (bool, bool) {
|
||||
if rowI[colIdx] == rowJ[colIdx] {
|
||||
func less(iVal string, jVal string, sb SortBy) (bool, bool) {
|
||||
if iVal == jVal {
|
||||
return true, false
|
||||
} else if col.Mode == Asc {
|
||||
return false, rowI[colIdx] < rowJ[colIdx]
|
||||
} else if col.Mode == Dsc {
|
||||
return false, rowI[colIdx] > rowJ[colIdx]
|
||||
}
|
||||
|
||||
iVal, iErr := strconv.ParseFloat(rowI[colIdx], 64)
|
||||
jVal, jErr := strconv.ParseFloat(rowJ[colIdx], 64)
|
||||
if iErr == nil && jErr == nil {
|
||||
if col.Mode == AscNumeric {
|
||||
return false, iVal < jVal
|
||||
} else if col.Mode == DscNumeric {
|
||||
return false, jVal < iVal
|
||||
switch sb.Mode {
|
||||
case Asc, Dsc:
|
||||
return lessAlphabetic(iVal, jVal, sb)
|
||||
case AscNumeric, DscNumeric:
|
||||
return lessNumeric(iVal, jVal, sb)
|
||||
default: // AscAlphaNumeric, AscNumericAlpha, DscAlphaNumeric, DscNumericAlpha
|
||||
return lessMixedMode(iVal, jVal, sb)
|
||||
}
|
||||
}
|
||||
|
||||
func lessAlphabetic(iVal string, jVal string, sb SortBy) (bool, bool) {
|
||||
if sb.IgnoreCase {
|
||||
iLow := strings.ToLower(iVal)
|
||||
jLow := strings.ToLower(jVal)
|
||||
// when two strings are case-insensitive identical, compare them casesensitive.
|
||||
// That makes sure to get a consistent sorting
|
||||
identical := iLow == jLow
|
||||
switch sb.Mode {
|
||||
case Asc, AscAlphaNumeric, AscNumericAlpha:
|
||||
return identical, (identical && iVal < jVal) || iLow < jLow
|
||||
default: // Dsc, DscAlphaNumeric, DscNumericAlpha
|
||||
return identical, (identical && iVal > jVal) || iLow > jLow
|
||||
}
|
||||
}
|
||||
return true, false
|
||||
switch sb.Mode {
|
||||
case Asc, AscAlphaNumeric, AscNumericAlpha:
|
||||
return false, iVal < jVal
|
||||
default: // Dsc, DscAlphaNumeric, DscNumericAlpha
|
||||
return false, iVal > jVal
|
||||
}
|
||||
}
|
||||
|
||||
func lessAlphaNumericI(sb SortBy) (bool, bool) {
|
||||
// i == "abc"; j == 5
|
||||
switch sb.Mode {
|
||||
case AscAlphaNumeric, DscAlphaNumeric:
|
||||
return false, true
|
||||
default: // AscNumericAlpha, DscNumericAlpha
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
||||
func lessAlphaNumericJ(sb SortBy) (bool, bool) {
|
||||
// i == 5; j == "abc"
|
||||
switch sb.Mode {
|
||||
case AscAlphaNumeric, DscAlphaNumeric:
|
||||
return false, false
|
||||
default: // AscNumericAlpha, DscNumericAlpha:
|
||||
return false, true
|
||||
}
|
||||
}
|
||||
|
||||
func lessMixedMode(iVal string, jVal string, sb SortBy) (bool, bool) {
|
||||
iNumVal, iErr := strconv.ParseFloat(iVal, 64)
|
||||
jNumVal, jErr := strconv.ParseFloat(jVal, 64)
|
||||
if iErr != nil && jErr != nil { // both are alphanumeric
|
||||
return lessAlphabetic(iVal, jVal, sb)
|
||||
}
|
||||
if iErr != nil { // iVal is alphabetic, jVal is numeric
|
||||
return lessAlphaNumericI(sb)
|
||||
}
|
||||
if jErr != nil { // iVal is numeric, jVal is alphabetic
|
||||
return lessAlphaNumericJ(sb)
|
||||
}
|
||||
// both values numeric
|
||||
return lessNumericVal(iNumVal, jNumVal, sb)
|
||||
}
|
||||
|
||||
func lessNumeric(iVal string, jVal string, sb SortBy) (bool, bool) {
|
||||
iNumVal, iErr := strconv.ParseFloat(iVal, 64)
|
||||
jNumVal, jErr := strconv.ParseFloat(jVal, 64)
|
||||
if iErr != nil || jErr != nil {
|
||||
return false, false
|
||||
}
|
||||
|
||||
return lessNumericVal(iNumVal, jNumVal, sb)
|
||||
}
|
||||
|
||||
func lessNumericVal(iVal float64, jVal float64, sb SortBy) (bool, bool) {
|
||||
if iVal == jVal {
|
||||
return true, false
|
||||
}
|
||||
|
||||
switch sb.Mode {
|
||||
case AscNumeric, AscAlphaNumeric, AscNumericAlpha:
|
||||
return false, iVal < jVal
|
||||
default: // DscNumeric, DscAlphaNumeric, DscNumericAlpha
|
||||
return false, iVal > jVal
|
||||
}
|
||||
}
|
||||
+13
-17
@@ -675,14 +675,12 @@ type FormatOptions struct {
|
||||
Row text.Format // (data) row(s) text format
|
||||
}
|
||||
|
||||
var (
|
||||
// FormatOptionsDefault defines sensible formatting options.
|
||||
FormatOptionsDefault = FormatOptions{
|
||||
Footer: text.FormatUpper,
|
||||
Header: text.FormatUpper,
|
||||
Row: text.FormatDefault,
|
||||
}
|
||||
)
|
||||
// FormatOptionsDefault defines sensible formatting options.
|
||||
var FormatOptionsDefault = FormatOptions{
|
||||
Footer: text.FormatUpper,
|
||||
Header: text.FormatUpper,
|
||||
Row: text.FormatDefault,
|
||||
}
|
||||
|
||||
// HTMLOptions defines the global options to control HTML rendering.
|
||||
type HTMLOptions struct {
|
||||
@@ -692,15 +690,13 @@ type HTMLOptions struct {
|
||||
Newline string // string to replace "\n" characters with
|
||||
}
|
||||
|
||||
var (
|
||||
// DefaultHTMLOptions defines sensible HTML rendering defaults.
|
||||
DefaultHTMLOptions = HTMLOptions{
|
||||
CSSClass: DefaultHTMLCSSClass,
|
||||
EmptyColumn: " ",
|
||||
EscapeText: true,
|
||||
Newline: "<br/>",
|
||||
}
|
||||
)
|
||||
// DefaultHTMLOptions defines sensible HTML rendering defaults.
|
||||
var DefaultHTMLOptions = HTMLOptions{
|
||||
CSSClass: DefaultHTMLCSSClass,
|
||||
EmptyColumn: " ",
|
||||
EscapeText: true,
|
||||
Newline: "<br/>",
|
||||
}
|
||||
|
||||
// Options defines the global options that determine how the Table is
|
||||
// rendered.
|
||||
|
||||
+59
-25
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/jedib0t/go-pretty/v6/text"
|
||||
)
|
||||
@@ -32,13 +33,13 @@ func (row rowStr) areEqual(colIdx1 int, colIdx2 int) bool {
|
||||
return colIdx1 >= 0 && colIdx2 < len(row) && row[colIdx1] == row[colIdx2]
|
||||
}
|
||||
|
||||
// Table helps print a 2-dimensional array in a human readable pretty-table.
|
||||
// Table helps print a 2-dimensional array in a human-readable pretty-table.
|
||||
type Table struct {
|
||||
// allowedRowLength is the max allowed length for a row (or line of output)
|
||||
allowedRowLength int
|
||||
// enable automatic indexing of the rows and columns like a spreadsheet?
|
||||
autoIndex bool
|
||||
// autoIndexVIndexMaxLength denotes the length in chars for the last rownum
|
||||
// autoIndexVIndexMaxLength denotes the length in chars for the last row
|
||||
autoIndexVIndexMaxLength int
|
||||
// caption stores the text to be rendered just below the table; and doesn't
|
||||
// get used when rendered as a CSV
|
||||
@@ -50,6 +51,8 @@ type Table struct {
|
||||
// columnConfigMap stores the custom-configuration by column
|
||||
// number and is generated before rendering
|
||||
columnConfigMap map[int]ColumnConfig
|
||||
// firstRowOfPage tells if the renderer is on the first row of a page?
|
||||
firstRowOfPage bool
|
||||
// htmlCSSClass stores the HTML CSS Class to use on the <table> node
|
||||
htmlCSSClass string
|
||||
// indexColumn stores the number of the column considered as the "index"
|
||||
@@ -106,6 +109,8 @@ type Table struct {
|
||||
// suppressEmptyColumns hides columns which have no content on all regular
|
||||
// rows
|
||||
suppressEmptyColumns bool
|
||||
// supressTrailingSpaces removes all trailing spaces from the end of the last column
|
||||
supressTrailingSpaces bool
|
||||
// title contains the text to appear above the table
|
||||
title string
|
||||
}
|
||||
@@ -164,14 +169,15 @@ func (t *Table) AppendRows(rows []Row, config ...RowConfig) {
|
||||
// append is a separator, it will not be rendered in addition to the usual table
|
||||
// separator.
|
||||
//
|
||||
//******************************************************************************
|
||||
// ******************************************************************************
|
||||
// Please note the following caveats:
|
||||
// 1. SetPageSize(): this may end up creating consecutive separator rows near
|
||||
// the end of a page or at the beginning of a page
|
||||
// 2. SortBy(): since SortBy could inherently alter the ordering of rows, the
|
||||
// separators may not appear after the row it was originally intended to
|
||||
// follow
|
||||
//******************************************************************************
|
||||
// 1. SetPageSize(): this may end up creating consecutive separator rows near
|
||||
// the end of a page or at the beginning of a page
|
||||
// 2. SortBy(): since SortBy could inherently alter the ordering of rows, the
|
||||
// separators may not appear after the row it was originally intended to
|
||||
// follow
|
||||
//
|
||||
// ******************************************************************************
|
||||
func (t *Table) AppendSeparator() {
|
||||
if t.separators == nil {
|
||||
t.separators = make(map[int]bool)
|
||||
@@ -228,7 +234,7 @@ func (t *Table) SetColumnConfigs(configs []ColumnConfig) {
|
||||
t.columnConfigs = configs
|
||||
}
|
||||
|
||||
// SetHTMLCSSClass sets the the HTML CSS Class to use on the <table> node
|
||||
// SetHTMLCSSClass sets the HTML CSS Class to use on the <table> node
|
||||
// when rendering the Table in HTML format.
|
||||
//
|
||||
// Deprecated: in favor of Style().HTML.CSSClass
|
||||
@@ -296,6 +302,11 @@ func (t *Table) SuppressEmptyColumns() {
|
||||
t.suppressEmptyColumns = true
|
||||
}
|
||||
|
||||
// SuppressTrailingSpaces removes all trailing spaces from the output.
|
||||
func (t *Table) SuppressTrailingSpaces() {
|
||||
t.supressTrailingSpaces = true
|
||||
}
|
||||
|
||||
func (t *Table) getAlign(colIdx int, hint renderHint) text.Align {
|
||||
align := text.AlignDefault
|
||||
if cfg, ok := t.columnConfigMap[colIdx]; ok {
|
||||
@@ -388,7 +399,7 @@ func (t *Table) getBorderRight(hint renderHint) string {
|
||||
|
||||
func (t *Table) getColumnColors(colIdx int, hint renderHint) text.Colors {
|
||||
if hint.isBorderOrSeparator() {
|
||||
if colors := t.getColumnColorsForBorderOrSeparator(colIdx, hint); colors != nil {
|
||||
if colors := t.getColumnColorsForBorderOrSeparator(hint); colors != nil {
|
||||
return colors
|
||||
}
|
||||
}
|
||||
@@ -410,7 +421,7 @@ func (t *Table) getColumnColors(colIdx int, hint renderHint) text.Colors {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Table) getColumnColorsForBorderOrSeparator(colIdx int, hint renderHint) text.Colors {
|
||||
func (t *Table) getColumnColorsForBorderOrSeparator(hint renderHint) text.Colors {
|
||||
if t.style.Options.DoNotColorBordersAndSeparators {
|
||||
return text.Colors{} // not nil to force caller to paint with no colors
|
||||
}
|
||||
@@ -439,12 +450,9 @@ func (t *Table) getColumnSeparator(row rowStr, colIdx int, hint renderHint) stri
|
||||
separator = t.style.Box.BottomSeparator
|
||||
}
|
||||
} else {
|
||||
separator = t.getColumnSeparatorNonBorder(
|
||||
t.shouldMergeCellsHorizontallyAbove(row, colIdx, hint),
|
||||
t.shouldMergeCellsHorizontallyBelow(row, colIdx, hint),
|
||||
colIdx,
|
||||
hint,
|
||||
)
|
||||
sm1 := t.shouldMergeCellsHorizontallyAbove(row, colIdx, hint)
|
||||
sm2 := t.shouldMergeCellsHorizontallyBelow(row, colIdx, hint)
|
||||
separator = t.getColumnSeparatorNonBorder(sm1, sm2, colIdx, hint)
|
||||
}
|
||||
}
|
||||
return separator
|
||||
@@ -681,6 +689,13 @@ func (t *Table) isIndexColumn(colIdx int, hint renderHint) bool {
|
||||
|
||||
func (t *Table) render(out *strings.Builder) string {
|
||||
outStr := out.String()
|
||||
if t.supressTrailingSpaces {
|
||||
var trimmed []string
|
||||
for _, line := range strings.Split(outStr, "\n") {
|
||||
trimmed = append(trimmed, strings.TrimRightFunc(line, unicode.IsSpace))
|
||||
}
|
||||
outStr = strings.Join(trimmed, "\n")
|
||||
}
|
||||
if t.outputMirror != nil && len(outStr) > 0 {
|
||||
_, _ = t.outputMirror.Write([]byte(outStr))
|
||||
_, _ = t.outputMirror.Write([]byte("\n"))
|
||||
@@ -721,7 +736,10 @@ func (t *Table) shouldMergeCellsHorizontallyBelow(row rowStr, colIdx int, hint r
|
||||
|
||||
var rowConfig RowConfig
|
||||
if hint.isSeparatorRow {
|
||||
if hint.isHeaderRow && hint.rowNumber == 0 {
|
||||
if hint.isRegularRow() {
|
||||
rowConfig = t.getRowConfig(renderHint{rowNumber: hint.rowNumber + 1})
|
||||
row = t.getRow(hint.rowNumber, renderHint{})
|
||||
} else if hint.isHeaderRow && hint.rowNumber == 0 {
|
||||
rowConfig = t.getRowConfig(renderHint{isHeaderRow: true, rowNumber: 1})
|
||||
row = t.getRow(0, hint)
|
||||
} else if hint.isHeaderRow && hint.isLastRow {
|
||||
@@ -733,9 +751,6 @@ func (t *Table) shouldMergeCellsHorizontallyBelow(row rowStr, colIdx int, hint r
|
||||
} else if hint.isFooterRow && hint.rowNumber >= 0 {
|
||||
rowConfig = t.getRowConfig(renderHint{isFooterRow: true, rowNumber: 1})
|
||||
row = t.getRow(hint.rowNumber, renderHint{isFooterRow: true})
|
||||
} else if hint.isRegularRow() {
|
||||
rowConfig = t.getRowConfig(renderHint{rowNumber: hint.rowNumber + 1})
|
||||
row = t.getRow(hint.rowNumber, renderHint{})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -746,24 +761,43 @@ func (t *Table) shouldMergeCellsHorizontallyBelow(row rowStr, colIdx int, hint r
|
||||
}
|
||||
|
||||
func (t *Table) shouldMergeCellsVertically(colIdx int, hint renderHint) bool {
|
||||
if t.columnConfigMap[colIdx].AutoMerge && colIdx < t.numColumns {
|
||||
if !t.firstRowOfPage && t.columnConfigMap[colIdx].AutoMerge && colIdx < t.numColumns {
|
||||
if hint.isSeparatorRow {
|
||||
rowPrev := t.getRow(hint.rowNumber-1, hint)
|
||||
rowNext := t.getRow(hint.rowNumber, hint)
|
||||
if colIdx < len(rowPrev) && colIdx < len(rowNext) {
|
||||
return rowPrev[colIdx] == rowNext[colIdx] || "" == rowNext[colIdx]
|
||||
return rowPrev[colIdx] == rowNext[colIdx]
|
||||
}
|
||||
} else {
|
||||
rowPrev := t.getRow(hint.rowNumber-2, hint)
|
||||
rowCurr := t.getRow(hint.rowNumber-1, hint)
|
||||
if colIdx < len(rowPrev) && colIdx < len(rowCurr) {
|
||||
return rowPrev[colIdx] == rowCurr[colIdx] || "" == rowCurr[colIdx]
|
||||
return rowPrev[colIdx] == rowCurr[colIdx]
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Table) shouldSeparateRows(rowIdx int, numRows int) bool {
|
||||
// not asked to separate rows and no manually added separator
|
||||
if !t.style.Options.SeparateRows && !t.separators[rowIdx] {
|
||||
return false
|
||||
}
|
||||
|
||||
pageSize := numRows
|
||||
if t.pageSize > 0 {
|
||||
pageSize = t.pageSize
|
||||
}
|
||||
if rowIdx%pageSize == pageSize-1 { // last row of page
|
||||
return false
|
||||
}
|
||||
if rowIdx == numRows-1 { // last row of table
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (t *Table) wrapRow(row rowStr) (int, rowStr) {
|
||||
colMaxLines := 0
|
||||
rowWrapped := make(rowStr, len(row))
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ func AutoIndexColumnID(colIdx int) string {
|
||||
type WidthEnforcer func(col string, maxLen int) string
|
||||
|
||||
// widthEnforcerNone returns the input string as is without any modifications.
|
||||
func widthEnforcerNone(col string, maxLen int) string {
|
||||
func widthEnforcerNone(col string, _ int) string {
|
||||
return col
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -4,7 +4,7 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Writer declares the interfaces that can be used to setup and render a table.
|
||||
// Writer declares the interfaces that can be used to set up and render a table.
|
||||
type Writer interface {
|
||||
AppendFooter(row Row, configs ...RowConfig)
|
||||
AppendHeader(row Row, configs ...RowConfig)
|
||||
@@ -16,6 +16,7 @@ type Writer interface {
|
||||
RenderCSV() string
|
||||
RenderHTML() string
|
||||
RenderMarkdown() string
|
||||
RenderTSV() string
|
||||
ResetFooters()
|
||||
ResetHeaders()
|
||||
ResetRows()
|
||||
@@ -32,6 +33,7 @@ type Writer interface {
|
||||
SortBy(sortBy []SortBy)
|
||||
Style() *Style
|
||||
SuppressEmptyColumns()
|
||||
SuppressTrailingSpaces()
|
||||
|
||||
// deprecated; in favor of Style().HTML.CSSClass
|
||||
SetHTMLCSSClass(cssClass string)
|
||||
|
||||
+43
-31
@@ -17,32 +17,44 @@ const (
|
||||
AlignCenter // " center "
|
||||
AlignJustify // "justify it"
|
||||
AlignRight // " right"
|
||||
AlignAuto // AlignRight for numbers, AlignLeft for the rest
|
||||
)
|
||||
|
||||
// Apply aligns the text as directed. For ex.:
|
||||
// * AlignDefault.Apply("Jon Snow", 12) returns "Jon Snow "
|
||||
// * AlignLeft.Apply("Jon Snow", 12) returns "Jon Snow "
|
||||
// * AlignCenter.Apply("Jon Snow", 12) returns " Jon Snow "
|
||||
// * AlignJustify.Apply("Jon Snow", 12) returns "Jon Snow"
|
||||
// * AlignRight.Apply("Jon Snow", 12) returns " Jon Snow"
|
||||
// - AlignDefault.Apply("Jon Snow", 12) returns "Jon Snow "
|
||||
// - AlignLeft.Apply("Jon Snow", 12) returns "Jon Snow "
|
||||
// - AlignCenter.Apply("Jon Snow", 12) returns " Jon Snow "
|
||||
// - AlignJustify.Apply("Jon Snow", 12) returns "Jon Snow"
|
||||
// - AlignRight.Apply("Jon Snow", 12) returns " Jon Snow"
|
||||
// - AlignAuto.Apply("Jon Snow", 12) returns "Jon Snow "
|
||||
func (a Align) Apply(text string, maxLength int) string {
|
||||
text = a.trimString(text)
|
||||
aComputed := a
|
||||
if aComputed == AlignAuto {
|
||||
_, err := strconv.ParseFloat(text, 64)
|
||||
if err == nil { // was able to parse a number out of the string
|
||||
aComputed = AlignRight
|
||||
} else {
|
||||
aComputed = AlignLeft
|
||||
}
|
||||
}
|
||||
|
||||
text = aComputed.trimString(text)
|
||||
sLen := utf8.RuneCountInString(text)
|
||||
sLenWoE := RuneWidthWithoutEscSequences(text)
|
||||
numEscChars := sLen - sLenWoE
|
||||
|
||||
// now, align the text
|
||||
switch a {
|
||||
switch aComputed {
|
||||
case AlignDefault, AlignLeft:
|
||||
return fmt.Sprintf("%-"+strconv.Itoa(maxLength+numEscChars)+"s", text)
|
||||
case AlignCenter:
|
||||
if sLenWoE < maxLength {
|
||||
// left pad with half the number of spaces needed before using %text
|
||||
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s",
|
||||
text+strings.Repeat(" ", int((maxLength-sLenWoE)/2)))
|
||||
text+strings.Repeat(" ", (maxLength-sLenWoE)/2))
|
||||
}
|
||||
case AlignJustify:
|
||||
return a.justifyText(text, sLenWoE, maxLength)
|
||||
return justifyText(text, sLenWoE, maxLength)
|
||||
}
|
||||
return fmt.Sprintf("%"+strconv.Itoa(maxLength+numEscChars)+"s", text)
|
||||
}
|
||||
@@ -77,16 +89,34 @@ func (a Align) MarkdownProperty() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (a Align) justifyText(text string, textLength int, maxLength int) string {
|
||||
func (a Align) trimString(text string) string {
|
||||
switch a {
|
||||
case AlignDefault, AlignLeft:
|
||||
if strings.HasSuffix(text, " ") {
|
||||
return strings.TrimRight(text, " ")
|
||||
}
|
||||
case AlignRight:
|
||||
if strings.HasPrefix(text, " ") {
|
||||
return strings.TrimLeft(text, " ")
|
||||
}
|
||||
default:
|
||||
if strings.HasPrefix(text, " ") || strings.HasSuffix(text, " ") {
|
||||
return strings.Trim(text, " ")
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func justifyText(text string, textLength int, maxLength int) string {
|
||||
// split the text into individual words
|
||||
wordsUnfiltered := strings.Split(text, " ")
|
||||
words := Filter(wordsUnfiltered, func(item string) bool {
|
||||
words := Filter(strings.Split(text, " "), func(item string) bool {
|
||||
return item != ""
|
||||
})
|
||||
// empty string implies spaces for maxLength
|
||||
// empty string implies result is just spaces for maxLength
|
||||
if len(words) == 0 {
|
||||
return strings.Repeat(" ", maxLength)
|
||||
}
|
||||
|
||||
// get the number of spaces to insert into the text
|
||||
numSpacesNeeded := maxLength - textLength + strings.Count(text, " ")
|
||||
numSpacesNeededBetweenWords := 0
|
||||
@@ -117,21 +147,3 @@ func (a Align) justifyText(text string, textLength int, maxLength int) string {
|
||||
}
|
||||
return outText.String()
|
||||
}
|
||||
|
||||
func (a Align) trimString(text string) string {
|
||||
switch a {
|
||||
case AlignDefault, AlignLeft:
|
||||
if strings.HasSuffix(text, " ") {
|
||||
return strings.TrimRight(text, " ")
|
||||
}
|
||||
case AlignRight:
|
||||
if strings.HasPrefix(text, " ") {
|
||||
return strings.TrimLeft(text, " ")
|
||||
}
|
||||
default:
|
||||
if strings.HasPrefix(text, " ") || strings.HasSuffix(text, " ") {
|
||||
return strings.Trim(text, " ")
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
+4
-2
@@ -8,7 +8,8 @@ var ANSICodesSupported = areANSICodesSupported()
|
||||
|
||||
// Escape encodes the string with the ANSI Escape Sequence.
|
||||
// For ex.:
|
||||
// Escape("Ghost", "") == "Ghost"
|
||||
//
|
||||
// Escape("Ghost", "") == "Ghost"
|
||||
// Escape("Ghost", "\x1b[91m") == "\x1b[91mGhost\x1b[0m"
|
||||
// Escape("\x1b[94mGhost\x1b[0mLady", "\x1b[91m") == "\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m"
|
||||
// Escape("Nymeria\x1b[94mGhost\x1b[0mLady", "\x1b[91m") == "\x1b[91mNymeria\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m"
|
||||
@@ -30,7 +31,8 @@ func Escape(str string, escapeSeq string) string {
|
||||
|
||||
// StripEscape strips all ANSI Escape Sequence from the string.
|
||||
// For ex.:
|
||||
// StripEscape("Ghost") == "Ghost"
|
||||
//
|
||||
// StripEscape("Ghost") == "Ghost"
|
||||
// StripEscape("\x1b[91mGhost\x1b[0m") == "Ghost"
|
||||
// StripEscape("\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m") == "GhostLady"
|
||||
// StripEscape("\x1b[91mNymeria\x1b[94mGhost\x1b[0m\x1b[91mLady\x1b[0m") == "NymeriaGhostLady"
|
||||
|
||||
+1
-3
@@ -10,9 +10,7 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
enableVTPMutex = sync.Mutex{}
|
||||
)
|
||||
var enableVTPMutex = sync.Mutex{}
|
||||
|
||||
func areANSICodesSupported() bool {
|
||||
enableVTPMutex.Lock()
|
||||
|
||||
+4
-8
@@ -8,9 +8,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
colorsEnabled = areANSICodesSupported()
|
||||
)
|
||||
var colorsEnabled = areANSICodesSupported()
|
||||
|
||||
// DisableColors (forcefully) disables color coding globally.
|
||||
func DisableColors() {
|
||||
@@ -23,7 +21,7 @@ func EnableColors() {
|
||||
}
|
||||
|
||||
// The logic here is inspired from github.com/fatih/color; the following is
|
||||
// the the bare minimum logic required to print Colored to the console.
|
||||
// the bare minimum logic required to print Colored to the console.
|
||||
// The differences:
|
||||
// * This one caches the escape sequences for cases with multiple colors
|
||||
// * This one handles cases where the incoming already has colors in the
|
||||
@@ -123,10 +121,8 @@ func (c Color) Sprintf(format string, a ...interface{}) string {
|
||||
// Example: Colors{FgCyan, BgBlack}
|
||||
type Colors []Color
|
||||
|
||||
var (
|
||||
// colorsSeqMap caches the escape sequence for a set of colors
|
||||
colorsSeqMap = sync.Map{}
|
||||
)
|
||||
// colorsSeqMap caches the escape sequence for a set of colors
|
||||
var colorsSeqMap = sync.Map{}
|
||||
|
||||
// EscapeSeq returns the ANSI escape sequence for the colors set.
|
||||
func (c Colors) EscapeSeq() string {
|
||||
|
||||
+44
-46
@@ -1,48 +1,46 @@
|
||||
package text
|
||||
|
||||
var (
|
||||
// colorCSSClassMap contains the equivalent CSS-class for all colors
|
||||
colorCSSClassMap = map[Color]string{
|
||||
Bold: "bold",
|
||||
Faint: "faint",
|
||||
Italic: "italic",
|
||||
Underline: "underline",
|
||||
BlinkSlow: "blink-slow",
|
||||
BlinkRapid: "blink-rapid",
|
||||
ReverseVideo: "reverse-video",
|
||||
Concealed: "concealed",
|
||||
CrossedOut: "crossed-out",
|
||||
FgBlack: "fg-black",
|
||||
FgRed: "fg-red",
|
||||
FgGreen: "fg-green",
|
||||
FgYellow: "fg-yellow",
|
||||
FgBlue: "fg-blue",
|
||||
FgMagenta: "fg-magenta",
|
||||
FgCyan: "fg-cyan",
|
||||
FgWhite: "fg-white",
|
||||
FgHiBlack: "fg-hi-black",
|
||||
FgHiRed: "fg-hi-red",
|
||||
FgHiGreen: "fg-hi-green",
|
||||
FgHiYellow: "fg-hi-yellow",
|
||||
FgHiBlue: "fg-hi-blue",
|
||||
FgHiMagenta: "fg-hi-magenta",
|
||||
FgHiCyan: "fg-hi-cyan",
|
||||
FgHiWhite: "fg-hi-white",
|
||||
BgBlack: "bg-black",
|
||||
BgRed: "bg-red",
|
||||
BgGreen: "bg-green",
|
||||
BgYellow: "bg-yellow",
|
||||
BgBlue: "bg-blue",
|
||||
BgMagenta: "bg-magenta",
|
||||
BgCyan: "bg-cyan",
|
||||
BgWhite: "bg-white",
|
||||
BgHiBlack: "bg-hi-black",
|
||||
BgHiRed: "bg-hi-red",
|
||||
BgHiGreen: "bg-hi-green",
|
||||
BgHiYellow: "bg-hi-yellow",
|
||||
BgHiBlue: "bg-hi-blue",
|
||||
BgHiMagenta: "bg-hi-magenta",
|
||||
BgHiCyan: "bg-hi-cyan",
|
||||
BgHiWhite: "bg-hi-white",
|
||||
}
|
||||
)
|
||||
// colorCSSClassMap contains the equivalent CSS-class for all colors
|
||||
var colorCSSClassMap = map[Color]string{
|
||||
Bold: "bold",
|
||||
Faint: "faint",
|
||||
Italic: "italic",
|
||||
Underline: "underline",
|
||||
BlinkSlow: "blink-slow",
|
||||
BlinkRapid: "blink-rapid",
|
||||
ReverseVideo: "reverse-video",
|
||||
Concealed: "concealed",
|
||||
CrossedOut: "crossed-out",
|
||||
FgBlack: "fg-black",
|
||||
FgRed: "fg-red",
|
||||
FgGreen: "fg-green",
|
||||
FgYellow: "fg-yellow",
|
||||
FgBlue: "fg-blue",
|
||||
FgMagenta: "fg-magenta",
|
||||
FgCyan: "fg-cyan",
|
||||
FgWhite: "fg-white",
|
||||
FgHiBlack: "fg-hi-black",
|
||||
FgHiRed: "fg-hi-red",
|
||||
FgHiGreen: "fg-hi-green",
|
||||
FgHiYellow: "fg-hi-yellow",
|
||||
FgHiBlue: "fg-hi-blue",
|
||||
FgHiMagenta: "fg-hi-magenta",
|
||||
FgHiCyan: "fg-hi-cyan",
|
||||
FgHiWhite: "fg-hi-white",
|
||||
BgBlack: "bg-black",
|
||||
BgRed: "bg-red",
|
||||
BgGreen: "bg-green",
|
||||
BgYellow: "bg-yellow",
|
||||
BgBlue: "bg-blue",
|
||||
BgMagenta: "bg-magenta",
|
||||
BgCyan: "bg-cyan",
|
||||
BgWhite: "bg-white",
|
||||
BgHiBlack: "bg-hi-black",
|
||||
BgHiRed: "bg-hi-red",
|
||||
BgHiGreen: "bg-hi-green",
|
||||
BgHiYellow: "bg-hi-yellow",
|
||||
BgHiBlue: "bg-hi-blue",
|
||||
BgHiMagenta: "bg-hi-magenta",
|
||||
BgHiCyan: "bg-hi-cyan",
|
||||
BgHiWhite: "bg-hi-white",
|
||||
}
|
||||
-1
@@ -47,5 +47,4 @@ func (e *escSeq) InspectRune(r rune) {
|
||||
}
|
||||
e.content.WriteRune(r)
|
||||
}
|
||||
return
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package text
|
||||
|
||||
import "fmt"
|
||||
|
||||
func Hyperlink(url, text string) string {
|
||||
if url == "" {
|
||||
return text
|
||||
}
|
||||
if text == "" {
|
||||
return url
|
||||
}
|
||||
// source https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
|
||||
return fmt.Sprintf("\x1b]8;;%s\x1b\\%s\x1b]8;;\x1b\\", url, text)
|
||||
}
|
||||
+92
-39
@@ -13,11 +13,12 @@ var (
|
||||
)
|
||||
|
||||
// InsertEveryN inserts the rune every N characters in the string. For ex.:
|
||||
// InsertEveryN("Ghost", '-', 1) == "G-h-o-s-t"
|
||||
// InsertEveryN("Ghost", '-', 2) == "Gh-os-t"
|
||||
// InsertEveryN("Ghost", '-', 3) == "Gho-st"
|
||||
// InsertEveryN("Ghost", '-', 4) == "Ghos-t"
|
||||
// InsertEveryN("Ghost", '-', 5) == "Ghost"
|
||||
//
|
||||
// InsertEveryN("Ghost", '-', 1) == "G-h-o-s-t"
|
||||
// InsertEveryN("Ghost", '-', 2) == "Gh-os-t"
|
||||
// InsertEveryN("Ghost", '-', 3) == "Gho-st"
|
||||
// InsertEveryN("Ghost", '-', 4) == "Ghos-t"
|
||||
// InsertEveryN("Ghost", '-', 5) == "Ghost"
|
||||
func InsertEveryN(str string, runeToInsert rune, n int) string {
|
||||
if n <= 0 {
|
||||
return str
|
||||
@@ -47,7 +48,8 @@ func InsertEveryN(str string, runeToInsert rune, n int) string {
|
||||
|
||||
// LongestLineLen returns the length of the longest "line" within the
|
||||
// argument string. For ex.:
|
||||
// LongestLineLen("Ghost!\nCome back here!\nRight now!") == 15
|
||||
//
|
||||
// LongestLineLen("Ghost!\nCome back here!\nRight now!") == 15
|
||||
func LongestLineLen(str string) int {
|
||||
maxLength, currLength, eSeq := 0, 0, escSeq{}
|
||||
for _, c := range str {
|
||||
@@ -91,11 +93,12 @@ func OverrideRuneWidthEastAsianWidth(val bool) {
|
||||
// Pad pads the given string with as many characters as needed to make it as
|
||||
// long as specified (maxLen). This function does not count escape sequences
|
||||
// while calculating length of the string. Ex.:
|
||||
// Pad("Ghost", 0, ' ') == "Ghost"
|
||||
// Pad("Ghost", 3, ' ') == "Ghost"
|
||||
// Pad("Ghost", 5, ' ') == "Ghost"
|
||||
// Pad("Ghost", 7, ' ') == "Ghost "
|
||||
// Pad("Ghost", 10, '.') == "Ghost....."
|
||||
//
|
||||
// Pad("Ghost", 0, ' ') == "Ghost"
|
||||
// Pad("Ghost", 3, ' ') == "Ghost"
|
||||
// Pad("Ghost", 5, ' ') == "Ghost"
|
||||
// Pad("Ghost", 7, ' ') == "Ghost "
|
||||
// Pad("Ghost", 10, '.') == "Ghost....."
|
||||
func Pad(str string, maxLen int, paddingChar rune) string {
|
||||
strLen := RuneWidthWithoutEscSequences(str)
|
||||
if strLen < maxLen {
|
||||
@@ -104,13 +107,57 @@ func Pad(str string, maxLen int, paddingChar rune) string {
|
||||
return str
|
||||
}
|
||||
|
||||
// ProcessCRLF converts "\r\n" to "\n", and processes lone "\r" by moving the
|
||||
// cursor/carriage to the start of the line and overwrites the contents
|
||||
// accordingly. Ex.:
|
||||
//
|
||||
// ProcessCRLF("abc") == "abc"
|
||||
// ProcessCRLF("abc\r\ndef") == "abc\ndef"
|
||||
// ProcessCRLF("abc\r\ndef\rghi") == "abc\nghi"
|
||||
// ProcessCRLF("abc\r\ndef\rghi\njkl") == "abc\nghi\njkl"
|
||||
// ProcessCRLF("abc\r\ndef\rghi\njkl\r") == "abc\nghi\njkl"
|
||||
// ProcessCRLF("abc\r\ndef\rghi\rjkl\rmn") == "abc\nmnl"
|
||||
func ProcessCRLF(str string) string {
|
||||
str = strings.ReplaceAll(str, "\r\n", "\n")
|
||||
if !strings.Contains(str, "\r") {
|
||||
return str
|
||||
}
|
||||
|
||||
lines := strings.Split(str, "\n")
|
||||
for lineIdx, line := range lines {
|
||||
if !strings.Contains(line, "\r") {
|
||||
continue
|
||||
}
|
||||
|
||||
lineRunes, newLineRunes := []rune(line), make([]rune, 0)
|
||||
for idx, realIdx := 0, 0; idx < len(lineRunes); idx++ {
|
||||
// if a CR, move "cursor" back to beginning of line
|
||||
if lineRunes[idx] == '\r' {
|
||||
realIdx = 0
|
||||
continue
|
||||
}
|
||||
|
||||
// if cursor is not at end, overwrite
|
||||
if realIdx < len(newLineRunes) {
|
||||
newLineRunes[realIdx] = lineRunes[idx]
|
||||
} else { // else append
|
||||
newLineRunes = append(newLineRunes, lineRunes[idx])
|
||||
}
|
||||
realIdx++
|
||||
}
|
||||
lines[lineIdx] = string(newLineRunes)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// RepeatAndTrim repeats the given string until it is as long as maxRunes.
|
||||
// For ex.:
|
||||
// RepeatAndTrim("", 5) == ""
|
||||
// RepeatAndTrim("Ghost", 0) == ""
|
||||
// RepeatAndTrim("Ghost", 5) == "Ghost"
|
||||
// RepeatAndTrim("Ghost", 7) == "GhostGh"
|
||||
// RepeatAndTrim("Ghost", 10) == "GhostGhost"
|
||||
//
|
||||
// RepeatAndTrim("", 5) == ""
|
||||
// RepeatAndTrim("Ghost", 0) == ""
|
||||
// RepeatAndTrim("Ghost", 5) == "Ghost"
|
||||
// RepeatAndTrim("Ghost", 7) == "GhostGh"
|
||||
// RepeatAndTrim("Ghost", 10) == "GhostGhost"
|
||||
func RepeatAndTrim(str string, maxRunes int) string {
|
||||
if str == "" || maxRunes == 0 {
|
||||
return ""
|
||||
@@ -123,10 +170,12 @@ func RepeatAndTrim(str string, maxRunes int) string {
|
||||
|
||||
// RuneCount is similar to utf8.RuneCountInString, except for the fact that it
|
||||
// ignores escape sequences while counting. For ex.:
|
||||
// RuneCount("") == 0
|
||||
// RuneCount("Ghost") == 5
|
||||
// RuneCount("\x1b[33mGhost\x1b[0m") == 5
|
||||
// RuneCount("\x1b[33mGhost\x1b[0") == 5
|
||||
//
|
||||
// RuneCount("") == 0
|
||||
// RuneCount("Ghost") == 5
|
||||
// RuneCount("\x1b[33mGhost\x1b[0m") == 5
|
||||
// RuneCount("\x1b[33mGhost\x1b[0") == 5
|
||||
//
|
||||
// Deprecated: in favor of RuneWidthWithoutEscSequences
|
||||
func RuneCount(str string) int {
|
||||
return RuneWidthWithoutEscSequences(str)
|
||||
@@ -135,21 +184,23 @@ func RuneCount(str string) int {
|
||||
// RuneWidth returns the mostly accurate character-width of the rune. This is
|
||||
// not 100% accurate as the character width is usually dependent on the
|
||||
// typeface (font) used in the console/terminal. For ex.:
|
||||
// RuneWidth('A') == 1
|
||||
// RuneWidth('ツ') == 2
|
||||
// RuneWidth('⊙') == 1
|
||||
// RuneWidth('︿') == 2
|
||||
// RuneWidth(0x27) == 0
|
||||
//
|
||||
// RuneWidth('A') == 1
|
||||
// RuneWidth('ツ') == 2
|
||||
// RuneWidth('⊙') == 1
|
||||
// RuneWidth('︿') == 2
|
||||
// RuneWidth(0x27) == 0
|
||||
func RuneWidth(r rune) int {
|
||||
return rwCondition.RuneWidth(r)
|
||||
}
|
||||
|
||||
// RuneWidthWithoutEscSequences is similar to RuneWidth, except for the fact
|
||||
// that it ignores escape sequences while counting. For ex.:
|
||||
// RuneWidthWithoutEscSequences("") == 0
|
||||
// RuneWidthWithoutEscSequences("Ghost") == 5
|
||||
// RuneWidthWithoutEscSequences("\x1b[33mGhost\x1b[0m") == 5
|
||||
// RuneWidthWithoutEscSequences("\x1b[33mGhost\x1b[0") == 5
|
||||
//
|
||||
// RuneWidthWithoutEscSequences("") == 0
|
||||
// RuneWidthWithoutEscSequences("Ghost") == 5
|
||||
// RuneWidthWithoutEscSequences("\x1b[33mGhost\x1b[0m") == 5
|
||||
// RuneWidthWithoutEscSequences("\x1b[33mGhost\x1b[0") == 5
|
||||
func RuneWidthWithoutEscSequences(str string) int {
|
||||
count, eSeq := 0, escSeq{}
|
||||
for _, c := range str {
|
||||
@@ -166,12 +217,13 @@ func RuneWidthWithoutEscSequences(str string) int {
|
||||
}
|
||||
|
||||
// Snip returns the given string with a fixed length. For ex.:
|
||||
// Snip("Ghost", 0, "~") == "Ghost"
|
||||
// Snip("Ghost", 1, "~") == "~"
|
||||
// Snip("Ghost", 3, "~") == "Gh~"
|
||||
// Snip("Ghost", 5, "~") == "Ghost"
|
||||
// Snip("Ghost", 7, "~") == "Ghost "
|
||||
// Snip("\x1b[33mGhost\x1b[0m", 7, "~") == "\x1b[33mGhost\x1b[0m "
|
||||
//
|
||||
// Snip("Ghost", 0, "~") == "Ghost"
|
||||
// Snip("Ghost", 1, "~") == "~"
|
||||
// Snip("Ghost", 3, "~") == "Gh~"
|
||||
// Snip("Ghost", 5, "~") == "Ghost"
|
||||
// Snip("Ghost", 7, "~") == "Ghost "
|
||||
// Snip("\x1b[33mGhost\x1b[0m", 7, "~") == "\x1b[33mGhost\x1b[0m "
|
||||
func Snip(str string, length int, snipIndicator string) string {
|
||||
if length > 0 {
|
||||
lenStr := RuneWidthWithoutEscSequences(str)
|
||||
@@ -185,10 +237,11 @@ func Snip(str string, length int, snipIndicator string) string {
|
||||
|
||||
// Trim trims a string to the given length while ignoring escape sequences. For
|
||||
// ex.:
|
||||
// Trim("Ghost", 3) == "Gho"
|
||||
// Trim("Ghost", 6) == "Ghost"
|
||||
// Trim("\x1b[33mGhost\x1b[0m", 3) == "\x1b[33mGho\x1b[0m"
|
||||
// Trim("\x1b[33mGhost\x1b[0m", 6) == "\x1b[33mGhost\x1b[0m"
|
||||
//
|
||||
// Trim("Ghost", 3) == "Gho"
|
||||
// Trim("Ghost", 6) == "Ghost"
|
||||
// Trim("\x1b[33mGhost\x1b[0m", 3) == "\x1b[33mGho\x1b[0m"
|
||||
// Trim("\x1b[33mGhost\x1b[0m", 6) == "\x1b[33mGhost\x1b[0m"
|
||||
func Trim(str string, maxLen int) string {
|
||||
if maxLen <= 0 {
|
||||
return ""
|
||||
|
||||
+7
-7
@@ -37,9 +37,9 @@ var (
|
||||
type Transformer func(val interface{}) string
|
||||
|
||||
// NewNumberTransformer returns a number Transformer that:
|
||||
// * transforms the number as directed by 'format' (ex.: %.2f)
|
||||
// * colors negative values Red
|
||||
// * colors positive values Green
|
||||
// - transforms the number as directed by 'format' (ex.: %.2f)
|
||||
// - colors negative values Red
|
||||
// - colors positive values Green
|
||||
func NewNumberTransformer(format string) Transformer {
|
||||
return func(val interface{}) string {
|
||||
if valStr := transformInt(format, val); valStr != "" {
|
||||
@@ -79,7 +79,7 @@ func transformInt(format string, val interface{}) string {
|
||||
return transform(int64(number))
|
||||
}
|
||||
if number, ok := val.(int64); ok {
|
||||
return transform(int64(number))
|
||||
return transform(number)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func transformUint(format string, val interface{}) string {
|
||||
return transform(uint64(number))
|
||||
}
|
||||
if number, ok := val.(uint64); ok {
|
||||
return transform(uint64(number))
|
||||
return transform(number)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func transformFloat(format string, val interface{}) string {
|
||||
return transform(float64(number))
|
||||
}
|
||||
if number, ok := val.(float64); ok {
|
||||
return transform(float64(number))
|
||||
return transform(number)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func NewJSONTransformer(prefix string, indent string) Transformer {
|
||||
if valStr, ok := val.(string); ok {
|
||||
var b bytes.Buffer
|
||||
if err := json.Indent(&b, []byte(strings.TrimSpace(valStr)), prefix, indent); err == nil {
|
||||
return string(b.Bytes())
|
||||
return b.String()
|
||||
}
|
||||
} else if b, err := json.MarshalIndent(val, prefix, indent); err == nil {
|
||||
return string(b)
|
||||
|
||||
+12
-12
@@ -14,12 +14,12 @@ const (
|
||||
)
|
||||
|
||||
// Apply aligns the lines vertically. For ex.:
|
||||
// * VAlignTop.Apply({"Game", "Of", "Thrones"}, 5)
|
||||
// returns {"Game", "Of", "Thrones", "", ""}
|
||||
// * VAlignMiddle.Apply({"Game", "Of", "Thrones"}, 5)
|
||||
// returns {"", "Game", "Of", "Thrones", ""}
|
||||
// * VAlignBottom.Apply({"Game", "Of", "Thrones"}, 5)
|
||||
// returns {"", "", "Game", "Of", "Thrones"}
|
||||
// - VAlignTop.Apply({"Game", "Of", "Thrones"}, 5)
|
||||
// returns {"Game", "Of", "Thrones", "", ""}
|
||||
// - VAlignMiddle.Apply({"Game", "Of", "Thrones"}, 5)
|
||||
// returns {"", "Game", "Of", "Thrones", ""}
|
||||
// - VAlignBottom.Apply({"Game", "Of", "Thrones"}, 5)
|
||||
// returns {"", "", "Game", "Of", "Thrones"}
|
||||
func (va VAlign) Apply(lines []string, maxLines int) []string {
|
||||
if len(lines) == maxLines {
|
||||
return lines
|
||||
@@ -42,12 +42,12 @@ func (va VAlign) Apply(lines []string, maxLines int) []string {
|
||||
}
|
||||
|
||||
// ApplyStr aligns the string (of 1 or more lines) vertically. For ex.:
|
||||
// * VAlignTop.ApplyStr("Game\nOf\nThrones", 5)
|
||||
// returns {"Game", "Of", "Thrones", "", ""}
|
||||
// * VAlignMiddle.ApplyStr("Game\nOf\nThrones", 5)
|
||||
// returns {"", "Game", "Of", "Thrones", ""}
|
||||
// * VAlignBottom.ApplyStr("Game\nOf\nThrones", 5)
|
||||
// returns {"", "", "Game", "Of", "Thrones"}
|
||||
// - VAlignTop.ApplyStr("Game\nOf\nThrones", 5)
|
||||
// returns {"Game", "Of", "Thrones", "", ""}
|
||||
// - VAlignMiddle.ApplyStr("Game\nOf\nThrones", 5)
|
||||
// returns {"", "Game", "Of", "Thrones", ""}
|
||||
// - VAlignBottom.ApplyStr("Game\nOf\nThrones", 5)
|
||||
// returns {"", "", "Game", "Of", "Thrones"}
|
||||
func (va VAlign) ApplyStr(text string, maxLines int) []string {
|
||||
return va.Apply(strings.Split(text, "\n"), maxLines)
|
||||
}
|
||||
|
||||
Vendored
+3
-3
@@ -424,8 +424,8 @@ github.com/jackc/puddle/v2/internal/genstack
|
||||
## explicit
|
||||
github.com/jcmturner/gofork/encoding/asn1
|
||||
github.com/jcmturner/gofork/x/crypto/pbkdf2
|
||||
# github.com/jedib0t/go-pretty/v6 v6.4.6
|
||||
## explicit; go 1.16
|
||||
# github.com/jedib0t/go-pretty/v6 v6.5.8
|
||||
## explicit; go 1.17
|
||||
github.com/jedib0t/go-pretty/v6/table
|
||||
github.com/jedib0t/go-pretty/v6/text
|
||||
# github.com/jinzhu/inflection v1.0.0
|
||||
@@ -490,7 +490,7 @@ github.com/mattn/go-colorable
|
||||
# github.com/mattn/go-isatty v0.0.20
|
||||
## explicit; go 1.15
|
||||
github.com/mattn/go-isatty
|
||||
# github.com/mattn/go-runewidth v0.0.14
|
||||
# github.com/mattn/go-runewidth v0.0.15
|
||||
## explicit; go 1.9
|
||||
github.com/mattn/go-runewidth
|
||||
# github.com/mattn/go-sqlite3 v1.14.18
|
||||
|
||||
Reference in new issue
Block a user