Compare commits

..

1 Commits

Author SHA1 Message Date
Viktor Scharf
e53a4f6379 delete commit id from release version 2025-07-03 11:42:03 +02:00
151 changed files with 574 additions and 1323 deletions

View File

@@ -29,6 +29,8 @@ ifeq ($(VERSION), daily)
STRING ?= $(shell git rev-parse --short HEAD)
else ifeq ($(VERSION),)
STRING ?= $(shell git rev-parse --short HEAD)
else
STRING :=
endif

View File

@@ -1539,10 +1539,10 @@ def dockerReleases(ctx):
return pipelines
def dockerRelease(ctx, repo, build_type):
build_args = {
"REVISION": "%s" % ctx.build.commit,
"VERSION": "%s" % (ctx.build.ref.replace("refs/tags/", "") if ctx.build.event == "tag" else "daily"),
}
build_args = [
"REVISION=%s" % ctx.build.commit,
"VERSION=%s" % (ctx.build.ref.replace("refs/tags/", "") if ctx.build.event == "tag" else "daily"),
]
depends_on = getPipelineNames(getGoBinForTesting(ctx))

View File

@@ -65,6 +65,7 @@ OC_MODULES = \
services/webdav\
services/webfinger\
opencloud \
pkg \
protogen
# bin file definitions

View File

@@ -58,15 +58,14 @@ mkdir ${sandbox} && cd ${sandbox}
# The operating system
os="linux"
if [[ "$OSTYPE" == 'darwin'* ]]; then
if [[ $OSTYPE == 'darwin'* ]]; then
os="darwin"
fi
# The platform
dlarch="amd64"
if [[ ( "$(uname -s)" == "Darwin" && "$(uname -m)" == "arm64" ) ||
( "$(uname -s)" == "Linux" && "$(uname -m)" == "aarch64" ) ]]; then
if [[ $(uname -s) == "Darwin" && $(uname -m) == "arm64" ]]; then
dlarch="arm64"
fi

View File

@@ -2166,23 +2166,6 @@
]
}
},
{
"id": "96bc2621-a714-4f15-ac1d-bc32df94382d",
"name": "display name",
"providerId": "full-name-ldap-mapper",
"subComponents": {},
"config": {
"read.only": [
"false"
],
"write.only": [
"true"
],
"ldap.full.name.attribute": [
"displayName"
]
}
},
{
"id": "cab8b569-0f50-4e13-b2a5-d24ee513cd8b",
"name": "first name",

8
go.mod
View File

@@ -14,7 +14,7 @@ require (
github.com/blevesearch/bleve/v2 v2.5.2
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.14.1
github.com/cs3org/go-cs3apis v0.0.0-20250703154118-810365dec814
github.com/cs3org/go-cs3apis v0.0.0-20250218144737-544dd3919658
github.com/davidbyttow/govips/v2 v2.16.0
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
@@ -58,13 +58,13 @@ require (
github.com/nats-io/nats-server/v2 v2.11.6
github.com/nats-io/nats.go v1.43.0
github.com/oklog/run v1.2.0
github.com/olekukonko/tablewriter v1.0.8
github.com/olekukonko/tablewriter v1.0.7
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.23.4
github.com/onsi/gomega v1.37.0
github.com/open-policy-agent/opa v1.6.0
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250707143759-32eaae12b2ce
github.com/opencloud-eu/reva/v2 v2.34.1-0.20250704134423-74abc5f04717
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450
github.com/opencloud-eu/reva/v2 v2.34.0
github.com/orcaman/concurrent-map v1.0.0
github.com/pkg/errors v0.9.1
github.com/pkg/xattr v0.4.12

16
go.sum
View File

@@ -244,8 +244,8 @@ github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo
github.com/crewjam/httperr v0.2.0/go.mod h1:Jlz+Sg/XqBQhyMjdDiC+GNNRzZTD7x39Gu3pglZ5oH4=
github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
github.com/cs3org/go-cs3apis v0.0.0-20250703154118-810365dec814 h1:bo0vg45RDYHOJn33XhfRB830gqrlQJoCQjqUkR2fiAk=
github.com/cs3org/go-cs3apis v0.0.0-20250703154118-810365dec814/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ=
github.com/cs3org/go-cs3apis v0.0.0-20250218144737-544dd3919658 h1:CmH7twDuNUrHQXChZMafWjsEp1V47KutJlOAt6FjzGA=
github.com/cs3org/go-cs3apis v0.0.0-20250218144737-544dd3919658/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
@@ -848,8 +848,8 @@ github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5
github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc=
github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ=
github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@@ -866,10 +866,10 @@ github.com/open-policy-agent/opa v1.6.0 h1:/S/cnNQJ2MUMNzizHPbisTWBHowmLkPrugY5j
github.com/open-policy-agent/opa v1.6.0/go.mod h1:zFmw4P+W62+CWGYRDDswfVYSCnPo6oYaktQnfIaRFC4=
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a h1:Sakl76blJAaM6NxylVkgSzktjo2dS504iDotEFJsh3M=
github.com/opencloud-eu/go-micro-plugins/v4/store/nats-js-kv v0.0.0-20250512152754-23325793059a/go.mod h1:pjcozWijkNPbEtX5SIQaxEW/h8VAVZYTLx+70bmB3LY=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250707143759-32eaae12b2ce h1:tjbIYsW5CFsEbCf5B/KN0Mo1oKU/K+oipgFm2B6wzG4=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250707143759-32eaae12b2ce/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
github.com/opencloud-eu/reva/v2 v2.34.1-0.20250704134423-74abc5f04717 h1:khqL0AenfN0vt6oXgvbqH4UIuJk+2+oxWSJKcal4GYQ=
github.com/opencloud-eu/reva/v2 v2.34.1-0.20250704134423-74abc5f04717/go.mod h1:hSIUWU8JUaX+y0cVjbh6VaW6Mh0uJ/azFPx5cSVhQfc=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450 h1:QWn9G2f1R/EbyZSbkjtd9jqNq9X0NIphmmD4KYLNZtA=
github.com/opencloud-eu/libre-graph-api-go v1.0.8-0.20250603072916-fa601fb14450/go.mod h1:pzatilMEHZFT3qV7C/X3MqOa3NlRQuYhlRhZTL+hN6Q=
github.com/opencloud-eu/reva/v2 v2.34.0 h1:9oiMWj3wAaooddgq2hskD6e+JBbdqzQNVublPMkhKDs=
github.com/opencloud-eu/reva/v2 v2.34.0/go.mod h1:hfXmgujx6teOLoh5dbDINaAgHYwXZZr9qYczxU/h+do=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=

View File

@@ -4,14 +4,10 @@ import (
"context"
"fmt"
"net"
"time"
"github.com/oklog/run"
"github.com/urfave/cli/v2"
microstore "go-micro.dev/v4/store"
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
"github.com/opencloud-eu/opencloud/pkg/registry"
registry "github.com/opencloud-eu/opencloud/pkg/registry"
"github.com/opencloud-eu/opencloud/pkg/tracing"
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/config"
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/config/parser"
@@ -23,6 +19,8 @@ import (
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/server/http"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
"github.com/opencloud-eu/reva/v2/pkg/store"
"github.com/urfave/cli/v2"
microstore "go-micro.dev/v4/store"
)
// Server is the entrypoint for the server command.
@@ -70,15 +68,9 @@ func Server(cfg *config.Config) *cli.Command {
return err
}
ticker := time.NewTicker(cfg.CS3Api.APPRegistrationInterval)
defer ticker.Stop()
go func() {
for ; true; <-ticker.C {
if err := helpers.RegisterAppProvider(ctx, cfg, logger, gatewaySelector, appUrls); err != nil {
logger.Warn().Err(err).Msg("Failed to register app provider")
}
}
}()
if err := helpers.RegisterAppProvider(ctx, cfg, logger, gatewaySelector, appUrls); err != nil {
return err
}
st := store.Create(
store.Store(cfg.Store.Store),

View File

@@ -1,17 +1,12 @@
package config
import (
"time"
"github.com/opencloud-eu/opencloud/pkg/shared"
)
import "github.com/opencloud-eu/opencloud/pkg/shared"
// CS3Api defines the available configuration in order to access to the CS3 gateway.
type CS3Api struct {
Gateway Gateway `yaml:"gateway"`
DataGateway DataGateway `yaml:"datagateway"`
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
APPRegistrationInterval time.Duration `yaml:"app_registration_interval" env:"COLLABORATION_CS3API_APP_REGISTRATION_INTERVAL" desc:"The interval at which the app provider registers itself." introductionVersion:"%%NEXT%%"`
Gateway Gateway `yaml:"gateway"`
DataGateway DataGateway `yaml:"datagateway"`
GRPCClientTLS *shared.GRPCClientTLS `yaml:"grpc_client_tls"`
}
// Gateway defines the available configuration for the CS3 API gateway

View File

@@ -65,7 +65,6 @@ func DefaultConfig() *config.Config {
DataGateway: config.DataGateway{
Insecure: false,
},
APPRegistrationInterval: 30 * time.Second,
},
}
}

View File

@@ -251,7 +251,7 @@ func (g Graph) getDrives(r *http.Request, unrestricted bool, apiVersion APIVersi
return nil, errorcode.New(errorcode.GeneralException, res.Status.Message)
}
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion, expandPermissions, getFieldMask(odataReq))
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, apiVersion, expandPermissions)
if err != nil {
log.Debug().Err(err).Msg("could not get drives: error parsing grpc response")
return nil, errorcode.New(errorcode.GeneralException, err.Error())
@@ -289,7 +289,7 @@ func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
log = log.With().Str("url", webDavBaseURL.String()).Logger()
odataReq, expandPermissions, err := parseDriveRequest(r)
_, expandPermissions, err := parseDriveRequest(r)
if err != nil {
log.Debug().Err(err).Msg("could not get drives: error parsing odata request")
errorcode.RenderError(w, r, err)
@@ -322,7 +322,7 @@ func (g Graph) GetSingleDrive(w http.ResponseWriter, r *http.Request) {
return
}
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions, getFieldMask(odataReq))
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions)
if err != nil {
log.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -480,7 +480,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
}
}
spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1, false, nil)
spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1, false)
if err != nil {
log.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -648,7 +648,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
}
}
spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1, false, nil)
spaces, err := g.formatDrives(r.Context(), webDavBaseURL, []*storageprovider.StorageSpace{resp.StorageSpace}, APIVersion_1, false)
if err != nil {
log.Debug().Err(err).Msg("could not update drive: error parsing grpc response")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -659,7 +659,7 @@ func (g Graph) UpdateDrive(w http.ResponseWriter, r *http.Request) {
render.JSON(w, r, spaces[0])
}
func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool, fieldMask map[string]struct{}) ([]*libregraph.Drive, error) {
func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces []*storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool) ([]*libregraph.Drive, error) {
errg, ctx := errgroup.WithContext(ctx)
work := make(chan *storageprovider.StorageSpace, len(storageSpaces))
results := make(chan *libregraph.Drive, len(storageSpaces))
@@ -689,7 +689,7 @@ func (g Graph) formatDrives(ctx context.Context, baseURL *url.URL, storageSpaces
// skip OCM shares they are no supposed to show up in the drives list
continue
}
res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion, expandPermissions, fieldMask)
res, err := g.cs3StorageSpaceToDrive(ctx, baseURL, storageSpace, apiVersion, expandPermissions)
if err != nil {
return err
}
@@ -773,7 +773,7 @@ func (g Graph) ListStorageSpacesWithFilters(ctx context.Context, filters []*stor
return res, err
}
func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool, fieldMask map[string]struct{}) (*libregraph.Drive, error) {
func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, space *storageprovider.StorageSpace, apiVersion APIVersion, expandPermissions bool) (*libregraph.Drive, error) {
logger := g.logger.SubloggerWithRequestID(ctx)
if space.Root == nil {
logger.Error().Msg("unable to parse space: space has no root")
@@ -790,7 +790,7 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
Name: space.Name,
//"createdDateTime": "string (timestamp)", // TODO read from StorageSpace ... needs Opaque for now
DriveType: &space.SpaceType,
// we currently always expand the root because it carries the deleted property that indicates if a space is trashed
// we currently always expandt the root because it carries the deleted property that indiccates if a space is trashed
Root: &libregraph.DriveItem{
Id: libregraph.PtrString(storagespace.FormatResourceID(spaceRid)),
},
@@ -799,10 +799,6 @@ func (g Graph) cs3StorageSpaceToDrive(ctx context.Context, baseURL *url.URL, spa
drive.Root.Permissions, _ = g.cs3SpacePermissionsToLibreGraph(ctx, space, false, apiVersion)
}
if _, ok := fieldMask["@libre.graph.hasTrashedItems"]; ok {
drive.LibreGraphHasTrashedItems = &space.HasTrashedItems
}
if space.SpaceType == _spaceTypeMountpoint {
var remoteItem *libregraph.RemoteItem
grantID := storageprovider.ResourceId{
@@ -1179,15 +1175,3 @@ func validateSpaceName(name string) error {
return nil
}
func getFieldMask(odataReq *godata.GoDataRequest) map[string]struct{} {
fieldMask := map[string]struct{}{}
if odataReq != nil && odataReq.Query.Select != nil && len(odataReq.Query.Select.SelectItems) > 0 {
for _, item := range odataReq.Query.Select.SelectItems {
for _, token := range item.Segments {
fieldMask[token.Value] = struct{}{}
}
}
}
return fieldMask
}

View File

@@ -20,7 +20,6 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
"github.com/google/uuid"
libregraph "github.com/opencloud-eu/libre-graph-api-go"
settingsmsg "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/messages/settings/v0"
settingssvc "github.com/opencloud-eu/opencloud/protogen/gen/opencloud/services/settings/v0"
"github.com/opencloud-eu/opencloud/services/graph/pkg/errorcode"
@@ -32,6 +31,7 @@ import (
"github.com/opencloud-eu/reva/v2/pkg/events"
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/status"
"github.com/opencloud-eu/reva/v2/pkg/utils"
libregraph "github.com/opencloud-eu/libre-graph-api-go"
)
// GetMe implements the Service interface.
@@ -183,7 +183,7 @@ func (g Graph) GetUserDrive(w http.ResponseWriter, r *http.Request) {
return
}
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions, nil)
spaces, err := g.formatDrives(ctx, webDavBaseURL, res.StorageSpaces, APIVersion_1, expandPermissions)
if err != nil {
log.Debug().Err(err).Msg("could not get personal drive: error parsing grpc response")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
@@ -546,7 +546,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
} else {
expandPermissions = expandDrivesPermissions
}
d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1, expandPermissions, nil)
d, err := g.cs3StorageSpaceToDrive(r.Context(), wdu, sp, APIVersion_1, expandPermissions)
if err != nil {
logger.Debug().Err(err).Interface("id", sp.Id).Msg("error converting space to drive")
continue
@@ -577,7 +577,7 @@ func (g Graph) GetUser(w http.ResponseWriter, r *http.Request) {
return
}
}
ctxHasFullPerms := g.contextUserHasFullAccountPerms(r.Context())
if !ctxHasFullPerms && !g.config.API.ShowUserEmailInResults {
user.Mail = nil

View File

@@ -155,7 +155,7 @@ type DecomposedS3Driver struct {
DisableContentSha256 bool `yaml:"put_object_disable_content_sha254" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_DISABLE_CONTENT_SHA256" desc:"Disable sending content sha256 when copying objects to S3." introductionVersion:"1.0.0"`
DisableMultipart bool `yaml:"put_object_disable_multipart" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_DISABLE_MULTIPART" desc:"Disable multipart uploads when copying objects to S3" introductionVersion:"1.0.0"`
SendContentMd5 bool `yaml:"put_object_send_content_md5" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_SEND_CONTENT_MD5" desc:"Send a Content-MD5 header when copying objects to S3." introductionVersion:"1.0.0"`
ConcurrentStreamParts bool `yaml:"put_object_concurrent_stream_parts" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_CONCURRENT_STREAM_PARTS" desc:"Always precreate parts when copying objects to S3. This is not recommended. It uses a memory buffer. If true, PartSize needs to be set." introductionVersion:"1.0.0"`
ConcurrentStreamParts bool `yaml:"put_object_concurrent_stream_parts" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_CONCURRENT_STREAM_PARTS" desc:"Always precreate parts when copying objects to S3." introductionVersion:"1.0.0"`
NumThreads uint `yaml:"put_object_num_threads" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_NUM_THREADS" desc:"Number of concurrent uploads to use when copying objects to S3." introductionVersion:"1.0.0"`
PartSize uint64 `yaml:"put_object_part_size" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_PART_SIZE" desc:"Part size for concurrent uploads to S3. If no value or 0 is set, the library's default value of 16MB is used. The value range is min 5MB and max 5GB." introductionVersion:"1.0.0"`
// PersonalSpaceAliasTemplate contains the template used to construct

View File

@@ -112,7 +112,7 @@ func DefaultConfig() *config.Config {
UserLayout: "{{.Id.OpaqueId}}",
Region: "default",
SendContentMd5: true,
ConcurrentStreamParts: false,
ConcurrentStreamParts: true,
NumThreads: 4,
PersonalSpaceAliasTemplate: "{{.SpaceType}}/{{.User.Username | lower}}",
PersonalSpacePathTemplate: "",
@@ -123,7 +123,6 @@ func DefaultConfig() *config.Config {
MaxConcurrency: 5,
LockCycleDurationFactor: 30,
DisableMultipart: true,
PartSize: 16777216,
AsyncUploads: true,
},
Decomposed: config.DecomposedDriver{

View File

@@ -1,4 +1,4 @@
// Copyright 2018-2025 CERN
// Copyright 2018-2019 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -1189,7 +1189,6 @@ type UpdateShareRequest_UpdateField struct {
//
// *UpdateShareRequest_UpdateField_Permissions
// *UpdateShareRequest_UpdateField_DisplayName
// *UpdateShareRequest_UpdateField_Expiration
Field isUpdateShareRequest_UpdateField_Field `protobuf_oneof:"field"`
}
@@ -1246,13 +1245,6 @@ func (x *UpdateShareRequest_UpdateField) GetDisplayName() string {
return ""
}
func (x *UpdateShareRequest_UpdateField) GetExpiration() *v1beta1.Timestamp {
if x, ok := x.GetField().(*UpdateShareRequest_UpdateField_Expiration); ok {
return x.Expiration
}
return nil
}
type isUpdateShareRequest_UpdateField_Field interface {
isUpdateShareRequest_UpdateField_Field()
}
@@ -1267,17 +1259,10 @@ type UpdateShareRequest_UpdateField_DisplayName struct {
DisplayName string `protobuf:"bytes,3,opt,name=display_name,json=displayName,proto3,oneof"`
}
type UpdateShareRequest_UpdateField_Expiration struct {
// Update the expiration time.
Expiration *v1beta1.Timestamp `protobuf:"bytes,4,opt,name=expiration,proto3,oneof"`
}
func (*UpdateShareRequest_UpdateField_Permissions) isUpdateShareRequest_UpdateField_Field() {}
func (*UpdateShareRequest_UpdateField_DisplayName) isUpdateShareRequest_UpdateField_Field() {}
func (*UpdateShareRequest_UpdateField_Expiration) isUpdateShareRequest_UpdateField_Field() {}
var File_cs3_sharing_collaboration_v1beta1_collaboration_api_proto protoreflect.FileDescriptor
var file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_rawDesc = []byte{
@@ -1324,7 +1309,7 @@ var file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_rawDesc = []b
0x61, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x63, 0x73, 0x33, 0x2e,
0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x53, 0x68,
0x61, 0x72, 0x65, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x22, 0xb9, 0x04, 0x0a, 0x12, 0x55,
0x61, 0x72, 0x65, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x22, 0xf9, 0x03, 0x0a, 0x12, 0x55,
0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x31, 0x0a, 0x06, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x19, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31,
@@ -1347,7 +1332,7 @@ var file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_rawDesc = []b
0x2e, 0x63, 0x73, 0x33, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6c,
0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74,
0x61, 0x31, 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x05, 0x73, 0x68, 0x61, 0x72, 0x65, 0x1a,
0xd4, 0x01, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12,
0x94, 0x01, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x12,
0x57, 0x0a, 0x0b, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x69,
0x6e, 0x67, 0x2e, 0x63, 0x6f, 0x6c, 0x6c, 0x61, 0x62, 0x6f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
@@ -1355,11 +1340,7 @@ var file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_rawDesc = []b
0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x48, 0x00, 0x52, 0x0b, 0x70, 0x65, 0x72,
0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x23, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70,
0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a,
0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
0x0b, 0x32, 0x1c, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31,
0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x48,
0x00, 0x52, 0x0a, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x07, 0x0a,
0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x42, 0x07, 0x0a,
0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x22, 0xb9, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74,
0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f,
0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17,
@@ -1644,7 +1625,6 @@ var file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_goTypes = []i
(*Filter)(nil), // 24: cs3.sharing.collaboration.v1beta1.Filter
(*ReceivedShare)(nil), // 25: cs3.sharing.collaboration.v1beta1.ReceivedShare
(*SharePermissions)(nil), // 26: cs3.sharing.collaboration.v1beta1.SharePermissions
(*v1beta1.Timestamp)(nil), // 27: cs3.types.v1beta1.Timestamp
}
var file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_depIdxs = []int32{
17, // 0: cs3.sharing.collaboration.v1beta1.CreateShareRequest.opaque:type_name -> cs3.types.v1beta1.Opaque
@@ -1692,28 +1672,27 @@ var file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_depIdxs = []i
17, // 42: cs3.sharing.collaboration.v1beta1.GetReceivedShareResponse.opaque:type_name -> cs3.types.v1beta1.Opaque
25, // 43: cs3.sharing.collaboration.v1beta1.GetReceivedShareResponse.share:type_name -> cs3.sharing.collaboration.v1beta1.ReceivedShare
26, // 44: cs3.sharing.collaboration.v1beta1.UpdateShareRequest.UpdateField.permissions:type_name -> cs3.sharing.collaboration.v1beta1.SharePermissions
27, // 45: cs3.sharing.collaboration.v1beta1.UpdateShareRequest.UpdateField.expiration:type_name -> cs3.types.v1beta1.Timestamp
0, // 46: cs3.sharing.collaboration.v1beta1.CollaborationAPI.CreateShare:input_type -> cs3.sharing.collaboration.v1beta1.CreateShareRequest
6, // 47: cs3.sharing.collaboration.v1beta1.CollaborationAPI.RemoveShare:input_type -> cs3.sharing.collaboration.v1beta1.RemoveShareRequest
8, // 48: cs3.sharing.collaboration.v1beta1.CollaborationAPI.GetShare:input_type -> cs3.sharing.collaboration.v1beta1.GetShareRequest
4, // 49: cs3.sharing.collaboration.v1beta1.CollaborationAPI.ListShares:input_type -> cs3.sharing.collaboration.v1beta1.ListSharesRequest
2, // 50: cs3.sharing.collaboration.v1beta1.CollaborationAPI.UpdateShare:input_type -> cs3.sharing.collaboration.v1beta1.UpdateShareRequest
10, // 51: cs3.sharing.collaboration.v1beta1.CollaborationAPI.ListReceivedShares:input_type -> cs3.sharing.collaboration.v1beta1.ListReceivedSharesRequest
12, // 52: cs3.sharing.collaboration.v1beta1.CollaborationAPI.UpdateReceivedShare:input_type -> cs3.sharing.collaboration.v1beta1.UpdateReceivedShareRequest
14, // 53: cs3.sharing.collaboration.v1beta1.CollaborationAPI.GetReceivedShare:input_type -> cs3.sharing.collaboration.v1beta1.GetReceivedShareRequest
1, // 54: cs3.sharing.collaboration.v1beta1.CollaborationAPI.CreateShare:output_type -> cs3.sharing.collaboration.v1beta1.CreateShareResponse
7, // 55: cs3.sharing.collaboration.v1beta1.CollaborationAPI.RemoveShare:output_type -> cs3.sharing.collaboration.v1beta1.RemoveShareResponse
9, // 56: cs3.sharing.collaboration.v1beta1.CollaborationAPI.GetShare:output_type -> cs3.sharing.collaboration.v1beta1.GetShareResponse
5, // 57: cs3.sharing.collaboration.v1beta1.CollaborationAPI.ListShares:output_type -> cs3.sharing.collaboration.v1beta1.ListSharesResponse
3, // 58: cs3.sharing.collaboration.v1beta1.CollaborationAPI.UpdateShare:output_type -> cs3.sharing.collaboration.v1beta1.UpdateShareResponse
11, // 59: cs3.sharing.collaboration.v1beta1.CollaborationAPI.ListReceivedShares:output_type -> cs3.sharing.collaboration.v1beta1.ListReceivedSharesResponse
13, // 60: cs3.sharing.collaboration.v1beta1.CollaborationAPI.UpdateReceivedShare:output_type -> cs3.sharing.collaboration.v1beta1.UpdateReceivedShareResponse
15, // 61: cs3.sharing.collaboration.v1beta1.CollaborationAPI.GetReceivedShare:output_type -> cs3.sharing.collaboration.v1beta1.GetReceivedShareResponse
54, // [54:62] is the sub-list for method output_type
46, // [46:54] is the sub-list for method input_type
46, // [46:46] is the sub-list for extension type_name
46, // [46:46] is the sub-list for extension extendee
0, // [0:46] is the sub-list for field type_name
0, // 45: cs3.sharing.collaboration.v1beta1.CollaborationAPI.CreateShare:input_type -> cs3.sharing.collaboration.v1beta1.CreateShareRequest
6, // 46: cs3.sharing.collaboration.v1beta1.CollaborationAPI.RemoveShare:input_type -> cs3.sharing.collaboration.v1beta1.RemoveShareRequest
8, // 47: cs3.sharing.collaboration.v1beta1.CollaborationAPI.GetShare:input_type -> cs3.sharing.collaboration.v1beta1.GetShareRequest
4, // 48: cs3.sharing.collaboration.v1beta1.CollaborationAPI.ListShares:input_type -> cs3.sharing.collaboration.v1beta1.ListSharesRequest
2, // 49: cs3.sharing.collaboration.v1beta1.CollaborationAPI.UpdateShare:input_type -> cs3.sharing.collaboration.v1beta1.UpdateShareRequest
10, // 50: cs3.sharing.collaboration.v1beta1.CollaborationAPI.ListReceivedShares:input_type -> cs3.sharing.collaboration.v1beta1.ListReceivedSharesRequest
12, // 51: cs3.sharing.collaboration.v1beta1.CollaborationAPI.UpdateReceivedShare:input_type -> cs3.sharing.collaboration.v1beta1.UpdateReceivedShareRequest
14, // 52: cs3.sharing.collaboration.v1beta1.CollaborationAPI.GetReceivedShare:input_type -> cs3.sharing.collaboration.v1beta1.GetReceivedShareRequest
1, // 53: cs3.sharing.collaboration.v1beta1.CollaborationAPI.CreateShare:output_type -> cs3.sharing.collaboration.v1beta1.CreateShareResponse
7, // 54: cs3.sharing.collaboration.v1beta1.CollaborationAPI.RemoveShare:output_type -> cs3.sharing.collaboration.v1beta1.RemoveShareResponse
9, // 55: cs3.sharing.collaboration.v1beta1.CollaborationAPI.GetShare:output_type -> cs3.sharing.collaboration.v1beta1.GetShareResponse
5, // 56: cs3.sharing.collaboration.v1beta1.CollaborationAPI.ListShares:output_type -> cs3.sharing.collaboration.v1beta1.ListSharesResponse
3, // 57: cs3.sharing.collaboration.v1beta1.CollaborationAPI.UpdateShare:output_type -> cs3.sharing.collaboration.v1beta1.UpdateShareResponse
11, // 58: cs3.sharing.collaboration.v1beta1.CollaborationAPI.ListReceivedShares:output_type -> cs3.sharing.collaboration.v1beta1.ListReceivedSharesResponse
13, // 59: cs3.sharing.collaboration.v1beta1.CollaborationAPI.UpdateReceivedShare:output_type -> cs3.sharing.collaboration.v1beta1.UpdateReceivedShareResponse
15, // 60: cs3.sharing.collaboration.v1beta1.CollaborationAPI.GetReceivedShare:output_type -> cs3.sharing.collaboration.v1beta1.GetReceivedShareResponse
53, // [53:61] is the sub-list for method output_type
45, // [45:53] is the sub-list for method input_type
45, // [45:45] is the sub-list for extension type_name
45, // [45:45] is the sub-list for extension extendee
0, // [0:45] is the sub-list for field type_name
}
func init() { file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_init() }
@@ -1931,7 +1910,6 @@ func file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_init() {
file_cs3_sharing_collaboration_v1beta1_collaboration_api_proto_msgTypes[16].OneofWrappers = []interface{}{
(*UpdateShareRequest_UpdateField_Permissions)(nil),
(*UpdateShareRequest_UpdateField_DisplayName)(nil),
(*UpdateShareRequest_UpdateField_Expiration)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{

View File

@@ -1,4 +1,4 @@
// Copyright 2018-2025 CERN
// Copyright 2018-2019 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1819,9 +1819,6 @@ type StorageSpace struct {
// OPTIONAL.
// Resource info for the storage space root.
RootInfo *ResourceInfo `protobuf:"bytes,9,opt,name=root_info,json=rootInfo,proto3" json:"root_info,omitempty"`
// OPTIONAL.
// HasTrashedItems indicates if the storage space has trashed items.
HasTrashedItems bool `protobuf:"varint,10,opt,name=has_trashed_items,json=hasTrashedItems,proto3" json:"has_trashed_items,omitempty"`
}
func (x *StorageSpace) Reset() {
@@ -1919,13 +1916,6 @@ func (x *StorageSpace) GetRootInfo() *ResourceInfo {
return nil
}
func (x *StorageSpace) GetHasTrashedItems() bool {
if x != nil {
return x.HasTrashedItems
}
return false
}
// The id of a storage space.
type StorageSpaceId struct {
state protoimpl.MessageState
@@ -2347,7 +2337,7 @@ var file_cs3_storage_provider_v1beta1_resources_proto_rawDesc = []byte{
0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x64, 0x6f, 0x77, 0x6e, 0x6c,
0x6f, 0x61, 0x64, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x65,
0x78, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x65, 0x78, 0x70,
0x6f, 0x73, 0x65, 0x22, 0x8b, 0x04, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53,
0x6f, 0x73, 0x65, 0x22, 0xdf, 0x03, 0x0a, 0x0c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53,
0x70, 0x61, 0x63, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73,
0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x52,
@@ -2377,79 +2367,77 @@ var file_cs3_storage_provider_v1beta1_resources_proto_rawDesc = []byte{
0x2a, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72,
0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x72, 0x6f, 0x6f,
0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2a, 0x0a, 0x11, 0x68, 0x61, 0x73, 0x5f, 0x74, 0x72, 0x61,
0x73, 0x68, 0x65, 0x64, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08,
0x52, 0x0f, 0x68, 0x61, 0x73, 0x54, 0x72, 0x61, 0x73, 0x68, 0x65, 0x64, 0x49, 0x74, 0x65, 0x6d,
0x73, 0x22, 0x2d, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x70, 0x61, 0x63,
0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x5f, 0x69, 0x64,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x49, 0x64,
0x22, 0xdc, 0x01, 0x0a, 0x05, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x12, 0x31, 0x0a, 0x06, 0x6f, 0x70,
0x61, 0x71, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x73, 0x33,
0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2e, 0x4f,
0x70, 0x61, 0x71, 0x75, 0x65, 0x52, 0x06, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x12, 0x26, 0x0a,
0x0f, 0x71, 0x75, 0x6f, 0x74, 0x61, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73,
0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x74, 0x61, 0x4d, 0x61, 0x78,
0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x71, 0x75, 0x6f, 0x74, 0x61, 0x5f, 0x6d,
0x61, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d,
0x71, 0x75, 0x6f, 0x74, 0x61, 0x4d, 0x61, 0x78, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x27, 0x0a,
0x0f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73,
0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e,
0x67, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e,
0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52,
0x0e, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x2a,
0x60, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x4c,
0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44,
0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
0x53, 0x48, 0x41, 0x52, 0x45, 0x44, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x4c, 0x4f, 0x43, 0x4b,
0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x10, 0x02, 0x12, 0x12, 0x0a,
0x0e, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x58, 0x43, 0x4c, 0x10,
0x03, 0x2a, 0xb2, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x79,
0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x16, 0x0a,
0x12, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46,
0x49, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43,
0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x41, 0x49, 0x4e, 0x45, 0x52,
0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x46, 0x45, 0x52, 0x45, 0x4e, 0x43, 0x45, 0x10, 0x03, 0x12,
0x19, 0x0a, 0x15, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45,
0x5f, 0x53, 0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x10, 0x04, 0x12, 0x1a, 0x0a, 0x16, 0x52, 0x45,
0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x45,
0x52, 0x4e, 0x41, 0x4c, 0x10, 0x05, 0x2a, 0xc1, 0x01, 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75,
0x72, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12,
0x22, 0x0a, 0x1e, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43,
0x4b, 0x53, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49,
0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f,
0x43, 0x48, 0x45, 0x43, 0x4b, 0x53, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e,
0x53, 0x45, 0x54, 0x10, 0x01, 0x12, 0x22, 0x0a, 0x1e, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43,
0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x53, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
0x41, 0x44, 0x4c, 0x45, 0x52, 0x33, 0x32, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x45, 0x53,
0x74, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x2d, 0x0a, 0x0e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65,
0x53, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x70, 0x61, 0x71, 0x75,
0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x61, 0x71,
0x75, 0x65, 0x49, 0x64, 0x22, 0xdc, 0x01, 0x0a, 0x05, 0x51, 0x75, 0x6f, 0x74, 0x61, 0x12, 0x31,
0x0a, 0x06, 0x6f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19,
0x2e, 0x63, 0x73, 0x33, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74,
0x61, 0x31, 0x2e, 0x4f, 0x70, 0x61, 0x71, 0x75, 0x65, 0x52, 0x06, 0x6f, 0x70, 0x61, 0x71, 0x75,
0x65, 0x12, 0x26, 0x0a, 0x0f, 0x71, 0x75, 0x6f, 0x74, 0x61, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x62,
0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x74,
0x61, 0x4d, 0x61, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x71, 0x75, 0x6f,
0x74, 0x61, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01,
0x28, 0x04, 0x52, 0x0d, 0x71, 0x75, 0x6f, 0x74, 0x61, 0x4d, 0x61, 0x78, 0x46, 0x69, 0x6c, 0x65,
0x73, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x62,
0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x61,
0x69, 0x6e, 0x69, 0x6e, 0x67, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65,
0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20,
0x01, 0x28, 0x04, 0x52, 0x0e, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x46, 0x69,
0x6c, 0x65, 0x73, 0x2a, 0x60, 0x0a, 0x08, 0x4c, 0x6f, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12,
0x15, 0x0a, 0x11, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56,
0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x14, 0x0a, 0x10, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x5f, 0x53, 0x48, 0x41, 0x52, 0x45, 0x44, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f,
0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x10,
0x02, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x4f, 0x43, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45,
0x58, 0x43, 0x4c, 0x10, 0x03, 0x2a, 0xb2, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52,
0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10,
0x00, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59,
0x50, 0x45, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x53,
0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x41,
0x49, 0x4e, 0x45, 0x52, 0x10, 0x02, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52,
0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x46, 0x45, 0x52, 0x45, 0x4e, 0x43,
0x45, 0x10, 0x03, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f,
0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x59, 0x4d, 0x4c, 0x49, 0x4e, 0x4b, 0x10, 0x04, 0x12, 0x1a,
0x0a, 0x16, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f,
0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x05, 0x2a, 0xc1, 0x01, 0x0a, 0x14, 0x52,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x73, 0x75, 0x6d, 0x54,
0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, 0x1e, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f,
0x43, 0x48, 0x45, 0x43, 0x4b, 0x53, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e,
0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x45, 0x53, 0x4f, 0x55,
0x52, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x53, 0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50,
0x45, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x01, 0x12, 0x22, 0x0a, 0x1e, 0x52, 0x45, 0x53,
0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x53, 0x55, 0x4d, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0x03, 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x53,
0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x53, 0x55, 0x4d, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x10, 0x04, 0x2a, 0x56, 0x0a, 0x0b, 0x47, 0x72,
0x61, 0x6e, 0x74, 0x65, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x47, 0x52, 0x41,
0x4e, 0x54, 0x45, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49,
0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x47, 0x52, 0x41, 0x4e, 0x54, 0x45, 0x45, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x47, 0x52,
0x41, 0x4e, 0x54, 0x45, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50,
0x10, 0x02, 0x42, 0x90, 0x02, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x73, 0x33, 0x2e, 0x73,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e,
0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x49, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x73, 0x33, 0x6f, 0x72, 0x67, 0x2f, 0x67, 0x6f, 0x2d,
0x63, 0x73, 0x33, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x63, 0x73, 0x33, 0x2f, 0x73, 0x74, 0x6f, 0x72,
0x61, 0x67, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x62,
0x65, 0x74, 0x61, 0x31, 0x3b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x76, 0x31, 0x62,
0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x53, 0x50, 0xaa, 0x02, 0x1c, 0x43, 0x73, 0x33,
0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
0x72, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x1c, 0x43, 0x73, 0x33, 0x5c,
0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5c, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x28, 0x43, 0x73, 0x33, 0x5c, 0x53,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5c, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5c,
0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64,
0x61, 0x74, 0x61, 0xea, 0x02, 0x1f, 0x43, 0x73, 0x33, 0x3a, 0x3a, 0x53, 0x74, 0x6f, 0x72, 0x61,
0x67, 0x65, 0x3a, 0x3a, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x3a, 0x3a, 0x56, 0x31,
0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x59, 0x50, 0x45, 0x5f, 0x41, 0x44, 0x4c, 0x45, 0x52, 0x33, 0x32, 0x10, 0x02, 0x12, 0x1e, 0x0a,
0x1a, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x53,
0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x44, 0x35, 0x10, 0x03, 0x12, 0x1f, 0x0a,
0x1b, 0x52, 0x45, 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x5f, 0x43, 0x48, 0x45, 0x43, 0x4b, 0x53,
0x55, 0x4d, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x48, 0x41, 0x31, 0x10, 0x04, 0x2a, 0x56,
0x0a, 0x0b, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x65, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a,
0x14, 0x47, 0x52, 0x41, 0x4e, 0x54, 0x45, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e,
0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x15, 0x0a, 0x11, 0x47, 0x52, 0x41, 0x4e, 0x54,
0x45, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, 0x16,
0x0a, 0x12, 0x47, 0x52, 0x41, 0x4e, 0x54, 0x45, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47,
0x52, 0x4f, 0x55, 0x50, 0x10, 0x02, 0x42, 0x90, 0x02, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x2e, 0x63,
0x73, 0x33, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69,
0x64, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0e, 0x52, 0x65, 0x73,
0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x49, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x73, 0x33, 0x6f, 0x72, 0x67,
0x2f, 0x67, 0x6f, 0x2d, 0x63, 0x73, 0x33, 0x61, 0x70, 0x69, 0x73, 0x2f, 0x63, 0x73, 0x33, 0x2f,
0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65,
0x72, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x53, 0x50, 0xaa, 0x02,
0x1c, 0x43, 0x73, 0x33, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x50, 0x72, 0x6f,
0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x1c,
0x43, 0x73, 0x33, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5c, 0x50, 0x72, 0x6f, 0x76,
0x69, 0x64, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x28, 0x43,
0x73, 0x33, 0x5c, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5c, 0x50, 0x72, 0x6f, 0x76, 0x69,
0x64, 0x65, 0x72, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x1f, 0x43, 0x73, 0x33, 0x3a, 0x3a, 0x53,
0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x3a, 0x3a, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72,
0x3a, 0x3a, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (

View File

@@ -416,244 +416,6 @@ func main() {
</table>
```
#### Custom Invoice Renderer
```go
package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/olekukonko/ll"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
)
// InvoiceRenderer implements tw.Renderer for a basic invoice style.
type InvoiceRenderer struct {
writer io.Writer
logger *ll.Logger
rendition tw.Rendition
}
func NewInvoiceRenderer() *InvoiceRenderer {
rendition := tw.Rendition{
Borders: tw.BorderNone,
Symbols: tw.NewSymbols(tw.StyleNone),
Settings: tw.Settings{Separators: tw.SeparatorsNone, Lines: tw.LinesNone},
Streaming: false,
}
defaultLogger := ll.New("simple-invoice-renderer")
return &InvoiceRenderer{logger: defaultLogger, rendition: rendition}
}
func (r *InvoiceRenderer) Logger(logger *ll.Logger) {
if logger != nil {
r.logger = logger
}
}
func (r *InvoiceRenderer) Config() tw.Rendition {
return r.rendition
}
func (r *InvoiceRenderer) Start(w io.Writer) error {
r.writer = w
r.logger.Debug("InvoiceRenderer: Start")
return nil
}
func (r *InvoiceRenderer) formatLine(cells []string, widths tw.Mapper[int, int], cellContexts map[int]tw.CellContext) string {
var sb strings.Builder
numCols := 0
if widths != nil { // Ensure widths is not nil before calling Len
numCols = widths.Len()
}
for i := 0; i < numCols; i++ {
data := ""
if i < len(cells) {
data = cells[i]
}
width := 0
if widths != nil { // Check again before Get
width = widths.Get(i)
}
align := tw.AlignDefault
if cellContexts != nil { // Check cellContexts
if ctx, ok := cellContexts[i]; ok {
align = ctx.Align
}
}
paddedCell := tw.Pad(data, " ", width, align)
sb.WriteString(paddedCell)
if i < numCols-1 {
sb.WriteString(" ") // Column separator
}
}
return sb.String()
}
func (r *InvoiceRenderer) Header(headers [][]string, ctx tw.Formatting) {
if r.writer == nil {
return
}
r.logger.Debugf("InvoiceRenderer: Header (lines: %d)", len(headers))
for _, headerLineCells := range headers {
lineStr := r.formatLine(headerLineCells, ctx.Row.Widths, ctx.Row.Current)
fmt.Fprintln(r.writer, lineStr)
}
if len(headers) > 0 {
totalWidth := 0
if ctx.Row.Widths != nil {
ctx.Row.Widths.Each(func(_ int, w int) { totalWidth += w })
if ctx.Row.Widths.Len() > 1 {
totalWidth += (ctx.Row.Widths.Len() - 1) * 3 // Separator spaces
}
}
if totalWidth > 0 {
fmt.Fprintln(r.writer, strings.Repeat("-", totalWidth))
}
}
}
func (r *InvoiceRenderer) Row(row []string, ctx tw.Formatting) {
if r.writer == nil {
return
}
r.logger.Debug("InvoiceRenderer: Row")
lineStr := r.formatLine(row, ctx.Row.Widths, ctx.Row.Current)
fmt.Fprintln(r.writer, lineStr)
}
func (r *InvoiceRenderer) Footer(footers [][]string, ctx tw.Formatting) {
if r.writer == nil {
return
}
r.logger.Debugf("InvoiceRenderer: Footer (lines: %d)", len(footers))
if len(footers) > 0 {
totalWidth := 0
if ctx.Row.Widths != nil {
ctx.Row.Widths.Each(func(_ int, w int) { totalWidth += w })
if ctx.Row.Widths.Len() > 1 {
totalWidth += (ctx.Row.Widths.Len() - 1) * 3
}
}
if totalWidth > 0 {
fmt.Fprintln(r.writer, strings.Repeat("-", totalWidth))
}
}
for _, footerLineCells := range footers {
lineStr := r.formatLine(footerLineCells, ctx.Row.Widths, ctx.Row.Current)
fmt.Fprintln(r.writer, lineStr)
}
}
func (r *InvoiceRenderer) Line(ctx tw.Formatting) {
r.logger.Debug("InvoiceRenderer: Line (no-op)")
// This simple renderer draws its own lines in Header/Footer.
}
func (r *InvoiceRenderer) Close() error {
r.logger.Debug("InvoiceRenderer: Close")
r.writer = nil
return nil
}
func main() {
data := [][]string{
{"Product A", "2", "10.00", "20.00"},
{"Super Long Product Name B", "1", "125.50", "125.50"},
{"Item C", "10", "1.99", "19.90"},
}
header := []string{"Description", "Qty", "Unit Price", "Total Price"}
footer := []string{"", "", "Subtotal:\nTax (10%):\nGRAND TOTAL:", "165.40\n16.54\n181.94"}
invoiceRenderer := NewInvoiceRenderer()
// Create table and set custom renderer using Options
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(invoiceRenderer),
tablewriter.WithAlignment([]tw.Align{
tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignRight,
}),
)
table.Header(header)
for _, v := range data {
table.Append(v)
}
// Use the Footer method with strings containing newlines for multi-line cells
table.Footer(footer)
fmt.Println("Rendering with InvoiceRenderer:")
table.Render()
// For comparison, render with default Blueprint renderer
// Re-create the table or reset it to use a different renderer
table2 := tablewriter.NewTable(os.Stdout,
tablewriter.WithAlignment([]tw.Align{
tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignRight,
}),
)
table2.Header(header)
for _, v := range data {
table2.Append(v)
}
table2.Footer(footer)
fmt.Println("\nRendering with Default Blueprint Renderer (for comparison):")
table2.Render()
}
```
```
Rendering with InvoiceRenderer:
DESCRIPTION QTY UNIT PRICE TOTAL PRICE
--------------------------------------------------------------------
Product A 2 10.00 20.00
Super Long Product Name B 1 125.50 125.50
Item C 10 1.99 19.90
--------------------------------------------------------------------
Subtotal: 165.40
--------------------------------------------------------------------
Tax (10%): 16.54
--------------------------------------------------------------------
GRAND TOTAL: 181.94
```
```
Rendering with Default Blueprint Renderer (for comparison):
┌───────────────────────────┬─────┬──────────────┬─────────────┐
│ DESCRIPTION │ QTY │ UNIT PRICE │ TOTAL PRICE │
├───────────────────────────┼─────┼──────────────┼─────────────┤
│ Product A │ 2 │ 10.00 │ 20.00 │
│ Super Long Product Name B │ 1 │ 125.50 │ 125.50 │
│ Item C │ 10 │ 1.99 │ 19.90 │
├───────────────────────────┼─────┼──────────────┼─────────────┤
│ │ │ Subtotal: │ 165.40 │
│ │ │ Tax (10%): │ 16.54 │
│ │ │ GRAND TOTAL: │ 181.94 │
└───────────────────────────┴─────┴──────────────┴─────────────┘
```
**Notes**:
- The `renderer.NewBlueprint()` is sufficient for most text-based use cases.
- Custom renderers require implementing all interface methods to handle table structure correctly. `tw.Formatting` (which includes `tw.RowContext`) provides cell content and metadata.
@@ -2152,7 +1914,7 @@ func main() {
- **Direct ANSI Codes**: Embed codes (e.g., `\033[32m` for green) in strings for manual control (`zoo.go:convertCellsToStrings`).
- **tw.Formatter**: Implement `Format() string` on custom types to control cell output, including colors (`tw/types.go:Formatter`).
- **tw.CellFilter**: Use `Config.<Section>.Filter.Global` or `PerColumn` to apply transformations like coloring dynamically (`tw/cell.go:CellFilter`).
- **Width Handling**: `twdw.Width()` correctly calculates display width of ANSI-coded strings, ignoring escape sequences (`tw/fn.go:DisplayWidth`).
- **Width Handling**: `tw.DisplayWidth()` correctly calculates display width of ANSI-coded strings, ignoring escape sequences (`tw/fn.go:DisplayWidth`).
- **No Built-In Color Presets**: Unlike v0.0.5s potential `tablewriter.Colors`, v1.0.x requires manual ANSI code management or external libraries for color constants.
**Migration Tips**:
@@ -2166,7 +1928,7 @@ func main() {
**Potential Pitfalls**:
- **Terminal Support**: Some terminals may not support ANSI codes, causing artifacts; test in your environment or provide a non-colored fallback.
- **Filter Overlap**: Combining `tw.CellFilter` with `AutoFormat` or other transformations can lead to unexpected results; prioritize filters for coloring (`zoo.go`).
- **Width Miscalculation**: Incorrect ANSI code handling (e.g., missing `Reset`) can skew width calculations; use `twdw.Width` (`tw/fn.go`).
- **Width Miscalculation**: Incorrect ANSI code handling (e.g., missing `Reset`) can skew width calculations; use `tw.DisplayWidth` (`tw/fn.go`).
- **Streaming Consistency**: In streaming mode, ensure color codes are applied consistently across rows to avoid visual discrepancies (`stream.go`).
- **Performance**: Applying filters to large datasets may add overhead; optimize filter logic for efficiency (`zoo.go`).
@@ -3056,7 +2818,7 @@ func main() {
**Notes**:
- **Configuration**: Uses `tw.CellFilter` for per-column coloring, embedding ANSI codes (`tw/cell.go`).
- **Migration from v0.0.5**: Replaces `SetColumnColor` with dynamic filters (`tablewriter.go`).
- **Key Features**: Flexible color application; `twdw.Width` handles ANSI codes correctly (`tw/fn.go`).
- **Key Features**: Flexible color application; `tw.DisplayWidth` handles ANSI codes correctly (`tw/fn.go`).
- **Best Practices**: Test in ANSI-compatible terminals; use constants for code clarity.
- **Potential Issues**: Non-ANSI terminals may show artifacts; provide fallbacks (`tw/fn.go`).
@@ -3243,7 +3005,7 @@ This section addresses common migration issues with detailed solutions, covering
| Merging not working | **Cause**: Streaming mode or mismatched data. **Solution**: Use batch mode for vertical/hierarchical merging; ensure identical content (`zoo.go`). |
| Alignment ignored | **Cause**: `PerColumn` overrides `Global`. **Solution**: Check `Config.Section.Alignment.PerColumn` settings or `ConfigBuilder` calls (`tw/cell.go`). |
| Padding affects widths | **Cause**: Padding included in `Config.Widths`. **Solution**: Adjust `Config.Widths` to account for `tw.CellPadding` (`zoo.go`). |
| Colors not rendering | **Cause**: Non-ANSI terminal or incorrect codes. **Solution**: Test in ANSI-compatible terminal; use `twdw.Width` (`tw/fn.go`). |
| Colors not rendering | **Cause**: Non-ANSI terminal or incorrect codes. **Solution**: Test in ANSI-compatible terminal; use `tw.DisplayWidth` (`tw/fn.go`). |
| Caption missing | **Cause**: `Close()` not called in streaming or incorrect `Spot`. **Solution**: Ensure `Close()`; verify `tw.Caption.Spot` (`tablewriter.go`). |
| Filters not applied | **Cause**: Incorrect `PerColumn` indexing or nil filters. **Solution**: Set filters correctly; test with sample data (`tw/cell.go`). |
| Stringer cache overhead | **Cause**: Large datasets with diverse types. **Solution**: Disable `WithStringerCache` for small tables if not using `WithStringer` or if types vary greatly (`tablewriter.go`). |

View File

@@ -86,43 +86,6 @@ func main() {
Create a table with `NewTable` or `NewWriter`, configure it using options or a `Config` struct, add data with `Append` or `Bulk`, and render to an `io.Writer`. Use renderers like `Blueprint` (ASCII), `HTML`, `Markdown`, `Colorized`, or `Ocean` (streaming).
Here's how the API primitives map to the generated ASCII table:
```
API Call ASCII Table Component
-------- ---------------------
table.Header([]string{"NAME", "AGE"}) ┌──────┬─────┐ ← Borders.Top
│ NAME │ AGE │ ← Header row
├──────┼─────┤ ← Lines.ShowTop (header separator)
table.Append([]string{"Alice", "25"}) │ Alice│ 25 │ ← Data row
├──────┼─────┤ ← Separators.BetweenRows
table.Append([]string{"Bob", "30"}) │ Bob │ 30 │ ← Data row
├──────┼─────┤ ← Lines.ShowBottom (footer separator)
table.Footer([]string{"Total", "2"}) │ Total│ 2 │ ← Footer row
└──────┴─────┘ ← Borders.Bottom
```
The core components include:
- **Renderer** - Implements the core interface for converting table data into output formats. Available renderers include Blueprint (ASCII), HTML, Markdown, Colorized (ASCII with color), Ocean (streaming ASCII), and SVG.
- **Config** - The root configuration struct that controls all table behavior and appearance
- **Behavior** - Controls high-level rendering behaviors including auto-hiding empty columns, trimming row whitespace, header/footer visibility, and compact mode for optimized merged cell calculations
- **CellConfig** - The comprehensive configuration template used for table sections (header, row, footer). Combines formatting, padding, alignment, filtering, callbacks, and width constraints with global and per-column control
- **StreamConfig** - Configuration for streaming mode including enable/disable state and strict column validation
- **Rendition** - Defines how a renderer formats tables and contains the complete visual styling configuration
- **Borders** - Control the outer frame visibility (top, bottom, left, right edges) of the table
- **Lines** - Control horizontal boundary lines (above/below headers, above footers) that separate different table sections
- **Separators** - Control the visibility of separators between rows and between columns within the table content
- **Symbols** - Define the characters used for drawing table borders, corners, and junctions
These components can be configured with various `tablewriter.With*()` functional options when creating a new table.
## Examples
### Basic Examples

View File

@@ -1,8 +1,6 @@
package tablewriter
import (
"github.com/olekukonko/tablewriter/tw"
)
import "github.com/olekukonko/tablewriter/tw"
// WithBorders configures the table's border settings by updating the renderer's border configuration.
// This function is deprecated and will be removed in a future version.

View File

@@ -1,9 +1,7 @@
package tablewriter
import (
"github.com/mattn/go-runewidth"
"github.com/olekukonko/ll"
"github.com/olekukonko/tablewriter/pkg/twwidth"
"github.com/olekukonko/tablewriter/tw"
"reflect"
)
@@ -615,31 +613,6 @@ func WithRendition(rendition tw.Rendition) Option {
}
}
// WithEastAsian configures the global East Asian width calculation setting.
// - enable=true: Enables East Asian width calculations. CJK and ambiguous characters
// are typically measured as double width.
// - enable=false: Disables East Asian width calculations. Characters are generally
// measured as single width, subject to Unicode standards.
//
// This setting affects all subsequent display width calculations using the twdw package.
func WithEastAsian(enable bool) Option {
return func(target *Table) {
twwidth.SetEastAsian(enable)
}
}
// WithCondition provides a way to set a custom global runewidth.Condition
// that will be used for all subsequent display width calculations by the twwidth (twdw) package.
//
// The runewidth.Condition object allows for more fine-grained control over how rune widths
// are determined, beyond just toggling EastAsianWidth. This could include settings for
// ambiguous width characters or other future properties of runewidth.Condition.
func WithCondition(condition *runewidth.Condition) Option {
return func(target *Table) {
twwidth.SetCondition(condition)
}
}
// WithSymbols sets the symbols used for drawing table borders and separators.
// The symbols are applied to the table's renderer configuration, if a renderer is set.
// If no renderer is set (target.renderer is nil), this option has no effect. .
@@ -709,11 +682,8 @@ func defaultConfig() Config {
PerColumn: []tw.Align{},
},
},
Stream: tw.StreamConfig{
Enable: false,
StrictColumns: false,
},
Debug: false,
Stream: tw.StreamConfig{},
Debug: false,
Behavior: tw.Behavior{
AutoHide: tw.Off,
TrimSpace: tw.On,
@@ -872,8 +842,6 @@ func mergeStreamConfig(dst, src tw.StreamConfig) tw.StreamConfig {
if src.Enable {
dst.Enable = true
}
dst.StrictColumns = src.StrictColumns
return dst
}

View File

@@ -8,13 +8,12 @@
package twwarp
import (
"github.com/rivo/uniseg"
"math"
"strings"
"unicode"
"github.com/olekukonko/tablewriter/pkg/twwidth" // IMPORT YOUR NEW PACKAGE
"github.com/rivo/uniseg"
// "github.com/mattn/go-runewidth" // This can be removed if all direct uses are gone
"github.com/mattn/go-runewidth"
)
const (
@@ -60,8 +59,7 @@ func WrapString(s string, lim int) ([]string, int) {
var lines []string
max := 0
for _, v := range words {
// max = runewidth.StringWidth(v) // OLD
max = twwidth.Width(v) // NEW: Use twdw.Width
max = runewidth.StringWidth(v)
if max > lim {
lim = max
}
@@ -84,13 +82,12 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
return []string{""}, lim
}
if strings.TrimSpace(s) == "" { // All spaces
// if runewidth.StringWidth(s) <= lim { // OLD
if twwidth.Width(s) <= lim { // NEW: Use twdw.Width
// return []string{s}, runewidth.StringWidth(s) // OLD
return []string{s}, twwidth.Width(s) // NEW: Use twdw.Width
if runewidth.StringWidth(s) <= lim {
return []string{s}, runewidth.StringWidth(s)
}
// For very long all-space strings, "wrap" by truncating to the limit.
if lim > 0 {
// Use our new helper function to get a substring of the correct display width
substring, _ := stringToDisplayWidth(s, lim)
return []string{substring}, lim
}
@@ -99,6 +96,7 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
var leadingSpaces, trailingSpaces, coreContent string
firstNonSpace := strings.IndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
// firstNonSpace will not be -1 due to TrimSpace check above.
leadingSpaces = s[:firstNonSpace]
lastNonSpace := strings.LastIndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
trailingSpaces = s[lastNonSpace+1:]
@@ -118,8 +116,7 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
maxCoreWordWidth := 0
for _, v := range words {
// w := runewidth.StringWidth(v) // OLD
w := twwidth.Width(v) // NEW: Use twdw.Width
w := runewidth.StringWidth(v)
if w > maxCoreWordWidth {
maxCoreWordWidth = w
}
@@ -156,14 +153,15 @@ func stringToDisplayWidth(s string, targetWidth int) (substring string, actualWi
g := uniseg.NewGraphemes(s)
for g.Next() {
grapheme := g.Str()
// graphemeWidth := runewidth.StringWidth(grapheme) // OLD
graphemeWidth := twwidth.Width(grapheme) // NEW: Use twdw.Width
graphemeWidth := runewidth.StringWidth(grapheme) // Get width of the current grapheme cluster
if currentWidth+graphemeWidth > targetWidth {
// Adding this grapheme would exceed the target width
break
}
currentWidth += graphemeWidth
// Get the end byte position of the current grapheme cluster
_, e := g.Positions()
endIndex = e
}
@@ -188,15 +186,14 @@ func WrapWords(words []string, spc, lim, pen int) [][]string {
}
lengths := make([]int, n)
for i := 0; i < n; i++ {
// lengths[i] = runewidth.StringWidth(words[i]) // OLD
lengths[i] = twwidth.Width(words[i]) // NEW: Use twdw.Width
lengths[i] = runewidth.StringWidth(words[i])
}
nbrk := make([]int, n)
cost := make([]int, n)
for i := range cost {
cost[i] = math.MaxInt32
}
remainderLen := lengths[n-1] // Uses updated lengths
remainderLen := lengths[n-1]
for i := n - 1; i >= 0; i-- {
if i < n-1 {
remainderLen += spc + lengths[i]

View File

@@ -1,321 +0,0 @@
package twwidth
import (
"bytes"
"github.com/mattn/go-runewidth"
"regexp"
"strings"
"sync"
)
// condition holds the global runewidth configuration, including East Asian width settings.
var condition *runewidth.Condition
// mu protects access to condition and widthCache for thread safety.
var mu sync.Mutex
// ansi is a compiled regular expression for stripping ANSI escape codes from strings.
var ansi = Filter()
func init() {
condition = runewidth.NewCondition()
widthCache = make(map[cacheKey]int)
}
// cacheKey is used as a key for memoizing string width results in widthCache.
type cacheKey struct {
str string // Input string
eastAsianWidth bool // East Asian width setting
}
// widthCache stores memoized results of Width calculations to improve performance.
var widthCache map[cacheKey]int
// Filter compiles and returns a regular expression for matching ANSI escape sequences,
// including CSI (Control Sequence Introducer) and OSC (Operating System Command) sequences.
// The returned regex can be used to strip ANSI codes from strings.
func Filter() *regexp.Regexp {
var regESC = "\x1b" // ASCII escape character
var regBEL = "\x07" // ASCII bell character
// ANSI string terminator: either ESC+\ or BEL
var regST = "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
// Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
var regCSI = regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
// Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
var regOSC = regexp.QuoteMeta(regESC+"]") + ".*?" + regST
// Combine CSI and OSC patterns into a single regex
return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")")
}
// SetEastAsian enables or disables East Asian width handling for width calculations.
// When the setting changes, the width cache is cleared to ensure accuracy.
// This function is thread-safe.
//
// Example:
//
// twdw.SetEastAsian(true) // Enable East Asian width handling
func SetEastAsian(enable bool) {
mu.Lock()
defer mu.Unlock()
if condition.EastAsianWidth != enable {
condition.EastAsianWidth = enable
widthCache = make(map[cacheKey]int) // Clear cache on setting change
}
}
// SetCondition updates the global runewidth.Condition used for width calculations.
// When the condition is changed, the width cache is cleared.
// This function is thread-safe.
//
// Example:
//
// newCond := runewidth.NewCondition()
// newCond.EastAsianWidth = true
// twdw.SetCondition(newCond)
func SetCondition(newCond *runewidth.Condition) {
mu.Lock()
defer mu.Unlock()
condition = newCond
widthCache = make(map[cacheKey]int) // Clear cache on setting change
}
// Width calculates the visual width of a string, excluding ANSI escape sequences,
// using the go-runewidth package for accurate Unicode handling. It accounts for the
// current East Asian width setting and caches results for performance.
// This function is thread-safe.
//
// Example:
//
// width := twdw.Width("Hello\x1b[31mWorld") // Returns 10
func Width(str string) int {
mu.Lock()
key := cacheKey{str: str, eastAsianWidth: condition.EastAsianWidth}
if w, found := widthCache[key]; found {
mu.Unlock()
return w
}
mu.Unlock()
// Use a temporary condition to avoid holding the lock during calculation
tempCond := runewidth.NewCondition()
tempCond.EastAsianWidth = key.eastAsianWidth
stripped := ansi.ReplaceAllLiteralString(str, "")
calculatedWidth := tempCond.StringWidth(stripped)
mu.Lock()
widthCache[key] = calculatedWidth
mu.Unlock()
return calculatedWidth
}
// WidthNoCache calculates the visual width of a string without using or
// updating the global cache. It uses the current global East Asian width setting.
// This function is intended for internal use (e.g., benchmarking) and is thread-safe.
//
// Example:
//
// width := twdw.WidthNoCache("Hello\x1b[31mWorld") // Returns 10
func WidthNoCache(str string) int {
mu.Lock()
currentEA := condition.EastAsianWidth
mu.Unlock()
tempCond := runewidth.NewCondition()
tempCond.EastAsianWidth = currentEA
stripped := ansi.ReplaceAllLiteralString(str, "")
return tempCond.StringWidth(stripped)
}
// Display calculates the visual width of a string, excluding ANSI escape sequences,
// using the provided runewidth condition. Unlike Width, it does not use caching
// and is intended for cases where a specific condition is required.
// This function is thread-safe with respect to the provided condition.
//
// Example:
//
// cond := runewidth.NewCondition()
// width := twdw.Display(cond, "Hello\x1b[31mWorld") // Returns 10
func Display(cond *runewidth.Condition, str string) int {
return cond.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
}
// Truncate shortens a string to fit within a specified visual width, optionally
// appending a suffix (e.g., "..."). It preserves ANSI escape sequences and adds
// a reset sequence (\x1b[0m) if needed to prevent formatting bleed. The function
// respects the global East Asian width setting and is thread-safe.
//
// If maxWidth is negative, an empty string is returned. If maxWidth is zero and
// a suffix is provided, the suffix is returned. If the string's visual width is
// less than or equal to maxWidth, the string (and suffix, if provided and fits)
// is returned unchanged.
//
// Example:
//
// s := twdw.Truncate("Hello\x1b[31mWorld", 5, "...") // Returns "Hello..."
// s = twdw.Truncate("Hello", 10) // Returns "Hello"
func Truncate(s string, maxWidth int, suffix ...string) string {
if maxWidth < 0 {
return ""
}
suffixStr := strings.Join(suffix, "")
sDisplayWidth := Width(s) // Uses global cached Width
suffixDisplayWidth := Width(suffixStr) // Uses global cached Width
// Case 1: Original string is visually empty.
if sDisplayWidth == 0 {
// If suffix is provided and fits within maxWidth (or if maxWidth is generous)
if len(suffixStr) > 0 && suffixDisplayWidth <= maxWidth {
return suffixStr
}
// If s has ANSI codes (len(s)>0) but maxWidth is 0, can't display them.
if maxWidth == 0 && len(s) > 0 {
return ""
}
return s // Returns "" or original ANSI codes
}
// Case 2: maxWidth is 0, but string has content. Cannot display anything.
if maxWidth == 0 {
return ""
}
// Case 3: String fits completely or fits with suffix.
// Here, maxWidth is the total budget for the line.
if sDisplayWidth <= maxWidth {
if len(suffixStr) == 0 { // No suffix.
return s
}
// Suffix is provided. Check if s + suffix fits.
if sDisplayWidth+suffixDisplayWidth <= maxWidth {
return s + suffixStr
}
// s fits, but s + suffix is too long. Return s.
return s
}
// Case 4: String needs truncation (sDisplayWidth > maxWidth).
// maxWidth is the total budget for the final string (content + suffix).
// Capture the global EastAsianWidth setting once for consistent use
mu.Lock()
currentGlobalEastAsianWidth := condition.EastAsianWidth
mu.Unlock()
// Special case for EastAsian true: if only suffix fits, return suffix.
// This was derived from previous test behavior.
if len(suffixStr) > 0 && currentGlobalEastAsianWidth {
provisionalContentWidth := maxWidth - suffixDisplayWidth
if provisionalContentWidth == 0 { // Exactly enough space for suffix only
return suffixStr // <<<< MODIFIED: No ANSI reset here
}
}
// Calculate the budget for the content part, reserving space for the suffix.
targetContentForIteration := maxWidth
if len(suffixStr) > 0 {
targetContentForIteration -= suffixDisplayWidth
}
// If content budget is negative, means not even suffix fits (or no suffix and no space).
// However, if only suffix fits, it should be handled.
if targetContentForIteration < 0 {
// Can we still fit just the suffix?
if len(suffixStr) > 0 && suffixDisplayWidth <= maxWidth {
if strings.Contains(s, "\x1b[") {
return "\x1b[0m" + suffixStr
}
return suffixStr
}
return "" // Cannot fit anything.
}
// If targetContentForIteration is 0, loop won't run, result will be empty string, then suffix is added.
var contentBuf bytes.Buffer
var currentContentDisplayWidth int
var ansiSeqBuf bytes.Buffer
inAnsiSequence := false
ansiWrittenToContent := false
localRunewidthCond := runewidth.NewCondition()
localRunewidthCond.EastAsianWidth = currentGlobalEastAsianWidth
for _, r := range s {
if r == '\x1b' {
inAnsiSequence = true
ansiSeqBuf.Reset()
ansiSeqBuf.WriteRune(r)
} else if inAnsiSequence {
ansiSeqBuf.WriteRune(r)
seqBytes := ansiSeqBuf.Bytes()
seqLen := len(seqBytes)
terminated := false
if seqLen >= 2 {
introducer := seqBytes[1]
if introducer == '[' {
if seqLen >= 3 && r >= 0x40 && r <= 0x7E {
terminated = true
}
} else if introducer == ']' {
if r == '\x07' {
terminated = true
} else if seqLen > 1 && seqBytes[seqLen-2] == '\x1b' && r == '\\' { // Check for ST: \x1b\
terminated = true
}
}
}
if terminated {
inAnsiSequence = false
contentBuf.Write(ansiSeqBuf.Bytes())
ansiWrittenToContent = true
ansiSeqBuf.Reset()
}
} else { // Normal character
runeDisplayWidth := localRunewidthCond.RuneWidth(r)
if targetContentForIteration == 0 { // No budget for content at all
break
}
if currentContentDisplayWidth+runeDisplayWidth > targetContentForIteration {
break
}
contentBuf.WriteRune(r)
currentContentDisplayWidth += runeDisplayWidth
}
}
result := contentBuf.String()
// Suffix is added if:
// 1. A suffix string is provided.
// 2. Truncation actually happened (sDisplayWidth > maxWidth originally)
// OR if the content part is empty but a suffix is meant to be shown
// (e.g. targetContentForIteration was 0).
if len(suffixStr) > 0 {
// Add suffix if we are in the truncation path (sDisplayWidth > maxWidth)
// OR if targetContentForIteration was 0 (meaning only suffix should be shown)
// but we must ensure we don't exceed original maxWidth.
// The logic above for targetContentForIteration already ensures space.
needsReset := false
// Condition for reset: if styling was active in 's' and might affect suffix
if (ansiWrittenToContent || (inAnsiSequence && strings.Contains(s, "\x1b["))) && (currentContentDisplayWidth > 0 || ansiWrittenToContent) {
if !strings.HasSuffix(result, "\x1b[0m") {
needsReset = true
}
} else if currentContentDisplayWidth > 0 && strings.Contains(result, "\x1b[") && !strings.HasSuffix(result, "\x1b[0m") && strings.Contains(s, "\x1b[") {
// If result has content and ANSI, and original had ANSI, and result not already reset
needsReset = true
}
if needsReset {
result += "\x1b[0m"
}
result += suffixStr
}
return result
}

View File

@@ -2,7 +2,6 @@ package renderer
import (
"github.com/olekukonko/ll"
"github.com/olekukonko/tablewriter/pkg/twwidth"
"io"
"strings"
@@ -43,7 +42,7 @@ func NewBlueprint(configs ...tw.Rendition) *Blueprint {
// Merge user settings with default settings
cfg.Settings = mergeSettings(cfg.Settings, userCfg.Settings)
}
return &Blueprint{config: cfg, logger: ll.New("blueprint")}
return &Blueprint{config: cfg}
}
// Close performs cleanup (no-op in this implementation).
@@ -107,7 +106,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
}
if prefix != tw.Empty || suffix != tw.Empty {
line.WriteString(prefix + suffix + tw.NewLine)
totalLineWidth = twwidth.Width(prefix) + twwidth.Width(suffix)
totalLineWidth = tw.DisplayWidth(prefix) + tw.DisplayWidth(suffix)
f.w.Write([]byte(line.String()))
}
f.logger.Debugf("Line: Handled empty row/widths case (total width %d)", totalLineWidth)
@@ -120,13 +119,13 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
targetTotalWidth += ctx.Row.Widths.Get(colIdx)
}
if f.config.Borders.Left.Enabled() {
targetTotalWidth += twwidth.Width(f.config.Symbols.Column())
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column())
}
if f.config.Borders.Right.Enabled() {
targetTotalWidth += twwidth.Width(f.config.Symbols.Column())
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column())
}
if f.config.Settings.Separators.BetweenColumns.Enabled() && len(sortedKeys) > 1 {
targetTotalWidth += twwidth.Width(f.config.Symbols.Column()) * (len(sortedKeys) - 1)
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column()) * (len(sortedKeys) - 1)
}
// Add left border if enabled
@@ -134,7 +133,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
if f.config.Borders.Left.Enabled() {
leftBorder := jr.RenderLeft()
line.WriteString(leftBorder)
leftBorderWidth = twwidth.Width(leftBorder)
leftBorderWidth = tw.DisplayWidth(leftBorder)
totalLineWidth += leftBorderWidth
f.logger.Debugf("Line: Left border='%s' (f.width %d)", leftBorder, leftBorderWidth)
}
@@ -157,11 +156,11 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
// Adjust colWidth to account for wider borders
adjustedColWidth := colWidth
if f.config.Borders.Left.Enabled() && keyIndex == 0 {
adjustedColWidth -= leftBorderWidth - twwidth.Width(f.config.Symbols.Column())
adjustedColWidth -= leftBorderWidth - tw.DisplayWidth(f.config.Symbols.Column())
}
if f.config.Borders.Right.Enabled() && keyIndex == len(visibleColIndices)-1 {
rightBorderWidth := twwidth.Width(jr.RenderRight(currentColIdx))
adjustedColWidth -= rightBorderWidth - twwidth.Width(f.config.Symbols.Column())
rightBorderWidth := tw.DisplayWidth(jr.RenderRight(currentColIdx))
adjustedColWidth -= rightBorderWidth - tw.DisplayWidth(f.config.Symbols.Column())
}
if adjustedColWidth < 0 {
adjustedColWidth = 0
@@ -173,7 +172,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
totalLineWidth += adjustedColWidth
f.logger.Debugf("Line: Rendered spaces='%s' (f.width %d) for col %d", spaces, adjustedColWidth, currentColIdx)
} else {
segmentWidth := twwidth.Width(segment)
segmentWidth := tw.DisplayWidth(segment)
if segmentWidth == 0 {
segmentWidth = 1 // Avoid division by zero
f.logger.Warnf("Line: Segment='%s' has zero width, using 1", segment)
@@ -184,11 +183,11 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
repeat = 1
}
repeatedSegment := strings.Repeat(segment, repeat)
actualWidth := twwidth.Width(repeatedSegment)
actualWidth := tw.DisplayWidth(repeatedSegment)
if actualWidth > adjustedColWidth {
// Truncate if too long
repeatedSegment = twwidth.Truncate(repeatedSegment, adjustedColWidth)
actualWidth = twwidth.Width(repeatedSegment)
repeatedSegment = tw.TruncateString(repeatedSegment, adjustedColWidth)
actualWidth = tw.DisplayWidth(repeatedSegment)
f.logger.Debugf("Line: Truncated segment='%s' to width %d", repeatedSegment, actualWidth)
} else if actualWidth < adjustedColWidth {
// Pad with segment character to match adjustedColWidth
@@ -196,7 +195,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
for i := 0; i < remainingWidth/segmentWidth; i++ {
repeatedSegment += segment
}
actualWidth = twwidth.Width(repeatedSegment)
actualWidth = tw.DisplayWidth(repeatedSegment)
if actualWidth < adjustedColWidth {
repeatedSegment = tw.PadRight(repeatedSegment, tw.Space, adjustedColWidth)
actualWidth = adjustedColWidth
@@ -215,13 +214,13 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
nextColIdx := visibleColIndices[keyIndex+1]
junction := jr.RenderJunction(currentColIdx, nextColIdx)
// Use center symbol (❀) or column separator (|) to match data rows
if twwidth.Width(junction) != twwidth.Width(f.config.Symbols.Column()) {
if tw.DisplayWidth(junction) != tw.DisplayWidth(f.config.Symbols.Column()) {
junction = f.config.Symbols.Center()
if twwidth.Width(junction) != twwidth.Width(f.config.Symbols.Column()) {
if tw.DisplayWidth(junction) != tw.DisplayWidth(f.config.Symbols.Column()) {
junction = f.config.Symbols.Column()
}
}
junctionWidth := twwidth.Width(junction)
junctionWidth := tw.DisplayWidth(junction)
line.WriteString(junction)
totalLineWidth += junctionWidth
f.logger.Debugf("Line: Junction between %d and %d: '%s' (f.width %d)", currentColIdx, nextColIdx, junction, junctionWidth)
@@ -233,7 +232,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
if f.config.Borders.Right.Enabled() && len(visibleColIndices) > 0 {
lastIdx := visibleColIndices[len(visibleColIndices)-1]
rightBorder := jr.RenderRight(lastIdx)
rightBorderWidth = twwidth.Width(rightBorder)
rightBorderWidth = tw.DisplayWidth(rightBorder)
line.WriteString(rightBorder)
totalLineWidth += rightBorderWidth
f.logger.Debugf("Line: Right border='%s' (f.width %d)", rightBorder, rightBorderWidth)
@@ -277,7 +276,7 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
content, width, align, padding.Left, padding.Right)
// Calculate display width of content
runeWidth := twwidth.Width(content)
runeWidth := tw.DisplayWidth(content)
// Set default padding characters
leftPadChar := padding.Left
@@ -293,8 +292,8 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
//}
// Calculate padding widths
padLeftWidth := twwidth.Width(leftPadChar)
padRightWidth := twwidth.Width(rightPadChar)
padLeftWidth := tw.DisplayWidth(leftPadChar)
padRightWidth := tw.DisplayWidth(rightPadChar)
// Calculate available width for content
availableContentWidth := width - padLeftWidth - padRightWidth
@@ -305,8 +304,8 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
// Truncate content if it exceeds available width
if runeWidth > availableContentWidth {
content = twwidth.Truncate(content, availableContentWidth)
runeWidth = twwidth.Width(content)
content = tw.TruncateString(content, availableContentWidth)
runeWidth = tw.DisplayWidth(content)
f.logger.Debugf("Truncated content to fit %d: '%s' (new width %d)", availableContentWidth, content, runeWidth)
}
@@ -364,10 +363,10 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
}
output := result.String()
finalWidth := twwidth.Width(output)
finalWidth := tw.DisplayWidth(output)
// Adjust output to match target width
if finalWidth > width {
output = twwidth.Truncate(output, width)
output = tw.TruncateString(output, width)
f.logger.Debugf("formatCell: Truncated output to width %d", width)
} else if finalWidth < width {
output = tw.PadRight(output, tw.Space, width)
@@ -375,9 +374,9 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
}
// Log warning if final width doesn't match target
if f.logger.Enabled() && twwidth.Width(output) != width {
if f.logger.Enabled() && tw.DisplayWidth(output) != width {
f.logger.Debugf("formatCell Warning: Final width %d does not match target %d for result '%s'",
twwidth.Width(output), width, output)
tw.DisplayWidth(output), width, output)
}
f.logger.Debugf("Formatted cell final result: '%s' (target width %d)", output, width)
@@ -408,14 +407,14 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
totalLineWidth := 0 // Track total display width
if prefix != tw.Empty {
output.WriteString(prefix)
totalLineWidth += twwidth.Width(prefix)
f.logger.Debugf("renderLine: Prefix='%s' (f.width %d)", prefix, twwidth.Width(prefix))
totalLineWidth += tw.DisplayWidth(prefix)
f.logger.Debugf("renderLine: Prefix='%s' (f.width %d)", prefix, tw.DisplayWidth(prefix))
}
colIndex := 0
separatorDisplayWidth := 0
if f.config.Settings.Separators.BetweenColumns.Enabled() {
separatorDisplayWidth = twwidth.Width(columnSeparator)
separatorDisplayWidth = tw.DisplayWidth(columnSeparator)
}
// Process each column
@@ -543,7 +542,7 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
formattedCell := f.formatCell(cellData, visualWidth, padding, align)
if len(formattedCell) > 0 {
output.WriteString(formattedCell)
cellWidth := twwidth.Width(formattedCell)
cellWidth := tw.DisplayWidth(formattedCell)
totalLineWidth += cellWidth
f.logger.Debugf("renderLine: Rendered col %d, formattedCell='%s' (f.width %d), totalLineWidth=%d", colIndex, formattedCell, cellWidth, totalLineWidth)
}
@@ -562,19 +561,17 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
// Add suffix and adjust total width
if output.Len() > len(prefix) || f.config.Borders.Right.Enabled() {
output.WriteString(suffix)
totalLineWidth += twwidth.Width(suffix)
f.logger.Debugf("renderLine: Suffix='%s' (f.width %d)", suffix, twwidth.Width(suffix))
totalLineWidth += tw.DisplayWidth(suffix)
f.logger.Debugf("renderLine: Suffix='%s' (f.width %d)", suffix, tw.DisplayWidth(suffix))
}
output.WriteString(tw.NewLine)
f.w.Write([]byte(output.String()))
f.logger.Debugf("renderLine: Final rendered line: '%s' (total width %d)", strings.TrimSuffix(output.String(), tw.NewLine), totalLineWidth)
}
// Rendition updates the Blueprint's configuration.
func (f *Blueprint) Rendition(config tw.Rendition) {
f.config = mergeRendition(f.config, config)
f.logger.Debugf("Blueprint.Rendition updated. New config: %+v", f.config)
f.logger.Debugf("Blueprint.Rendition updated. New internal config: %+v", f.config)
}
// Ensure Blueprint implements tw.Renditioning

View File

@@ -4,7 +4,6 @@ import (
"github.com/fatih/color"
"github.com/olekukonko/ll"
"github.com/olekukonko/ll/lh"
"github.com/olekukonko/tablewriter/pkg/twwidth"
"io"
"strings"
@@ -255,7 +254,7 @@ func (c *Colorized) Line(ctx tw.Formatting) {
line.WriteString(strings.Repeat(tw.Space, colWidth))
} else {
// Calculate how many times to repeat the segment
segmentWidth := twwidth.Width(segment)
segmentWidth := tw.DisplayWidth(segment)
if segmentWidth <= 0 {
segmentWidth = 1
}
@@ -267,7 +266,7 @@ func (c *Colorized) Line(ctx tw.Formatting) {
line.WriteString(drawnSegment)
// Adjust for width discrepancies
actualDrawnWidth := twwidth.Width(drawnSegment)
actualDrawnWidth := tw.DisplayWidth(drawnSegment)
if actualDrawnWidth < colWidth {
missingWidth := colWidth - actualDrawnWidth
spaces := strings.Repeat(tw.Space, missingWidth)
@@ -374,7 +373,7 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
}
// Calculate visual width of content
contentVisualWidth := twwidth.Width(content)
contentVisualWidth := tw.DisplayWidth(content)
// Set default padding characters
padLeftCharStr := padding.Left
@@ -387,8 +386,8 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
}
// Calculate padding widths
definedPadLeftWidth := twwidth.Width(padLeftCharStr)
definedPadRightWidth := twwidth.Width(padRightCharStr)
definedPadLeftWidth := tw.DisplayWidth(padLeftCharStr)
definedPadRightWidth := tw.DisplayWidth(padRightCharStr)
// Calculate available width for content and alignment
availableForContentAndAlign := width - definedPadLeftWidth - definedPadRightWidth
if availableForContentAndAlign < 0 {
@@ -397,8 +396,8 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
// Truncate content if it exceeds available width
if contentVisualWidth > availableForContentAndAlign {
content = twwidth.Truncate(content, availableForContentAndAlign)
contentVisualWidth = twwidth.Width(content)
content = tw.TruncateString(content, availableForContentAndAlign)
contentVisualWidth = tw.DisplayWidth(content)
c.logger.Debugf("Truncated content to fit %d: '%s' (new width %d)", availableForContentAndAlign, content, contentVisualWidth)
}
@@ -473,12 +472,12 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
output := sb.String()
// Adjust output width if necessary
currentVisualWidth := twwidth.Width(output)
currentVisualWidth := tw.DisplayWidth(output)
if currentVisualWidth != width {
c.logger.Debugf("formatCell MISMATCH: content='%s', target_w=%d. Calculated parts width = %d. String: '%s'",
content, width, currentVisualWidth, output)
if currentVisualWidth > width {
output = twwidth.Truncate(output, width)
output = tw.TruncateString(output, width)
} else {
paddingSpacesStr := strings.Repeat(tw.Space, width-currentVisualWidth)
if len(tint.BG) > 0 {
@@ -487,10 +486,10 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
output += paddingSpacesStr
}
}
c.logger.Debugf("formatCell Post-Correction: Target %d, New Visual width %d. Output: '%s'", width, twwidth.Width(output), output)
c.logger.Debugf("formatCell Post-Correction: Target %d, New Visual width %d. Output: '%s'", width, tw.DisplayWidth(output), output)
}
c.logger.Debugf("Formatted cell final result: '%s' (target width %d, display width %d)", output, width, twwidth.Width(output))
c.logger.Debugf("Formatted cell final result: '%s' (target width %d, display width %d)", output, width, tw.DisplayWidth(output))
return output
}
@@ -530,7 +529,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
separatorString := tw.Empty
if c.config.Settings.Separators.BetweenColumns.Enabled() {
separatorString = c.config.Separator.Apply(c.config.Symbols.Column())
separatorDisplayWidth = twwidth.Width(c.config.Symbols.Column())
separatorDisplayWidth = tw.DisplayWidth(c.config.Symbols.Column())
}
// Process each column
@@ -694,7 +693,8 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
// Rendition updates the parts of ColorizedConfig that correspond to tw.Rendition
// by merging the provided newRendition. Color-specific Tints are not modified.
func (c *Colorized) Rendition(newRendition tw.Rendition) { // Method name matches interface
c.logger.Debug("Colorized.Rendition called. Current B/Sym/Set: B:%+v, Sym:%T, S:%+v. Override: %+v", c.config.Borders, c.config.Symbols, c.config.Settings, newRendition)
c.logger.Debug("Colorized.Rendition called. Current B/Sym/Set: B:%+v, Sym:%T, S:%+v. Override: %+v",
c.config.Borders, c.config.Symbols, c.config.Settings, newRendition)
currentRenditionPart := tw.Rendition{
Borders: c.config.Borders,

View File

@@ -63,7 +63,6 @@ func NewHTML(configs ...HTMLConfig) *HTML {
tableStarted: false,
tbodyStarted: false,
tfootStarted: false,
logger: ll.New("html"),
}
}

View File

@@ -2,7 +2,6 @@ package renderer
import (
"github.com/olekukonko/ll"
"github.com/olekukonko/tablewriter/pkg/twwidth"
"io"
"strings"
@@ -36,7 +35,7 @@ func NewMarkdown(configs ...tw.Rendition) *Markdown {
if len(configs) > 0 {
cfg = mergeMarkdownConfig(cfg, configs[0])
}
return &Markdown{config: cfg, logger: ll.New("markdown")}
return &Markdown{config: cfg}
}
// mergeMarkdownConfig combines user-provided config with Markdown defaults, enforcing Markdown-specific settings.
@@ -158,7 +157,7 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
//if m.config.Settings.TrimWhitespace.Enabled() {
// content = strings.TrimSpace(content)
//}
contentVisualWidth := twwidth.Width(content)
contentVisualWidth := tw.DisplayWidth(content)
// Use specified padding characters or default to spaces
padLeftChar := padding.Left
@@ -171,8 +170,8 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
}
// Calculate padding widths
padLeftCharWidth := twwidth.Width(padLeftChar)
padRightCharWidth := twwidth.Width(padRightChar)
padLeftCharWidth := tw.DisplayWidth(padLeftChar)
padRightCharWidth := tw.DisplayWidth(padRightChar)
minWidth := tw.Max(3, contentVisualWidth+padLeftCharWidth+padRightCharWidth)
targetWidth := tw.Max(width, minWidth)
@@ -213,7 +212,7 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
result := leftPadStr + content + rightPadStr
// Adjust width if needed
finalWidth := twwidth.Width(result)
finalWidth := tw.DisplayWidth(result)
if finalWidth != targetWidth {
m.logger.Debugf("Markdown formatCell MISMATCH: content='%s', target_w=%d, paddingL='%s', paddingR='%s', align=%s -> result='%s', result_w=%d",
content, targetWidth, padding.Left, padding.Right, align, result, finalWidth)
@@ -230,9 +229,9 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
result += adjStr
}
} else {
result = twwidth.Truncate(result, targetWidth)
result = tw.TruncateString(result, targetWidth)
}
m.logger.Debugf("Markdown formatCell Corrected: target_w=%d, result='%s', new_w=%d", targetWidth, result, twwidth.Width(result))
m.logger.Debugf("Markdown formatCell Corrected: target_w=%d, result='%s', new_w=%d", targetWidth, result, tw.DisplayWidth(result))
}
m.logger.Debugf("Markdown formatCell: content='%s', width=%d, align=%s, paddingL='%s', paddingR='%s' -> '%s' (target %d)",
@@ -263,11 +262,11 @@ func (m *Markdown) formatSeparator(width int, align tw.Align) string {
}
result := sb.String()
currentLen := twwidth.Width(result)
currentLen := tw.DisplayWidth(result)
if currentLen < targetWidth {
result += strings.Repeat("-", targetWidth-currentLen)
} else if currentLen > targetWidth {
result = twwidth.Truncate(result, targetWidth)
result = tw.TruncateString(result, targetWidth)
}
m.logger.Debugf("Markdown formatSeparator: width=%d, align=%s -> '%s'", width, align, result)
@@ -315,7 +314,7 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader
output.WriteString(prefix)
colIndex := 0
separatorWidth := twwidth.Width(separator)
separatorWidth := tw.DisplayWidth(separator)
for colIndex < numCols {
cellCtx, ok := ctx.Row.Current[colIndex]

View File

@@ -1,7 +1,6 @@
package renderer
import (
"github.com/olekukonko/tablewriter/pkg/twwidth"
"io"
"strings"
@@ -107,9 +106,17 @@ func (o *Ocean) Header(headers [][]string, ctx tw.Formatting) {
if !o.widthsFinalized {
o.tryFinalizeWidths(ctx)
}
// The batch renderer (table.go/renderHeader) will call Line() for the top border
// and for the header separator if its main config t.config says so.
// So, Ocean.Header should *not* draw these itself when in batch mode.
// For true streaming, table.go's streamRenderHeader would make these Line() calls.
// Decision: Ocean.Header *only* renders header content.
// Lines (top border, header separator) are managed by the caller (batch or stream logic in table.go).
if !o.widthsFinalized {
o.logger.Error("Ocean.Header: Cannot render content, widths are not finalized.")
// o.headerContentRendered = true; // No, content wasn't rendered.
return
}
@@ -126,7 +133,10 @@ func (o *Ocean) Header(headers [][]string, ctx tw.Formatting) {
o.headerContentRendered = true
} else {
o.logger.Debug("Ocean.Header: No actual header content lines to render.")
// If header is empty, table.go's renderHeader might still call Line() for the separator.
// o.headerContentRendered remains false if no content.
}
// DO NOT draw the header separator line here. Let table.go's renderHeader or streamRenderHeader call o.Line().
}
func (o *Ocean) Row(row []string, ctx tw.Formatting) {
@@ -135,6 +145,15 @@ func (o *Ocean) Row(row []string, ctx tw.Formatting) {
if !o.widthsFinalized {
o.tryFinalizeWidths(ctx)
}
// Top border / header separator logic:
// If this is the very first output, table.go's batch renderHeader (or streamRenderHeader)
// should have already called Line() for top border and header separator.
// If Header() was called but rendered no content, table.go's renderHeader would still call Line() for the separator.
// If Header() was never called by table.go (e.g. streaming rows directly after Start()),
// then table.go's streamAppendRow needs to handle initial lines.
// Decision: Ocean.Row *only* renders row content.
if !o.widthsFinalized {
o.logger.Error("Ocean.Row: Cannot render content, widths are not finalized.")
return
@@ -152,6 +171,11 @@ func (o *Ocean) Footer(footers [][]string, ctx tw.Formatting) {
o.tryFinalizeWidths(ctx)
o.logger.Warn("Ocean.Footer: Widths finalized at Footer stage (unusual).")
}
// Separator line before footer:
// This should be handled by table.go's renderFooter or streamRenderFooter calling o.Line().
// Decision: Ocean.Footer *only* renders footer content.
if !o.widthsFinalized {
o.logger.Error("Ocean.Footer: Cannot render content, widths are not finalized.")
return
@@ -170,19 +194,24 @@ func (o *Ocean) Footer(footers [][]string, ctx tw.Formatting) {
} else {
o.logger.Debug("Ocean.Footer: No actual footer content lines to render.")
}
// DO NOT draw the bottom border here. Let table.go's main Close or batch renderFooter call o.Line().
}
func (o *Ocean) Line(ctx tw.Formatting) {
// This method is now called EXTERNALLY by table.go's batch or stream logic
// to draw all horizontal lines (top border, header sep, footer sep, bottom border).
if !o.widthsFinalized {
// If Line is called before widths are known (e.g. table.go's batch renderHeader trying to draw top border)
// we must try to finalize widths from this context.
o.tryFinalizeWidths(ctx)
if !o.widthsFinalized {
o.logger.Error("Ocean.Line: Called but widths could not be finalized. Skipping line rendering.")
return
}
}
// Ensure Line uses the consistent fixedWidths for drawing
ctx.Row.Widths = o.fixedWidths
o.logger.Debugf("Ocean.Line DRAWING: Level=%v, Loc=%s, Pos=%s, IsSubRow=%t, WidthsLen=%d", ctx.Level, ctx.Row.Location, ctx.Row.Position, ctx.IsSubRow, ctx.Row.Widths.Len())
jr := NewJunction(JunctionContext{
@@ -233,7 +262,7 @@ func (o *Ocean) Line(ctx tw.Formatting) {
if segmentChar == tw.Empty {
segmentChar = o.config.Symbols.Row()
}
segmentDisplayWidth := twwidth.Width(segmentChar)
segmentDisplayWidth := tw.DisplayWidth(segmentChar)
if segmentDisplayWidth <= 0 {
segmentDisplayWidth = 1
}
@@ -267,6 +296,12 @@ func (o *Ocean) Line(ctx tw.Formatting) {
func (o *Ocean) Close() error {
o.logger.Debug("Ocean.Close() called.")
// The actual bottom border drawing is expected to be handled by table.go's
// batch render logic (renderFooter) or stream logic (streamRenderBottomBorder)
// by making an explicit call to o.Line() with the correct context.
// Ocean.Close() itself does not draw the bottom border to avoid duplication.
// Only reset state.
o.resetState()
return nil
}
@@ -339,7 +374,7 @@ func (o *Ocean) renderContentLine(ctx tw.Formatting, lineData []string) {
}
if k < hSpan-1 && o.config.Settings.Separators.BetweenColumns.Enabled() {
currentMergeTotalRenderWidth += twwidth.Width(o.config.Symbols.Column())
currentMergeTotalRenderWidth += tw.DisplayWidth(o.config.Symbols.Column())
}
}
actualCellWidthToRender = currentMergeTotalRenderWidth
@@ -393,7 +428,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
return tw.Empty
}
contentDisplayWidth := twwidth.Width(content)
contentDisplayWidth := tw.DisplayWidth(content)
padLeftChar := padding.Left
if padLeftChar == tw.Empty {
@@ -404,8 +439,8 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
padRightChar = tw.Space
}
padLeftDisplayWidth := twwidth.Width(padLeftChar)
padRightDisplayWidth := twwidth.Width(padRightChar)
padLeftDisplayWidth := tw.DisplayWidth(padLeftChar)
padRightDisplayWidth := tw.DisplayWidth(padRightChar)
spaceForContentAndAlignment := cellVisualWidth - padLeftDisplayWidth - padRightDisplayWidth
if spaceForContentAndAlignment < 0 {
@@ -413,8 +448,8 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
}
if contentDisplayWidth > spaceForContentAndAlignment {
content = twwidth.Truncate(content, spaceForContentAndAlignment)
contentDisplayWidth = twwidth.Width(content)
content = tw.TruncateString(content, spaceForContentAndAlignment)
contentDisplayWidth = tw.DisplayWidth(content)
}
remainingSpace := spaceForContentAndAlignment - contentDisplayWidth
@@ -442,7 +477,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
sb.WriteString(PR)
sb.WriteString(padRightChar)
currentFormattedWidth := twwidth.Width(sb.String())
currentFormattedWidth := tw.DisplayWidth(sb.String())
if currentFormattedWidth < cellVisualWidth {
if align == tw.AlignRight {
prefixSpaces := strings.Repeat(tw.Space, cellVisualWidth-currentFormattedWidth)
@@ -455,7 +490,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
} else if currentFormattedWidth > cellVisualWidth {
tempStr := sb.String()
sb.Reset()
sb.WriteString(twwidth.Truncate(tempStr, cellVisualWidth))
sb.WriteString(tw.TruncateString(tempStr, cellVisualWidth))
o.logger.Warnf("formatCellContent: Final string '%s' (width %d) exceeded target %d. Force truncated.", tempStr, currentFormattedWidth, cellVisualWidth)
}
return sb.String()

View File

@@ -138,7 +138,6 @@ func NewSVG(configs ...SVGConfig) *SVG {
allVisualLineData: make([][][]string, 3),
allVisualLineCtx: make([][]tw.Formatting, 3),
vMergeTrack: make(map[int]int),
logger: ll.New("svg"),
}
for i := 0; i < 3; i++ {
r.allVisualLineData[i] = make([][]string, 0)

View File

@@ -3,7 +3,6 @@ package tablewriter
import (
"fmt"
"github.com/olekukonko/errors"
"github.com/olekukonko/tablewriter/pkg/twwidth"
"github.com/olekukonko/tablewriter/tw"
"math"
)
@@ -221,34 +220,21 @@ func (t *Table) streamAppendRow(row interface{}) error {
}
if err := t.ensureStreamWidthsCalculated(rawCellsSlice, t.config.Row); err != nil {
return fmt.Errorf("failed to establish stream column count/widths: %w", err)
return err
}
// Now, check for column mismatch if a column count has been established.
if t.streamNumCols > 0 {
if len(rawCellsSlice) != t.streamNumCols {
if t.config.Stream.StrictColumns {
err := errors.Newf("input row column count (%d) does not match established stream column count (%d) and StrictColumns is enabled", len(rawCellsSlice), t.streamNumCols)
t.logger.Error(err.Error())
return err
}
// If not strict, retain the old lenient behavior (warn and pad/truncate)
t.logger.Warnf("streamAppendRow: Input row column count (%d) != stream column count (%d). Padding/Truncating (StrictColumns is false).", len(rawCellsSlice), t.streamNumCols)
if len(rawCellsSlice) < t.streamNumCols {
paddedCells := make([]string, t.streamNumCols)
copy(paddedCells, rawCellsSlice)
for i := len(rawCellsSlice); i < t.streamNumCols; i++ {
paddedCells[i] = tw.Empty
}
rawCellsSlice = paddedCells
} else {
rawCellsSlice = rawCellsSlice[:t.streamNumCols]
if t.streamNumCols > 0 && len(rawCellsSlice) != t.streamNumCols {
t.logger.Warnf("streamAppendRow: Input row column count (%d) != stream column count (%d). Padding/Truncating.", len(rawCellsSlice), t.streamNumCols)
if len(rawCellsSlice) < t.streamNumCols {
paddedCells := make([]string, t.streamNumCols)
copy(paddedCells, rawCellsSlice)
for i := len(rawCellsSlice); i < t.streamNumCols; i++ {
paddedCells[i] = tw.Empty
}
rawCellsSlice = paddedCells
} else {
rawCellsSlice = rawCellsSlice[:t.streamNumCols]
}
} else if len(rawCellsSlice) > 0 && t.config.Stream.StrictColumns {
err := errors.Newf("failed to establish stream column count from first data row (%d cells) and StrictColumns is enabled", len(rawCellsSlice))
t.logger.Error(err.Error())
return err
}
if t.streamNumCols == 0 {
@@ -525,7 +511,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
ellipsisWidthBuffer := 0
if autoWrapForWidthCalc == tw.WrapTruncate {
ellipsisWidthBuffer = twwidth.Width(tw.CharEllipsis)
ellipsisWidthBuffer = tw.DisplayWidth(tw.CharEllipsis)
}
varianceBuffer := 2 // Your suggested variance
minTotalColWidth := tw.MinimumColumnWidth
@@ -539,14 +525,14 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
if i < len(sampling) {
sampleContent = t.Trimmer(sampling[i])
}
sampleContentDisplayWidth := twwidth.Width(sampleContent)
sampleContentDisplayWidth := tw.DisplayWidth(sampleContent)
colPad := paddingForWidthCalc.Global
if i < len(paddingForWidthCalc.PerColumn) && paddingForWidthCalc.PerColumn[i].Paddable() {
colPad = paddingForWidthCalc.PerColumn[i]
}
currentPadLWidth := twwidth.Width(colPad.Left)
currentPadRWidth := twwidth.Width(colPad.Right)
currentPadLWidth := tw.DisplayWidth(colPad.Left)
currentPadRWidth := tw.DisplayWidth(colPad.Right)
currentTotalPaddingWidth := currentPadLWidth + currentPadRWidth
// Start with the target content width logic
@@ -609,7 +595,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
if t.renderer != nil {
rendererConfig := t.renderer.Config()
if rendererConfig.Settings.Separators.BetweenColumns.Enabled() {
separatorWidth = twwidth.Width(rendererConfig.Symbols.Column())
separatorWidth = tw.DisplayWidth(rendererConfig.Symbols.Column())
}
} else {
separatorWidth = 1 // Default if renderer not available yet

View File

@@ -7,14 +7,11 @@ import (
"github.com/olekukonko/ll"
"github.com/olekukonko/ll/lh"
"github.com/olekukonko/tablewriter/pkg/twwarp"
"github.com/olekukonko/tablewriter/pkg/twwidth"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"io"
"math"
"os"
"reflect"
"runtime"
"strings"
"sync"
)
@@ -410,14 +407,10 @@ func (t *Table) Options(opts ...Option) *Table {
t.logger.Suspend()
}
// Get additional system information for debugging
goVersion := runtime.Version()
goOS := runtime.GOOS
goArch := runtime.GOARCH
numCPU := runtime.NumCPU()
t.logger.Infof("Environment: LC_CTYPE=%s, LANG=%s, TERM=%s", os.Getenv("LC_CTYPE"), os.Getenv("LANG"), os.Getenv("TERM"))
t.logger.Infof("Go Runtime: Version=%s, OS=%s, Arch=%s, CPUs=%d", goVersion, goOS, goArch, numCPU)
// help resolve from deprecation
//if t.config.Stream.Enable {
// t.config.Widths = t.config.Stream.Widths
//}
// send logger to renderer
// this will overwrite the default logger
@@ -642,7 +635,7 @@ func (t *Table) finalizeHierarchicalMergeBlock(ctx *renderContext, mctx *mergeCo
startState.Hierarchical.Present = true
startState.Hierarchical.Start = true
startState.Hierarchical.Span = finalSpan
startState.Hierarchical.End = finalSpan == 1
startState.Hierarchical.End = (finalSpan == 1)
mctx.rowMerges[startRow][col] = startState
}
@@ -763,7 +756,7 @@ func (t *Table) printTopBottomCaption(w io.Writer, actualTableWidth int) {
captionWrapWidth = t.caption.Width
t.logger.Debugf("[printCaption] Using user-defined caption.Width %d for wrapping.", captionWrapWidth)
} else if actualTableWidth <= 4 {
captionWrapWidth = twwidth.Width(t.caption.Text)
captionWrapWidth = tw.DisplayWidth(t.caption.Text)
t.logger.Debugf("[printCaption] Empty table, no user caption.Width: Using natural caption width %d.", captionWrapWidth)
} else {
captionWrapWidth = actualTableWidth
@@ -886,8 +879,8 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
colPad = config.Padding.PerColumn[i]
}
padLeftWidth := twwidth.Width(colPad.Left)
padRightWidth := twwidth.Width(colPad.Right)
padLeftWidth := tw.DisplayWidth(colPad.Left)
padRightWidth := tw.DisplayWidth(colPad.Right)
effectiveContentMaxWidth := t.calculateContentMaxWidth(i, config, padLeftWidth, padRightWidth, isStreaming)
@@ -909,12 +902,12 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
}
finalLinesForCell = append(finalLinesForCell, wrapped...)
case tw.WrapTruncate:
if twwidth.Width(line) > effectiveContentMaxWidth {
ellipsisWidth := twwidth.Width(tw.CharEllipsis)
if tw.DisplayWidth(line) > effectiveContentMaxWidth {
ellipsisWidth := tw.DisplayWidth(tw.CharEllipsis)
if effectiveContentMaxWidth >= ellipsisWidth {
finalLinesForCell = append(finalLinesForCell, twwidth.Truncate(line, effectiveContentMaxWidth-ellipsisWidth, tw.CharEllipsis))
finalLinesForCell = append(finalLinesForCell, tw.TruncateString(line, effectiveContentMaxWidth-ellipsisWidth, tw.CharEllipsis))
} else {
finalLinesForCell = append(finalLinesForCell, twwidth.Truncate(line, effectiveContentMaxWidth, ""))
finalLinesForCell = append(finalLinesForCell, tw.TruncateString(line, effectiveContentMaxWidth, ""))
}
} else {
finalLinesForCell = append(finalLinesForCell, line)
@@ -922,8 +915,8 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
case tw.WrapBreak:
wrapped := make([]string, 0)
currentLine := line
breakCharWidth := twwidth.Width(tw.CharBreak)
for twwidth.Width(currentLine) > effectiveContentMaxWidth {
breakCharWidth := tw.DisplayWidth(tw.CharBreak)
for tw.DisplayWidth(currentLine) > effectiveContentMaxWidth {
targetWidth := effectiveContentMaxWidth - breakCharWidth
if targetWidth < 0 {
targetWidth = 0
@@ -936,7 +929,7 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
tempWidth := 0
for charIdx, r := range runes {
runeStr := string(r)
rw := twwidth.Width(runeStr)
rw := tw.DisplayWidth(runeStr)
if tempWidth+rw > targetWidth && charIdx > 0 {
break
}
@@ -963,10 +956,10 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
currentLine = string(runes[breakPoint:])
}
}
if twwidth.Width(currentLine) > 0 {
if tw.DisplayWidth(currentLine) > 0 {
wrapped = append(wrapped, currentLine)
}
if len(wrapped) == 0 && twwidth.Width(line) > 0 && len(finalLinesForCell) == 0 {
if len(wrapped) == 0 && tw.DisplayWidth(line) > 0 && len(finalLinesForCell) == 0 {
finalLinesForCell = append(finalLinesForCell, line)
} else {
finalLinesForCell = append(finalLinesForCell, wrapped...)
@@ -1448,7 +1441,7 @@ func (t *Table) render() error {
actualTableWidth := 0
trimmedBuffer := strings.TrimRight(renderedTableContent, "\r\n \t")
for _, line := range strings.Split(trimmedBuffer, "\n") {
w := twwidth.Width(line)
w := tw.DisplayWidth(line)
if w > actualTableWidth {
actualTableWidth = w
}
@@ -1720,7 +1713,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
if j == 0 || representativePadChar == " " {
representativePadChar = padChar
}
padWidth := twwidth.Width(padChar)
padWidth := tw.DisplayWidth(padChar)
if padWidth < 1 {
padWidth = 1
}
@@ -1735,12 +1728,12 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
repeatCount = 0
}
rawPaddingContent := strings.Repeat(padChar, repeatCount)
currentWd := twwidth.Width(rawPaddingContent)
currentWd := tw.DisplayWidth(rawPaddingContent)
if currentWd < colWd {
rawPaddingContent += strings.Repeat(" ", colWd-currentWd)
}
if currentWd > colWd && colWd > 0 {
rawPaddingContent = twwidth.Truncate(rawPaddingContent, colWd)
rawPaddingContent = tw.TruncateString(rawPaddingContent, colWd)
}
if colWd == 0 {
rawPaddingContent = ""

View File

@@ -3,14 +3,146 @@
package tw
import (
"github.com/olekukonko/tablewriter/pkg/twwidth"
"math" // For mathematical operations like ceiling
"strconv" // For string-to-number conversions
"strings" // For string manipulation utilities
"unicode" // For Unicode character classification
"unicode/utf8" // For UTF-8 rune handling
"bytes" // For buffering string output
"github.com/mattn/go-runewidth" // For calculating display width of Unicode characters
"math" // For mathematical operations like ceiling
"regexp" // For regular expression handling of ANSI codes
"strconv" // For string-to-number conversions
"strings" // For string manipulation utilities
"unicode" // For Unicode character classification
"unicode/utf8" // For UTF-8 rune handling
)
// ansi is a compiled regex pattern used to strip ANSI escape codes.
// These codes are used in terminal output for styling and are invisible in rendered text.
var ansi = CompileANSIFilter()
// CompileANSIFilter constructs and compiles a regex for matching ANSI sequences.
// It supports both control sequences (CSI) and operating system commands (OSC) like hyperlinks.
func CompileANSIFilter() *regexp.Regexp {
var regESC = "\x1b" // ASCII escape character
var regBEL = "\x07" // ASCII bell character
// ANSI string terminator: either ESC+\ or BEL
var regST = "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
// Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
var regCSI = regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
// Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
var regOSC = regexp.QuoteMeta(regESC+"]") + ".*?" + regST
// Combine CSI and OSC patterns into a single regex
return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")")
}
// DisplayWidth calculates the visual width of a string, excluding ANSI escape sequences.
// It uses go-runewidth to handle Unicode characters correctly.
func DisplayWidth(str string) int {
// Strip ANSI codes before calculating width to avoid counting invisible characters
return runewidth.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
}
// TruncateString shortens a string to a specified maximum display width while preserving ANSI color codes.
// An optional suffix (e.g., "...") is appended if truncation occurs.
func TruncateString(s string, maxWidth int, suffix ...string) string {
// If maxWidth is 0 or negative, return an empty string
if maxWidth <= 0 {
return ""
}
// Join suffix slices into a single string and calculate its display width
suffixStr := strings.Join(suffix, " ")
suffixDisplayWidth := 0
if len(suffixStr) > 0 {
// Strip ANSI from suffix for accurate width calculation
suffixDisplayWidth = runewidth.StringWidth(ansi.ReplaceAllLiteralString(suffixStr, ""))
}
// Check if the string (without ANSI) plus suffix fits within maxWidth
strippedS := ansi.ReplaceAllLiteralString(s, "")
if runewidth.StringWidth(strippedS)+suffixDisplayWidth <= maxWidth {
// If it fits, return the original string (with ANSI) plus suffix
return s + suffixStr
}
// Handle edge case: maxWidth is too small for even the suffix
if maxWidth < suffixDisplayWidth {
// Try truncating the string without suffix
return TruncateString(s, maxWidth) // Recursive call without suffix
}
// Handle edge case: maxWidth exactly equals suffix width
if maxWidth == suffixDisplayWidth {
if runewidth.StringWidth(strippedS) > 0 {
// If there's content, it's fully truncated; return suffix
return suffixStr
}
return "" // No content and no space for content; return empty string
}
// Calculate the maximum width available for the content (excluding suffix)
targetContentDisplayWidth := maxWidth - suffixDisplayWidth
var contentBuf bytes.Buffer // Buffer for building truncated content
var currentContentDisplayWidth int // Tracks display width of content
var ansiSeqBuf bytes.Buffer // Buffer for collecting ANSI sequences
inAnsiSequence := false // Tracks if we're inside an ANSI sequence
// Iterate over runes to build content while respecting maxWidth
for _, r := range s {
if r == '\x1b' { // Start of ANSI escape sequence
if inAnsiSequence {
// Unexpected new ESC; flush existing sequence
contentBuf.Write(ansiSeqBuf.Bytes())
ansiSeqBuf.Reset()
}
inAnsiSequence = true
ansiSeqBuf.WriteRune(r)
} else if inAnsiSequence {
ansiSeqBuf.WriteRune(r)
// Detect end of common ANSI sequences (e.g., SGR 'm' or CSI terminators)
if r == 'm' || (ansiSeqBuf.Len() > 2 && ansiSeqBuf.Bytes()[1] == '[' && r >= '@' && r <= '~') {
inAnsiSequence = false
contentBuf.Write(ansiSeqBuf.Bytes()) // Append completed sequence
ansiSeqBuf.Reset()
} else if ansiSeqBuf.Len() > 128 { // Prevent buffer overflow for malformed sequences
inAnsiSequence = false
contentBuf.Write(ansiSeqBuf.Bytes())
ansiSeqBuf.Reset()
}
} else {
// Handle displayable characters
runeDisplayWidth := runewidth.RuneWidth(r)
if currentContentDisplayWidth+runeDisplayWidth > targetContentDisplayWidth {
// Adding this rune would exceed the content width; stop here
break
}
contentBuf.WriteRune(r)
currentContentDisplayWidth += runeDisplayWidth
}
}
// Append any unterminated ANSI sequence
if ansiSeqBuf.Len() > 0 {
contentBuf.Write(ansiSeqBuf.Bytes())
}
finalContent := contentBuf.String()
// Append suffix if content was truncated or if suffix is provided and content exists
if runewidth.StringWidth(ansi.ReplaceAllLiteralString(finalContent, "")) < runewidth.StringWidth(strippedS) {
// Content was truncated; append suffix
return finalContent + suffixStr
} else if len(suffixStr) > 0 && len(finalContent) > 0 {
// No truncation but suffix exists; append it
return finalContent + suffixStr
} else if len(suffixStr) > 0 && len(strippedS) == 0 {
// Original string was empty; return suffix
return suffixStr
}
// Return content as is (with preserved ANSI codes)
return finalContent
}
// Title normalizes and uppercases a label string for use in headers.
// It replaces underscores and certain dots with spaces and trims whitespace.
func Title(name string) string {
@@ -40,7 +172,7 @@ func Title(name string) string {
// PadCenter centers a string within a specified width using a padding character.
// Extra padding is split between left and right, with slight preference to left if uneven.
func PadCenter(s, pad string, width int) string {
gap := width - twwidth.Width(s)
gap := width - DisplayWidth(s)
if gap > 0 {
// Calculate left and right padding; ceil ensures left gets extra if gap is odd
gapLeft := int(math.Ceil(float64(gap) / 2))
@@ -53,7 +185,7 @@ func PadCenter(s, pad string, width int) string {
// PadRight left-aligns a string within a specified width, filling remaining space on the right with padding.
func PadRight(s, pad string, width int) string {
gap := width - twwidth.Width(s)
gap := width - DisplayWidth(s)
if gap > 0 {
// Append padding to the right
return s + strings.Repeat(pad, gap)
@@ -64,7 +196,7 @@ func PadRight(s, pad string, width int) string {
// PadLeft right-aligns a string within a specified width, filling remaining space on the left with padding.
func PadLeft(s, pad string, width int) string {
gap := width - twwidth.Width(s)
gap := width - DisplayWidth(s)
if gap > 0 {
// Prepend padding to the left
return strings.Repeat(pad, gap) + s
@@ -76,9 +208,9 @@ func PadLeft(s, pad string, width int) string {
// Pad aligns a string within a specified width using a padding character.
// It truncates if the string is wider than the target width.
func Pad(s string, padChar string, totalWidth int, alignment Align) string {
sDisplayWidth := twwidth.Width(s)
sDisplayWidth := DisplayWidth(s)
if sDisplayWidth > totalWidth {
return twwidth.Truncate(s, totalWidth) // Only truncate if necessary
return TruncateString(s, totalWidth) // Only truncate if necessary
}
switch alignment {
case AlignLeft:
@@ -202,7 +334,7 @@ func BreakPoint(s string, limit int) int {
runeCount := 0
// Iterate over runes, accumulating display width
for _, r := range s {
runeWidth := twwidth.Width(string(r)) // Calculate width of individual rune
runeWidth := DisplayWidth(string(r)) // Calculate width of individual rune
if currentWidth+runeWidth > limit {
// Adding this rune would exceed the limit; breakpoint is before this rune
if currentWidth == 0 {

View File

@@ -118,13 +118,6 @@ type Border struct {
type StreamConfig struct {
Enable bool
// StrictColumns, if true, causes Append() to return an error
// in streaming mode if the number of cells in an appended row
// does not match the established number of columns for the stream.
// If false (default), rows with mismatched column counts will be
// padded or truncated with a warning log.
StrictColumns bool
// Deprecated: Use top-level Config.Widths for streaming width control.
// This field will be removed in a future version. It will be respected if
// Config.Widths is not set and this field is.

View File

@@ -56,7 +56,7 @@ const (
)
const (
SectionHeader = "header"
SectionHeader = "heder"
SectionRow = "row"
SectionFooter = "footer"
)

View File

@@ -4,7 +4,6 @@ import (
"database/sql"
"fmt"
"github.com/olekukonko/errors"
"github.com/olekukonko/tablewriter/pkg/twwidth"
"github.com/olekukonko/tablewriter/tw"
"io"
"math"
@@ -166,7 +165,7 @@ func (t *Table) applyHorizontalMergeWidths(position tw.Position, ctx *renderCont
if t.renderer != nil {
rendererConfig := t.renderer.Config()
if rendererConfig.Settings.Separators.BetweenColumns.Enabled() {
separatorWidth = twwidth.Width(rendererConfig.Symbols.Column())
separatorWidth = tw.DisplayWidth(rendererConfig.Symbols.Column())
}
}
@@ -542,7 +541,7 @@ func (t *Table) buildCoreCellContexts(line []string, merges map[int]tw.MergeStat
// It generates a []string where each element is the padding content for a column, using the specified padChar.
func (t *Table) buildPaddingLineContents(padChar string, widths tw.Mapper[int, int], numCols int, merges map[int]tw.MergeState) []string {
line := make([]string, numCols)
padWidth := twwidth.Width(padChar)
padWidth := tw.DisplayWidth(padChar)
if padWidth < 1 {
padWidth = 1
}
@@ -716,15 +715,15 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
if 0 < len(t.config.Header.Padding.PerColumn) && t.config.Header.Padding.PerColumn[0].Paddable() {
headerCellPadding = t.config.Header.Padding.PerColumn[0]
}
actualMergedHeaderContentPhysicalWidth := twwidth.Width(mergedContentString) +
twwidth.Width(headerCellPadding.Left) +
twwidth.Width(headerCellPadding.Right)
actualMergedHeaderContentPhysicalWidth := tw.DisplayWidth(mergedContentString) +
tw.DisplayWidth(headerCellPadding.Left) +
tw.DisplayWidth(headerCellPadding.Right)
currentSumOfColumnWidths := 0
workingWidths.Each(func(_ int, w int) { currentSumOfColumnWidths += w })
numSeparatorsInFullSpan := 0
if ctx.numCols > 1 {
if t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() {
numSeparatorsInFullSpan = (ctx.numCols - 1) * twwidth.Width(t.renderer.Config().Symbols.Column())
numSeparatorsInFullSpan = (ctx.numCols - 1) * tw.DisplayWidth(t.renderer.Config().Symbols.Column())
}
}
totalCurrentSpanPhysicalWidth := currentSumOfColumnWidths + numSeparatorsInFullSpan
@@ -785,7 +784,7 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
finalWidths.Each(func(_ int, w int) { currentSumOfFinalColWidths += w })
numSeparators := 0
if ctx.numCols > 1 && t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() {
numSeparators = (ctx.numCols - 1) * twwidth.Width(t.renderer.Config().Symbols.Column())
numSeparators = (ctx.numCols - 1) * tw.DisplayWidth(t.renderer.Config().Symbols.Column())
}
totalCurrentTablePhysicalWidth := currentSumOfFinalColWidths + numSeparators
if totalCurrentTablePhysicalWidth > t.config.Widths.Global {
@@ -1656,16 +1655,16 @@ func (t *Table) updateWidths(row []string, widths tw.Mapper[int, int], padding t
t.logger.Debugf(" Col %d: Using global padding: L:'%s' R:'%s'", i, padding.Global.Left, padding.Global.Right)
}
padLeftWidth := twwidth.Width(colPad.Left)
padRightWidth := twwidth.Width(colPad.Right)
padLeftWidth := tw.DisplayWidth(colPad.Left)
padRightWidth := tw.DisplayWidth(colPad.Right)
// Split cell into lines and find maximum content width
lines := strings.Split(cell, tw.NewLine)
contentWidth := 0
for _, line := range lines {
lineWidth := twwidth.Width(line)
lineWidth := tw.DisplayWidth(line)
if t.config.Behavior.TrimSpace.Enabled() {
lineWidth = twwidth.Width(t.Trimmer(line))
lineWidth = tw.DisplayWidth(t.Trimmer(line))
}
if lineWidth > contentWidth {
contentWidth = lineWidth

View File

@@ -5,7 +5,7 @@ Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
## Overview
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [OpenAPI-spec](https://www.openapis.org/) from a remote server, you can easily generate an API client.
- API version: v1.0.8
- API version: v1.0.7
- Package version: 1.0.0
- Generator version: 7.13.0
- Build package: org.openapitools.codegen.languages.GoClientCodegen

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -252,13 +252,6 @@ type ApiGetDriveRequest struct {
ctx context.Context
ApiService *DrivesApiService
driveId string
select_ *[]string
}
// Select properties to be returned. By default all properties are returned.
func (r ApiGetDriveRequest) Select_(select_ []string) ApiGetDriveRequest {
r.select_ = &select_
return r
}
func (r ApiGetDriveRequest) Execute() (*Drive, *http.Response, error) {
@@ -302,9 +295,6 @@ func (a *DrivesApiService) GetDriveExecute(r ApiGetDriveRequest) (*Drive, *http.
localVarQueryParams := url.Values{}
localVarFormParams := url.Values{}
if r.select_ != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "$select", r.select_, "form", "csv")
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -153,7 +153,6 @@ type ApiListAllDrivesBetaRequest struct {
orderby *string
filter *string
expand *string
select_ *[]string
}
// The $orderby system query option allows clients to request resources in either ascending order using asc or descending order using desc.
@@ -174,12 +173,6 @@ func (r ApiListAllDrivesBetaRequest) Expand(expand string) ApiListAllDrivesBetaR
return r
}
// Select properties to be returned. By default all properties are returned.
func (r ApiListAllDrivesBetaRequest) Select_(select_ []string) ApiListAllDrivesBetaRequest {
r.select_ = &select_
return r
}
func (r ApiListAllDrivesBetaRequest) Execute() (*CollectionOfDrives1, *http.Response, error) {
return r.ApiService.ListAllDrivesBetaExecute(r)
}
@@ -227,9 +220,6 @@ func (a *DrivesGetDrivesApiService) ListAllDrivesBetaExecute(r ApiListAllDrivesB
if r.expand != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "$expand", r.expand, "form", "")
}
if r.select_ != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "$select", r.select_, "form", "csv")
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -153,7 +153,6 @@ type ApiListMyDrivesBetaRequest struct {
orderby *string
filter *string
expand *string
select_ *[]string
}
// The $orderby system query option allows clients to request resources in either ascending order using asc or descending order using desc.
@@ -174,12 +173,6 @@ func (r ApiListMyDrivesBetaRequest) Expand(expand string) ApiListMyDrivesBetaReq
return r
}
// Select properties to be returned. By default all properties are returned.
func (r ApiListMyDrivesBetaRequest) Select_(select_ []string) ApiListMyDrivesBetaRequest {
r.select_ = &select_
return r
}
func (r ApiListMyDrivesBetaRequest) Execute() (*CollectionOfDrives, *http.Response, error) {
return r.ApiService.ListMyDrivesBetaExecute(r)
}
@@ -227,9 +220,6 @@ func (a *MeDrivesApiService) ListMyDrivesBetaExecute(r ApiListMyDrivesBetaReques
if r.expand != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "$expand", r.expand, "form", "")
}
if r.select_ != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "$select", r.select_, "form", "csv")
}
// to determine the Content-Type header
localVarHTTPContentTypes := []string{}

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -41,7 +41,7 @@ var (
queryDescape = strings.NewReplacer( "%5B", "[", "%5D", "]" )
)
// APIClient manages communication with the Libre Graph API API vv1.0.8
// APIClient manages communication with the Libre Graph API API vv1.0.7
// In most cases there should be only one, shared, APIClient.
type APIClient struct {
cfg *Configuration

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -50,8 +50,6 @@ type Drive struct {
Root *DriveItem `json:"root,omitempty"`
// A collection of special drive resources.
Special []DriveItem `json:"special,omitempty"`
// Indicates whether the drive has items in the trash. Read-only.
LibreGraphHasTrashedItems *bool `json:"@libre.graph.hasTrashedItems,omitempty"`
}
type _Drive Drive
@@ -610,38 +608,6 @@ func (o *Drive) SetSpecial(v []DriveItem) {
o.Special = v
}
// GetLibreGraphHasTrashedItems returns the LibreGraphHasTrashedItems field value if set, zero value otherwise.
func (o *Drive) GetLibreGraphHasTrashedItems() bool {
if o == nil || IsNil(o.LibreGraphHasTrashedItems) {
var ret bool
return ret
}
return *o.LibreGraphHasTrashedItems
}
// GetLibreGraphHasTrashedItemsOk returns a tuple with the LibreGraphHasTrashedItems field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *Drive) GetLibreGraphHasTrashedItemsOk() (*bool, bool) {
if o == nil || IsNil(o.LibreGraphHasTrashedItems) {
return nil, false
}
return o.LibreGraphHasTrashedItems, true
}
// HasLibreGraphHasTrashedItems returns a boolean if a field has been set.
func (o *Drive) HasLibreGraphHasTrashedItems() bool {
if o != nil && !IsNil(o.LibreGraphHasTrashedItems) {
return true
}
return false
}
// SetLibreGraphHasTrashedItems gets a reference to the given bool and assigns it to the LibreGraphHasTrashedItems field.
func (o *Drive) SetLibreGraphHasTrashedItems(v bool) {
o.LibreGraphHasTrashedItems = &v
}
func (o Drive) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -701,9 +667,6 @@ func (o Drive) ToMap() (map[string]interface{}, error) {
if !IsNil(o.Special) {
toSerialize["special"] = o.Special
}
if !IsNil(o.LibreGraphHasTrashedItems) {
toSerialize["@libre.graph.hasTrashedItems"] = o.LibreGraphHasTrashedItems
}
return toSerialize, nil
}

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.
@@ -48,8 +48,6 @@ type DriveUpdate struct {
Root *DriveItem `json:"root,omitempty"`
// A collection of special drive resources.
Special []DriveItem `json:"special,omitempty"`
// Indicates whether the drive has items in the trash. Read-only.
LibreGraphHasTrashedItems *bool `json:"@libre.graph.hasTrashedItems,omitempty"`
}
// NewDriveUpdate instantiates a new DriveUpdate object
@@ -613,38 +611,6 @@ func (o *DriveUpdate) SetSpecial(v []DriveItem) {
o.Special = v
}
// GetLibreGraphHasTrashedItems returns the LibreGraphHasTrashedItems field value if set, zero value otherwise.
func (o *DriveUpdate) GetLibreGraphHasTrashedItems() bool {
if o == nil || IsNil(o.LibreGraphHasTrashedItems) {
var ret bool
return ret
}
return *o.LibreGraphHasTrashedItems
}
// GetLibreGraphHasTrashedItemsOk returns a tuple with the LibreGraphHasTrashedItems field value if set, nil otherwise
// and a boolean to check if the value has been set.
func (o *DriveUpdate) GetLibreGraphHasTrashedItemsOk() (*bool, bool) {
if o == nil || IsNil(o.LibreGraphHasTrashedItems) {
return nil, false
}
return o.LibreGraphHasTrashedItems, true
}
// HasLibreGraphHasTrashedItems returns a boolean if a field has been set.
func (o *DriveUpdate) HasLibreGraphHasTrashedItems() bool {
if o != nil && !IsNil(o.LibreGraphHasTrashedItems) {
return true
}
return false
}
// SetLibreGraphHasTrashedItems gets a reference to the given bool and assigns it to the LibreGraphHasTrashedItems field.
func (o *DriveUpdate) SetLibreGraphHasTrashedItems(v bool) {
o.LibreGraphHasTrashedItems = &v
}
func (o DriveUpdate) MarshalJSON() ([]byte, error) {
toSerialize,err := o.ToMap()
if err != nil {
@@ -706,9 +672,6 @@ func (o DriveUpdate) ToMap() (map[string]interface{}, error) {
if !IsNil(o.Special) {
toSerialize["special"] = o.Special
}
if !IsNil(o.LibreGraphHasTrashedItems) {
toSerialize["@libre.graph.hasTrashedItems"] = o.LibreGraphHasTrashedItems
}
return toSerialize, nil
}

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

View File

@@ -3,7 +3,7 @@ Libre Graph API
Libre Graph is a free API for cloud collaboration inspired by the MS Graph API.
API version: v1.0.8
API version: v1.0.7
*/
// Code generated by OpenAPI Generator (https://openapi-generator.tech); DO NOT EDIT.

Some files were not shown because too many files have changed in this diff Show More