Skip to content

Commit

Permalink
feat: uniform kcl cli
Browse files Browse the repository at this point in the history
Signed-off-by: zongz <[email protected]>
  • Loading branch information
zong-zhe committed Jun 5, 2024
1 parent 45415db commit f384a5f
Show file tree
Hide file tree
Showing 110 changed files with 261 additions and 83 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ build/
_kcl_test.k

.DS_store

# e2e test cases dir
scripts/e2e/pkg_in_reg/*
scripts/e2e/registry_auth/*
2 changes: 2 additions & 0 deletions cmd/kcl/commands/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ var (
vendor bool
update bool
git string
oci string
path string
tag string
commit string
branch string
Expand Down
155 changes: 98 additions & 57 deletions cmd/kcl/commands/mod_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package cmd

import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
"kcl-lang.io/kpm/pkg/client"
Expand All @@ -24,11 +24,23 @@ const (
# Add the module dependency named "k8s" with the version "1.28"
kcl mod add k8s:1.28
# Add the module dependency from the GitHub
# Add the module dependency from the GitHub by git url
kcl mod add git://github.com/kcl-lang/konfig --tag v0.4.0
# Add the module dependency from the OCI Registry by oci url
kcl mod add oci://github.com/kcl-lang/konfig --tag v0.4.0
# Add the module dependency from the local file system by file url
kcl mod add /path/to/another_module
# Add the module dependency from the GitHub by flag
kcl mod add --git https://github.com/kcl-lang/konfig --tag v0.4.0
# Add a local dependency
kcl mod add /path/to/another_module`
# Add the module dependency from the OCI Registry by flag
kcl mod add --oci https://ghcr.io/kcl-lang/helloworld --tag 0.1.0
# Add a local dependency by flag
kcl mod add --path /path/to/another_module`
)

// NewModAddCmd returns the mod add command.
Expand All @@ -45,7 +57,8 @@ func NewModAddCmd(cli *client.KpmClient) *cobra.Command {
}

cmd.Flags().StringVar(&git, "git", "", "git repository url")
cmd.Flags().StringVar(&tag, "tag", "", "git repository tag")
cmd.Flags().StringVar(&oci, "oci", "", "oci repository url")
cmd.Flags().StringVar(&tag, "tag", "", "git or oci repository tag")
cmd.Flags().StringVar(&commit, "commit", "", "git repository commit")
cmd.Flags().StringVar(&branch, "branch", "", "git repository branch")
cmd.Flags().StringVar(&rename, "rename", "", "rename the dependency")
Expand Down Expand Up @@ -122,31 +135,94 @@ func ModAdd(cli *client.KpmClient, args []string) error {

// parseAddOptions will parse the user cli inputs.
func parseAddOptions(cli *client.KpmClient, localPath string, args []string) (*opt.AddOptions, error) {
// parse the CLI command with the following style
// kcl mod add --git https://xxx/xxx --tag 0.0.1
// kcl mod add --oci https://xxx/xxx --tag 0.0.1
// kcl mod add --path /path/to/xxx
if len(args) == 0 {
return &opt.AddOptions{
LocalPath: localPath,
RegistryOpts: opt.RegistryOptions{
Git: &opt.GitOptions{
Url: git,
Tag: tag,
Commit: commit,
Branch: branch,
},
},
NoSumCheck: noSumCheck,
NewPkgName: rename,
}, nil
if len(git) != 0 {
gitUrl, err := url.Parse(git)
if err != nil {
return nil, err
}
gitOpt := opt.NewGitOptionsFromUrl(gitUrl)
if gitOpt == nil {
return nil, fmt.Errorf("invalid git url '%s'", git)
}

gitOpt.Tag = tag
gitOpt.Commit = commit
gitOpt.Branch = branch

return &opt.AddOptions{
LocalPath: localPath,
RegistryOpts: opt.RegistryOptions{Git: gitOpt},
NoSumCheck: noSumCheck,
NewPkgName: rename,
}, nil
} else if len(oci) != 0 {
ociUrl, err := url.Parse(oci)
if err != nil {
return nil, err
}
ociOpt := opt.NewOciOptionsFromUrl(ociUrl)
if ociOpt == nil {
return nil, fmt.Errorf("invalid oci url '%s'", oci)
}
ociOpt.Tag = tag

return &opt.AddOptions{
LocalPath: localPath,
RegistryOpts: opt.RegistryOptions{Oci: ociOpt},
NoSumCheck: noSumCheck,
NewPkgName: rename,
}, nil
} else if len(path) != 0 {
pathUrl, err := url.Parse(path)
if err != nil {
return nil, err
}

pathOpt, err := opt.NewLocalOptionsFromUrl(pathUrl)
if err != (*reporter.KpmEvent)(nil) {
return nil, err
}

return &opt.AddOptions{
LocalPath: localPath,
RegistryOpts: opt.RegistryOptions{Local: pathOpt},
NoSumCheck: noSumCheck,
NewPkgName: rename,
}, nil
}
} else {
// parse the CLI command with the following style
// kcl mod add k8s
// kcl mod add k8s:0.0.1
// kcl mod add /path/to/xxx
// kcl mod add https://xxx/xxx --tag 0.0.1
// kcl mod add oci://xxx/xxx --tag 0.0.1

localPkg, err := parseLocalPathOptions(args)
pkgSource := argsGet(args, 0)
if err != (*reporter.KpmEvent)(nil) {
// parse from 'kpm add xxx:0.0.1'.
ociReg, err := parseOciRegistryOptions(cli, args)
// parse url and ref
regOpt, err := opt.NewRegistryOptionsFrom(pkgSource, cli.GetSettings())
if err != nil {
return nil, err
}

if regOpt.Git != nil {
regOpt.Git.Tag = tag
regOpt.Git.Commit = commit
regOpt.Git.Branch = branch
} else if regOpt.Oci != nil && len(tag) != 0 {
regOpt.Oci.Tag = tag
}

return &opt.AddOptions{
LocalPath: localPath,
RegistryOpts: *ociReg,
RegistryOpts: *regOpt,
NoSumCheck: noSumCheck,
NewPkgName: rename,
}, nil
Expand All @@ -159,24 +235,8 @@ func parseAddOptions(cli *client.KpmClient, localPath string, args []string) (*o
}, nil
}
}
}

// parseOciRegistryOptions will parse the oci registry information from user cli inputs.
func parseOciRegistryOptions(cli *client.KpmClient, args []string) (*opt.RegistryOptions, error) {
ociPkgRef := argsGet(args, 0)
name, version, err := parseOciPkgNameAndVersion(ociPkgRef)
if err != nil {
return nil, err
}

return &opt.RegistryOptions{
Oci: &opt.OciOptions{
Reg: cli.GetSettings().DefaultOciRegistry(),
Repo: cli.GetSettings().DefaultOciRepo(),
PkgName: name,
Tag: version,
},
}, nil
return nil, fmt.Errorf("invalid add options")
}

// parseLocalPathOptions will parse the local path information from user cli inputs.
Expand All @@ -196,22 +256,3 @@ func parseLocalPathOptions(args []string) (*opt.RegistryOptions, *reporter.KpmEv
}, nil
}
}

// parseOciPkgNameAndVersion will parse package name and version
// from string "<pkg_name>:<pkg_version>".
func parseOciPkgNameAndVersion(s string) (string, string, error) {
parts := strings.Split(s, ":")
if len(parts) == 1 {
return parts[0], "", nil
}

if len(parts) > 2 {
return "", "", reporter.NewErrorEvent(reporter.InvalidPkgRef, fmt.Errorf("invalid oci package reference '%s'", s))
}

if parts[1] == "" {
return "", "", reporter.NewErrorEvent(reporter.InvalidPkgRef, fmt.Errorf("invalid oci package reference '%s'", s))
}

return parts[0], parts[1], nil
}
6 changes: 3 additions & 3 deletions cmd/kcl/commands/mod_push.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/spf13/cobra"
"kcl-lang.io/kpm/pkg/client"
"kcl-lang.io/kpm/pkg/errors"
"kcl-lang.io/kpm/pkg/oci"
kpmoci "kcl-lang.io/kpm/pkg/oci"
"kcl-lang.io/kpm/pkg/opt"
pkg "kcl-lang.io/kpm/pkg/package"
"kcl-lang.io/kpm/pkg/reporter"
Expand Down Expand Up @@ -72,7 +72,7 @@ func genDefaultOciUrlForKclPkg(pkg *pkg.KclPkg, cli *client.KpmClient) (string,
urlPath := utils.JoinPath(cli.GetSettings().DefaultOciRepo(), pkg.GetPkgName())

u := &url.URL{
Scheme: oci.OCI_SCHEME,
Scheme: kpmoci.OCI_SCHEME,
Host: cli.GetSettings().DefaultOciRegistry(),
Path: urlPath,
}
Expand Down Expand Up @@ -169,7 +169,7 @@ func pushPackage(ociUrl string, kclPkg *pkg.KclPkg, vendorMode bool, cli *client
"only support url scheme 'oci://'.",
)
}
ociOpts.Annotations, err = oci.GenOciManifestFromPkg(kclPkg)
ociOpts.Annotations, err = kpmoci.GenOciManifestFromPkg(kclPkg)
if err != nil {
return err
}
Expand Down
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
kcl-lang.io/kcl-go v0.9.0-beta.1
kcl-lang.io/kcl-openapi v0.6.1
kcl-lang.io/kcl-playground v0.5.1
kcl-lang.io/kpm v0.9.0-beta.1
kcl-lang.io/kpm v0.9.0-beta.1.0.20240605035613-c5c5085800b4
)

require (
Expand All @@ -29,6 +29,7 @@ require (
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/containers/ocicrypt v1.1.10 // indirect
github.com/containers/storage v1.54.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/emicklei/proto v1.13.2 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
Expand All @@ -52,9 +53,12 @@ require (
github.com/moby/sys/user v0.1.0 // indirect
github.com/opencontainers/runtime-spec v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/urfave/cli/v2 v2.25.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
Expand Down
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
Expand Down Expand Up @@ -867,7 +868,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
Expand Down Expand Up @@ -934,13 +937,18 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk=
github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8=
github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down Expand Up @@ -1685,6 +1693,14 @@ kcl-lang.io/kcl-playground v0.5.1 h1:MKQQUHgt4+2QyU2NVwa73oksOaBJGDi4keGoggA0MiU
kcl-lang.io/kcl-playground v0.5.1/go.mod h1:IFmnlw7m011ccX8OidMUfnnN2u/TWdtQGxyABRTbmow=
kcl-lang.io/kpm v0.9.0-beta.1 h1:buc7yp6RR3MJPG0Un6IkkCpQaVPF8LnwgZtluY0a6dA=
kcl-lang.io/kpm v0.9.0-beta.1/go.mod h1:QJNbc1+go6OXAhiiBHJ7GAjYJ1AC5wLmKAmDS9o5n+k=
kcl-lang.io/kpm v0.9.0-beta.1.0.20240530104317-7a35562e78a2 h1:c/xCUaE/5rnN4FQjgh+Hd0QGl2KmeWxuibgtsjr+y40=
kcl-lang.io/kpm v0.9.0-beta.1.0.20240530104317-7a35562e78a2/go.mod h1:QJNbc1+go6OXAhiiBHJ7GAjYJ1AC5wLmKAmDS9o5n+k=
kcl-lang.io/kpm v0.9.0-beta.1.0.20240604083117-d7258bd4bbf0 h1:t5Z8qa9DzZVG8yLR95rYTOeWhFjUZi+aa06Oxw27jk0=
kcl-lang.io/kpm v0.9.0-beta.1.0.20240604083117-d7258bd4bbf0/go.mod h1:QJNbc1+go6OXAhiiBHJ7GAjYJ1AC5wLmKAmDS9o5n+k=
kcl-lang.io/kpm v0.9.0-beta.1.0.20240604093242-4244558de99a h1:2Y/Ek+TgDFMzq2dlbiRNAc2LXgeOByzWRPSzTSasFjE=
kcl-lang.io/kpm v0.9.0-beta.1.0.20240604093242-4244558de99a/go.mod h1:QJNbc1+go6OXAhiiBHJ7GAjYJ1AC5wLmKAmDS9o5n+k=
kcl-lang.io/kpm v0.9.0-beta.1.0.20240605035613-c5c5085800b4 h1:G4hkxgAQcxYt5vOIfma944KFLcMi6da7b8SnDRw9Cmg=
kcl-lang.io/kpm v0.9.0-beta.1.0.20240605035613-c5c5085800b4/go.mod h1:QJNbc1+go6OXAhiiBHJ7GAjYJ1AC5wLmKAmDS9o5n+k=
kcl-lang.io/lib v0.9.0-beta.1 h1:oO/3h5Ubk61LKVnE7A9xlV4FfSfKa6Jv+KNsoHMcnNc=
kcl-lang.io/lib v0.9.0-beta.1/go.mod h1:ubsalGXxJaa5II/EsHmsI/tL2EluYHIcW+BwzQPt+uY=
oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo=
Expand Down
2 changes: 1 addition & 1 deletion scripts/e2e/push_pkg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ $current_dir/bin/kcl mod push
cd "$current_dir"

# Push the package helloworld/0.1.1 to the registry
cd ./scripts/pkg_in_reg/ghcr.io/kcl-lang/helloworld/0.1.1
cd ./scripts/e2e/pkg_in_reg/ghcr.io/kcl-lang/helloworld/0.1.1
$current_dir/bin/kcl mod push

cd "$current_dir"
Expand Down
2 changes: 0 additions & 2 deletions scripts/e2e/registry_auth/htpasswd

This file was deleted.

5 changes: 1 addition & 4 deletions test/e2e/test_suites/test_kcl_mod_add_git/stdout
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
adding dependency 'konfig'
cloning 'https://github.com/kcl-lang/konfig' with tag 'v0.4.0'
downloading 'test/k8s:1.28' from 'localhost:5001/test/k8s:1.28'
add dependency 'konfig:v0.4.0' successfully
add dependency 'konfig:v0.4.0' successfully
1 change: 1 addition & 0 deletions test/e2e/test_suites/test_kcl_mod_add_git_1/input
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kcl mod add --git https://github.com/kcl-lang/konfig --branch main
Empty file.
1 change: 1 addition & 0 deletions test/e2e/test_suites/test_kcl_mod_add_git_1/stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add dependency 'konfig:main' successfully
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "test_space"
edition = "v0.9.0"
version = "0.0.1"

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The_first_kcl_program = 'Hello World!'
1 change: 1 addition & 0 deletions test/e2e/test_suites/test_kcl_mod_add_git_2/input
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kcl mod add --git https://github.com/kcl-lang/konfig --commit 01ca24c
Empty file.
1 change: 1 addition & 0 deletions test/e2e/test_suites/test_kcl_mod_add_git_2/stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add dependency 'konfig:01ca24c' successfully
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "test_space"
edition = "v0.9.0"
version = "0.0.1"

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The_first_kcl_program = 'Hello World!'
1 change: 1 addition & 0 deletions test/e2e/test_suites/test_kcl_mod_add_git_3/input
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kcl mod add --git ssh://github.com/kcl-lang/konfig --commit 01ca24c
Empty file.
1 change: 1 addition & 0 deletions test/e2e/test_suites/test_kcl_mod_add_git_3/stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add dependency 'konfig:01ca24c' successfully
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "test_space"
edition = "v0.9.0"
version = "0.0.1"

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The_first_kcl_program = 'Hello World!'
Loading

0 comments on commit f384a5f

Please sign in to comment.