Skip to content

Commit

Permalink
Merge branch 'main' into dmt
Browse files Browse the repository at this point in the history
# Conflicts:
#	Makefile
#	README.md
#	base/commands/atomic_long/atomic_long_destroy.go
#	base/commands/atomic_long/atomic_long_get.go
#	base/commands/atomic_long/atomic_long_set.go
#	base/commands/atomic_long/common.go
#	base/commands/common.go
#	base/commands/config/config_list.go
#	base/commands/demo/demo_generate_data.go
#	base/commands/demo/demo_it_test.go
#	base/commands/demo/demo_map_set_many.go
#	base/commands/job/common.go
#	base/commands/job/job.go
#	base/commands/job/job_export_snapshot.go
#	base/commands/job/job_list.go
#	base/commands/job/job_resume.go
#	base/commands/job/job_submit.go
#	base/commands/job/job_terminate.go
#	base/commands/list/common.go
#	base/commands/list/list_add.go
#	base/commands/list/list_clear.go
#	base/commands/list/list_contains.go
#	base/commands/list/list_destroy.go
#	base/commands/list/list_remove_index.go
#	base/commands/list/list_remove_value.go
#	base/commands/list/list_set.go
#	base/commands/list/list_size.go
#	base/commands/map/map.go
#	base/commands/map/map_clear.go
#	base/commands/map/map_destroy.go
#	base/commands/map/map_entry_set.go
#	base/commands/map/map_get.go
#	base/commands/map/map_key_set.go
#	base/commands/map/map_load_all.go
#	base/commands/map/map_lock.go
#	base/commands/map/map_remove.go
#	base/commands/map/map_set.go
#	base/commands/map/map_size.go
#	base/commands/map/map_try_lock.go
#	base/commands/map/map_unlock.go
#	base/commands/map/map_values.go
#	base/commands/map_common.go
#	base/commands/multimap/multimap_clear.go
#	base/commands/multimap/multimap_destroy.go
#	base/commands/multimap/multimap_entry_set.go
#	base/commands/multimap/multimap_get.go
#	base/commands/multimap/multimap_key_set.go
#	base/commands/multimap/multimap_lock.go
#	base/commands/multimap/multimap_put.go
#	base/commands/multimap/multimap_remove.go
#	base/commands/multimap/multimap_size.go
#	base/commands/multimap/multimap_try_lock.go
#	base/commands/multimap/multimap_unlock.go
#	base/commands/multimap/multimap_values.go
#	base/commands/object/object.go
#	base/commands/object/object_list.go
#	base/commands/project/project.go
#	base/commands/project/project_create.go
#	base/commands/project/project_list_templates.go
#	base/commands/queue/queue_clear.go
#	base/commands/queue/queue_destroy.go
#	base/commands/queue/queue_offer.go
#	base/commands/queue/queue_poll.go
#	base/commands/queue/queue_size.go
#	base/commands/script.go
#	base/commands/set/set_add.go
#	base/commands/set/set_clear.go
#	base/commands/set/set_destroy.go
#	base/commands/set/set_get_all.go
#	base/commands/set/set_remove.go
#	base/commands/set/set_size.go
#	base/commands/shell.go
#	base/commands/sql/sql.go
#	base/commands/sql/testdata/sql_help.txt
#	base/commands/topic/topic.go
#	base/commands/topic/topic_destroy.go
#	base/commands/topic/topic_publish.go
#	base/commands/topic/topic_subscribe.go
#	base/commands/viridian/custom_class_delete.go
#	base/commands/viridian/custom_class_download.go
#	base/commands/viridian/custom_class_list.go
#	base/commands/viridian/custom_class_upload.go
#	base/commands/viridian/download_logs.go
#	base/commands/viridian/viridian_cluster_create.go
#	base/commands/viridian/viridian_cluster_delete.go
#	base/commands/viridian/viridian_cluster_get.go
#	base/commands/viridian/viridian_cluster_list.go
#	base/commands/viridian/viridian_cluster_resume.go
#	base/commands/viridian/viridian_cluster_stop.go
#	base/commands/viridian/viridian_import_config.go
#	base/commands/viridian/viridian_it_test.go
#	base/commands/viridian/viridian_log_stream.go
#	base/commands/viridian/viridian_login.go
#	clc/cmd/clc.go
#	clc/cmd/command_context.go
#	clc/cmd/exec_context.go
#	clc/cmd/utils.go
#	clc/config/config.go
#	clc/config/config_it_test.go
#	clc/config/file_provider.go
#	clc/config/wizard/input.go
#	clc/config/wizard/list.go
#	clc/config/wizard_provider.go
#	clc/paths/paths.go
#	clc/shell/common.go
#	clc/sql/sql.go
#	clc/store/store.go
#	clc/store/store_test.go
#	clc/ux/stage/stage.go
#	clc/ux/stage/stage_test.go
#	cmd/clc/main.go
#	docs/modules/ROOT/nav.adoc
#	docs/modules/ROOT/pages/clc-demo.adoc
#	docs/modules/ROOT/pages/configuration.adoc
#	docs/modules/ROOT/pages/environment-variables.adoc
#	docs/modules/ROOT/pages/install-clc.adoc
#	errors/error.go
#	extras/unix/install.sh
#	extras/windows/installer/hazelcast-clc-installer.iss
#	go.mod
#	go.sum
#	internal/http/http.go
#	internal/it/context.go
#	internal/it/test_context.go
#	internal/plug/context.go
#	internal/types/types.go
#	internal/types/types_test.go
  • Loading branch information
kmetin committed Sep 28, 2023
2 parents 450da4b + 63940d8 commit a5092d1
Show file tree
Hide file tree
Showing 152 changed files with 2,501 additions and 848 deletions.
5 changes: 2 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

GIT_COMMIT = $(shell git rev-parse HEAD 2> /dev/null || echo unknown)
CLC_VERSION ?= v0.0.0-CUSTOMBUILD
CLC_SKIP_UPDATE_CHECK ?= 0
LDFLAGS = "-s -w -X 'github.com/hazelcast/hazelcast-commandline-client/internal.GitCommit=$(GIT_COMMIT)' -X 'github.com/hazelcast/hazelcast-commandline-client/internal.Version=$(CLC_VERSION)' -X 'github.com/hazelcast/hazelcast-go-client/internal.ClientType=CLC' -X 'github.com/hazelcast/hazelcast-go-client/internal.ClientVersion=$(CLC_VERSION)' -X 'github.com/hazelcast/hazelcast-commandline-client/internal.SkipUpdateCheck=$(CLC_SKIP_UPDATE_CHECK)'"
MAIN_CMD_HELP ?= Hazelcast CLC
LDFLAGS = -s -w -X 'github.com/hazelcast/hazelcast-commandline-client/clc/cmd.MainCommandShortHelp=$(MAIN_CMD_HELP)' -X 'github.com/hazelcast/hazelcast-commandline-client/internal.GitCommit=$(GIT_COMMIT)' -X 'github.com/hazelcast/hazelcast-commandline-client/internal.Version=$(CLC_VERSION)' -X 'github.com/hazelcast/hazelcast-go-client/internal.ClientType=CLC' -X 'github.com/hazelcast/hazelcast-go-client/internal.ClientVersion=$(CLC_VERSION)'
TEST_FLAGS ?= -count 1 -timeout 30m -race
Expand All @@ -17,9 +19,6 @@ TARGZ ?= true
build:
CGO_ENABLED=0 go build -tags base,std,hazelcastinternal,hazelcastinternaltest -ldflags "$(LDFLAGS)" -o build/$(BINARY_NAME) ./cmd/clc

build-dmt:
CGO_ENABLED=0 go build -tags base,migration,config,home,version,hazelcastinternal,hazelcastinternaltest -ldflags "$(LDFLAGS)" -o build/$(BINARY_NAME) ./cmd/clc

test:
go test -tags base,std,hazelcastinternal,hazelcastinternaltest -p 1 $(TEST_FLAGS) ./...

Expand Down
13 changes: 4 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Currently we provide precompiled binaries of CLC for the following platforms and

You can run the following command to install the latest stable CLC on a computer running Linux x64 or macOS 10.15 (Catalina) x64/ARM 64 (M1/M2):
```
curl -sL https://raw.githubusercontent.com/hazelcast/hazelcast-commandline-client/main/extras/unix/install.sh | bash
curl https://hazelcast.com/clc/install.sh | bash
```

On macOS, binaries downloaded outside of AppStore require your intervention to run.
Expand Down Expand Up @@ -58,14 +58,6 @@ This file can exist anywhere in the file system, and can be used with the `--con
$ clc -c test/config.yaml
```

If there is a `config.yaml` in the same directory with the CLC binary and the configuration was not explicitly set, CLC tries to load that configuration file:
```
$ ls -lh
total 17M
-rwxrwxr-x 1 yuce yuce 17M Nov 26 23:11 clc*
-rw------- 1 yuce yuce 200 Nov 26 23:12 config.yaml
```

`configs` directory in `$CLC_HOME` is special, it contains all the configurations known to CLC.
Known configurations can be directly specified by their names, instead of the full path.
`clc config list` command lists the configurations known to CLC:
Expand All @@ -80,6 +72,9 @@ $ clc -c pr-3066
```

If no configuration is specified, the `default` configuration is used if it exists.
The name of the default configuration may be overriden using the `CLC_CONFIG` environment variable.

If there's only a single named configuration, then it is used if the configuration is not specified with `-c`/`--config`.

#### Configuration format

Expand Down
2 changes: 1 addition & 1 deletion base/commands/atomic_long/atomic_long_destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ import (
)

func init() {
c := commands.NewDestroyCommand("AtomicLong", getAtomicLong)
c := commands.NewDestroyCommand("AtomicLong", "atomiclong", getAtomicLong)
check.Must(plug.Registry.RegisterCommand("atomic-long:destroy", c))
}
1 change: 1 addition & 0 deletions base/commands/atomic_long/atomic_long_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func (GetCommand) Exec(ctx context.Context, ec plug.ExecContext) error {
if err != nil {
return nil, err
}
cmd.IncrementClusterMetric(ctx, ec, "total.atomiclong")
sp.SetText(fmt.Sprintf("Getting value of AtomicLong %s", ali.Name()))
val, err := ali.Get(ctx)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions base/commands/atomic_long/atomic_long_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (mc *SetCommand) Exec(ctx context.Context, ec plug.ExecContext) error {
if err != nil {
return nil, err
}
cmd.IncrementClusterMetric(ctx, ec, "total.atomiclong")
sp.SetText(fmt.Sprintf("Setting value of AtomicLong %s", name))
v := ec.GetInt64Arg(base.ArgValue)
err = ali.Set(ctx, v)
Expand Down
1 change: 1 addition & 0 deletions base/commands/atomic_long/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func atomicLongChangeValue(ctx context.Context, ec plug.ExecContext, verb string
if err != nil {
return nil, err
}
cmd.IncrementClusterMetric(ctx, ec, "total.atomiclong")
sp.SetText(fmt.Sprintf("%sing the AtomicLong %s", verb, name))
val, err := ali.AddAndGet(ctx, change(by))
if err != nil {
Expand Down
47 changes: 44 additions & 3 deletions base/commands/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package commands
import (
"context"
"fmt"
"os"
"strings"

"github.com/hazelcast/hazelcast-go-client"

"github.com/hazelcast/hazelcast-commandline-client/base"
_ "github.com/hazelcast/hazelcast-commandline-client/base"
"github.com/hazelcast/hazelcast-commandline-client/clc"
"github.com/hazelcast/hazelcast-commandline-client/clc/cmd"
"github.com/hazelcast/hazelcast-commandline-client/errors"
"github.com/hazelcast/hazelcast-commandline-client/internal"
"github.com/hazelcast/hazelcast-commandline-client/internal/mk"
Expand All @@ -34,12 +36,14 @@ type getDestroyerFunc[T Destroyer] func(context.Context, plug.ExecContext, clc.S

type DestroyCommand[T Destroyer] struct {
typeName string
metricName string
getDestroyerFn getDestroyerFunc[T]
}

func NewDestroyCommand[T Destroyer](typeName string, getFn getDestroyerFunc[T]) *DestroyCommand[T] {
func NewDestroyCommand[T Destroyer](typeName string, metricName string, getFn getDestroyerFunc[T]) *DestroyCommand[T] {
return &DestroyCommand[T]{
typeName: typeName,
metricName: metricName,
getDestroyerFn: getFn,
}
}
Expand Down Expand Up @@ -74,6 +78,7 @@ func (cm DestroyCommand[T]) Exec(ctx context.Context, ec plug.ExecContext) error
if err != nil {
return nil, err
}
cmd.IncrementClusterMetric(ctx, ec, "total."+cm.metricName)
sp.SetText(fmt.Sprintf("Destroying %s '%s'", cm.typeName, name))
if err := m.Destroy(ctx); err != nil {
return nil, err
Expand All @@ -97,12 +102,14 @@ type getClearerFunc[T Clearer] func(context.Context, plug.ExecContext, clc.Spinn

type ClearCommand[T Clearer] struct {
typeName string
metricName string
getClearerFn getClearerFunc[T]
}

func NewClearCommand[T Clearer](typeName string, getFn getClearerFunc[T]) *ClearCommand[T] {
func NewClearCommand[T Clearer](typeName, metricName string, getFn getClearerFunc[T]) *ClearCommand[T] {
return &ClearCommand[T]{
typeName: typeName,
metricName: metricName,
getClearerFn: getFn,
}
}
Expand Down Expand Up @@ -134,6 +141,7 @@ func (cm ClearCommand[T]) Exec(ctx context.Context, ec plug.ExecContext) error {
if err != nil {
return nil, err
}
cmd.IncrementClusterMetric(ctx, ec, "total."+cm.metricName)
sp.SetText(fmt.Sprintf("Clearing %s '%s'", cm.typeName, name))
if err := m.Clear(ctx); err != nil {
return nil, err
Expand All @@ -157,12 +165,14 @@ type getSizerFunc[T Sizer] func(context.Context, plug.ExecContext, clc.Spinner)

type SizeCommand[T Sizer] struct {
typeName string
metricName string
getSizerFn getSizerFunc[T]
}

func NewSizeCommand[T Sizer](typeName string, getFn getSizerFunc[T]) *SizeCommand[T] {
func NewSizeCommand[T Sizer](typeName, metricName string, getFn getSizerFunc[T]) *SizeCommand[T] {
return &SizeCommand[T]{
typeName: typeName,
metricName: metricName,
getSizerFn: getFn,
}
}
Expand All @@ -181,6 +191,7 @@ func (cm SizeCommand[T]) Exec(ctx context.Context, ec plug.ExecContext) error {
if err != nil {
return nil, err
}
cmd.IncrementClusterMetric(ctx, ec, "total."+cm.metricName)
sp.SetText(fmt.Sprintf("Getting the size of %s '%s'", cm.typeName, name))
return m.Size(ctx)
})
Expand All @@ -207,6 +218,28 @@ func AddValueTypeFlag(cc plug.InitContext) {
cc.AddStringFlag(FlagValueType, "v", "string", false, help)
}

func MakeValueFromString(s string, typeStr string) (any, error) {
if typeStr == "" {
typeStr = "string"
}
return mk.ValueFromString(s, typeStr)
}

func MakeValuesFromStrings(typeStr string, items []string) ([]any, error) {
if typeStr == "" {
typeStr = "string"
}
vs := make([]any, len(items))
for i, s := range items {
v, err := mk.ValueFromString(s, typeStr)
if err != nil {
return nil, fmt.Errorf("converting string to key: %w", err)
}
vs[i] = v
}
return vs, nil
}

func MakeKeyData(ec plug.ExecContext, ci *hazelcast.ClientInternal, keyStr string) (hazelcast.Data, error) {
kt := ec.Props().GetString(FlagKeyType)
if kt == "" {
Expand Down Expand Up @@ -260,3 +293,11 @@ func GetTTL(ec plug.ExecContext) int64 {
}
return clc.TTLUnset
}

func IsYes(ec plug.ExecContext) bool {
yes := ec.Props().GetBool(clc.FlagAutoYes)
if yes {
return true
}
return os.Getenv(clc.EnvYes) == "1"
}
2 changes: 1 addition & 1 deletion base/commands/config/config_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func (ListCommand) Exec(ctx context.Context, ec plug.ExecContext) error {
ec.PrintlnUnnecessary("OK No configurations found.")
return nil
}
msg := fmt.Sprintf("OK Found %d configurations.", len(rows))
msg := fmt.Sprintf("OK Found %d configuration(s).", len(rows))
defer ec.PrintlnUnnecessary(msg)
return ec.AddOutputRows(ctx, rows...)
}
Expand Down
42 changes: 24 additions & 18 deletions base/commands/demo/demo_generate_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/hazelcast/hazelcast-go-client"
"github.com/hazelcast/hazelcast-go-client/serialization"

"github.com/hazelcast/hazelcast-commandline-client/base/commands"
"github.com/hazelcast/hazelcast-commandline-client/clc"
"github.com/hazelcast/hazelcast-commandline-client/clc/cmd"
"github.com/hazelcast/hazelcast-commandline-client/clc/sql"
Expand All @@ -21,6 +22,7 @@ import (
"github.com/hazelcast/hazelcast-commandline-client/internal/demo/wikimedia"
"github.com/hazelcast/hazelcast-commandline-client/internal/output"
"github.com/hazelcast/hazelcast-commandline-client/internal/plug"
"github.com/hazelcast/hazelcast-commandline-client/internal/prompt"
)

const (
Expand Down Expand Up @@ -50,6 +52,7 @@ Generate data for given name, supported names are:
cc.SetCommandHelp(long, short)
cc.AddIntFlag(flagMaxValues, "", 0, false, "number of events to create (default: 0, no limits)")
cc.AddBoolFlag(flagPreview, "", false, false, "print the generated data without interacting with the cluster")
cc.AddBoolFlag(clc.FlagAutoYes, "", false, false, "skip confirming the data generation")
cc.AddStringArg(argGeneratorName, argTitleGeneratorName)
cc.AddKeyValueSliceArg(argKeyValues, argTitleKeyValues, 0, clc.MaxArgs)
return nil
Expand All @@ -63,13 +66,28 @@ func (GenerateDataCommand) Exec(ctx context.Context, ec plug.ExecContext) error
}
kvs := ec.GetKeyValuesArg(argKeyValues)
preview := ec.Props().GetBool(flagPreview)
yes := commands.IsYes(ec)
if !yes {
p := prompt.New(ec.Stdin(), ec.Stdout())
ec.PrintlnUnnecessary("The data is streamed from Wikipedia changes.")
ec.PrintlnUnnecessary("Hazelcast has no control over the incoming data,")
yes, err := p.YesNo("Proceed?")
if err != nil {
ec.Logger().Info("User input could not be processed due to error: %s", err.Error())
return hzerrors.ErrUserCancelled
}
if !yes {
return hzerrors.ErrUserCancelled
}
}
if preview {
return generatePreviewResult(ctx, ec, generator, kvs.Map())
}
return generateResult(ctx, ec, generator, kvs.Map())
}

func generatePreviewResult(ctx context.Context, ec plug.ExecContext, generator dataStreamGenerator, keyVals map[string]string) error {
cmd.IncrementMetric(ctx, ec, "total.demo")
maxCount := ec.Props().GetInt(flagMaxValues)
if maxCount < 1 {
maxCount = 10
Expand Down Expand Up @@ -104,15 +122,16 @@ func generateResult(ctx context.Context, ec plug.ExecContext, generator dataStre
return fmt.Errorf("either %s key-value pair must be given or --preview must be used", pairMapName)
}
maxCount := ec.Props().GetInt(flagMaxValues)
query, err := generator.GenerateMappingQuery(mapName)
if err != nil {
return err
}
query, stop, err := cmd.ExecuteBlocking(ctx, ec, func(ctx context.Context, sp clc.Spinner) (string, error) {
sp.SetText("Creating the mapping")
query, err := generator.GenerateMappingQuery(mapName)
if err != nil {
return "", err
}
if _, err := sql.ExecSQL(ctx, ec, query); err != nil {
return "", err
}
cmd.IncrementClusterMetric(ctx, ec, "total.demo")
return query, nil
})
if err != nil {
Expand All @@ -133,7 +152,7 @@ func generateResult(ctx context.Context, ec plug.ExecContext, generator dataStre
errCh := make(chan error)
itemCh, stopStream := generator.Stream(ctx)
defer stopStream()
ci, err := ec.ClientInternal(ctx)
ci, err := cmd.ClientInternal(ctx, ec, sp)
if err != nil {
return 0, err
}
Expand Down Expand Up @@ -232,19 +251,6 @@ var supportedEventStreams = map[string]dataStreamGenerator{
"wikipedia-event-stream": wikimedia.StreamGenerator{},
}

func getMap(ctx context.Context, ec plug.ExecContext, sp clc.Spinner, mapName string) (*hazelcast.Map, error) {
ci, err := cmd.ClientInternal(ctx, ec, sp)
if err != nil {
return nil, err
}
sp.SetText(fmt.Sprintf("Getting Map '%s'", mapName))
m, err := ci.Client().GetMap(ctx, mapName)
if err != nil {
return nil, err
}
return m, nil
}

func init() {
check.Must(plug.Registry.RegisterCommand("demo:generate-data", &GenerateDataCommand{}))
}
4 changes: 2 additions & 2 deletions base/commands/demo/demo_it_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func generateData_WikipediaTest(t *testing.T) {
t := tcx.T
ctx := context.Background()
tcx.WithReset(func() {
err := tcx.CLCExecuteErr(ctx, "demo", "generate-data", "wikipedia-event-stream", "map="+m.Name(), "--timeout", "2s")
err := tcx.CLCExecuteErr(ctx, "demo", "generate-data", "wikipedia-event-stream", "map="+m.Name(), "--timeout", "2s", "--yes")
require.Error(t, err)
size := check.MustValue(m.Size(context.Background()))
require.Greater(t, size, 0)
Expand All @@ -48,7 +48,7 @@ func generateData_Wikipedia_MaxValues_Test(t *testing.T) {
ctx := context.Background()
count := 10
tcx.WithReset(func() {
tcx.CLCExecute(ctx, "demo", "generate-data", "wikipedia-event-stream", "map="+m.Name(), fmt.Sprintf("--max-values=%d", count))
tcx.CLCExecute(ctx, "demo", "generate-data", "wikipedia-event-stream", "map="+m.Name(), fmt.Sprintf("--max-values=%d", count), "--yes")
size := check.MustValue(m.Size(context.Background()))
require.Equal(t, count, size)
})
Expand Down
5 changes: 3 additions & 2 deletions base/commands/demo/demo_map_set_many.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

"github.com/hazelcast/hazelcast-commandline-client/clc"
"github.com/hazelcast/hazelcast-commandline-client/clc/cmd"
. "github.com/hazelcast/hazelcast-commandline-client/internal/check"
"github.com/hazelcast/hazelcast-commandline-client/internal/check"
"github.com/hazelcast/hazelcast-commandline-client/internal/plug"
)

Expand Down Expand Up @@ -48,6 +48,7 @@ func (m MapSetManyCmd) Exec(ctx context.Context, ec plug.ExecContext) error {
if err != nil {
return nil, err
}
cmd.IncrementClusterMetric(ctx, ec, "total.demo")
sp.SetText(fmt.Sprintf("Creating entries in map %s with %d entries", mapName, count))
mm, err := ci.Client().GetMap(ctx, mapName)
if err != nil {
Expand Down Expand Up @@ -113,5 +114,5 @@ func getValueSize(sizeStr string) (int64, error) {
}

func init() {
Must(plug.Registry.RegisterCommand("demo:map-setmany", &MapSetManyCmd{}))
check.Must(plug.Registry.RegisterCommand("demo:map-setmany", &MapSetManyCmd{}))
}
Loading

0 comments on commit a5092d1

Please sign in to comment.