6 Commits

Author SHA1 Message Date
98999336dc remove existing 2023-01-21 15:02:04 +01:00
cb3c15abbc add readme
All checks were successful
continuous-integration/drone/push Build is passing
2022-12-29 02:59:32 +01:00
a4c39c00d0 Update module github.com/hashicorp/go-plugin to v1.4.6
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-11-08 19:06:31 +00:00
cb37c9556e Update module github.com/stretchr/testify to v1.8.1
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-11-06 16:11:20 +00:00
c8fd91ddbb Update all dependencies
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2022-11-06 15:55:16 +00:00
3b2cb129df Add drone
All checks were successful
continuous-integration/drone Build is passing
2022-11-06 16:40:43 +01:00
32 changed files with 0 additions and 1490 deletions

1
.gitignore vendored
View File

@@ -1 +0,0 @@
dist/

View File

@@ -1,10 +0,0 @@
# Char
```yaml
# file: .char.yml
registry: git.front.kjuulh.io
plugins:
"kjuulh/char#/plugins/gocli":
vars:
name: "char"
```

View File

@@ -1,67 +0,0 @@
package char
import (
"context"
"log"
"git.front.kjuulh.io/kjuulh/char/pkg/charcontext"
"github.com/spf13/cobra"
)
type RequiredArg struct {
Required bool
Value string
}
func NewDoCommand(charctx *charcontext.CharContext) *cobra.Command {
cmd := &cobra.Command{
Use: "do",
}
about, err := charctx.About(context.Background())
if err != nil {
log.Fatal(err)
}
for _, a := range about {
for _, c := range a.Commands {
requiredArgs := make(map[string]*RequiredArg, len(c.Args))
for _, arg := range c.Args {
requiredArgs[arg] = &RequiredArg{
Required: false,
}
}
for _, required := range c.Required {
if _, ok := requiredArgs[required]; ok {
requiredArg := requiredArgs[required]
requiredArg.Required = true
}
}
doCmd := &cobra.Command{
Use: c.Name,
RunE: func(cmd *cobra.Command, args []string) error {
if err := cmd.ParseFlags(args); err != nil {
return err
}
if err := charctx.Do(cmd.Context(), a.ClientName, c.Name, nil); err != nil {
return err
}
return nil
},
}
for argName, argValue := range requiredArgs {
doCmd.PersistentFlags().StringVar(&argValue.Value, argName, "", "")
if argValue.Required {
doCmd.MarkPersistentFlagRequired(argName)
}
}
cmd.AddCommand(doCmd)
}
}
return cmd
}

View File

@@ -1,13 +0,0 @@
package char
import (
"github.com/spf13/cobra"
)
func NewLimitedCharCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "char",
}
return cmd
}

View File

@@ -1,64 +0,0 @@
package char
import (
"fmt"
"git.front.kjuulh.io/kjuulh/char/pkg/charcontext"
"github.com/spf13/cobra"
)
func NewLsCommand(charctx *charcontext.CharContext) *cobra.Command {
cmd := &cobra.Command{
Use: "ls",
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
about, err := charctx.About(ctx)
if err != nil {
return err
}
for _, a := range about {
fmt.Printf("plugin: %s\n", a.Name)
fmt.Printf("\tversion: %s\n", a.Version)
fmt.Printf("\tabout: %s\n", a.About)
if len(a.Vars) > 0 {
fmt.Println("\tVars:")
for _, av := range a.Vars {
fmt.Printf("\t\t%s\n", av)
}
}
if len(a.Commands) > 0 {
fmt.Println("\tCommands:")
for _, ac := range a.Commands {
fmt.Printf("\t\t%s\n", ac.Name)
if len(ac.Args) == 0 {
continue
}
fmt.Println("\t\tArgs")
for _, aca := range ac.Args {
isrequired := false
for _, acr := range ac.Required {
if acr == aca {
isrequired = true
}
}
if isrequired {
fmt.Printf("\t\t\t%s: required\n", aca)
} else {
fmt.Printf("\t\t\t%s\n", aca)
}
}
}
}
fmt.Println()
}
return nil
},
}
return cmd
}

View File

@@ -1,19 +0,0 @@
package char
import (
"git.front.kjuulh.io/kjuulh/char/pkg/charcontext"
"github.com/spf13/cobra"
)
func NewCharCmd(charctx *charcontext.CharContext) *cobra.Command {
cmd := &cobra.Command{
Use: "char",
}
cmd.AddCommand(
NewLsCommand(charctx),
NewDoCommand(charctx),
)
return cmd
}

View File

@@ -1,4 +0,0 @@
registry: git.front.kjuulh.io
plugins:
"kjuulh/char#plugins/gocli": {}
"kjuulh/char#plugins/rust": {}

View File

@@ -1,2 +0,0 @@
.char/plugins/
char

View File

@@ -1,21 +0,0 @@
#!/bin/bash
set -e
go build -o char ../../main.go
function devcharls() {
CHAR_DEV_MODE=true ./char ls 2&> /dev/null
}
function charls() {
./char ls 2&> /dev/null
}
echo "scratch"
time devcharls
echo ""
echo "ready"
time charls
echo ""

View File

@@ -1,7 +0,0 @@
#!/bin/bash
set -e
go build -o char ../../main.go
CHAR_DEV_MODE=true ./char ls

View File

@@ -1,13 +0,0 @@
#!/bin/bash
set -e
go build -o char ../../main.go
echo "base"
CHAR_DEV_MODE=true ./char do -h
echo
echo "--------"
echo "local_up"
CHAR_DEV_MODE=false ./char do local_up --fish something

27
go.mod
View File

@@ -1,27 +0,0 @@
module git.front.kjuulh.io/kjuulh/char
go 1.19
require (
github.com/hashicorp/go-hclog v0.14.1
github.com/hashicorp/go-plugin v1.4.5
github.com/spf13/cobra v1.6.1
golang.org/x/sync v0.0.0-20190423024810-112230192c58
)
require (
github.com/fatih/color v1.7.0 // indirect
github.com/golang/protobuf v1.3.4 // indirect
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
golang.org/x/sys v0.0.0-20191008105621-543471e840be // indirect
golang.org/x/text v0.3.0 // indirect
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect
google.golang.org/grpc v1.27.1 // indirect
)

87
go.sum
View File

@@ -1,87 +0,0 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-plugin v1.4.5 h1:oTE/oQR4eghggRg8VY7PAz3dr++VwDNBGCcOfIvHpBo=
github.com/hashicorp/go-plugin v1.4.5/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M=
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@@ -1,3 +0,0 @@
go 1.19
use .

View File

@@ -1 +0,0 @@
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

29
main.go
View File

@@ -1,29 +0,0 @@
package main
import (
"context"
"errors"
"log"
"git.front.kjuulh.io/kjuulh/char/cmd/char"
"git.front.kjuulh.io/kjuulh/char/pkg/charcontext"
)
func main() {
charctx, err := charcontext.NewCharContext(context.Background())
if err != nil {
if errors.Is(err, charcontext.ErrNoContextFound) {
log.Print("you are not in a char context, as such you will be presented with limited options")
if err := char.NewLimitedCharCmd().Execute(); err != nil {
log.Fatal(err)
}
} else {
log.Fatal(err)
}
}
defer charctx.Close()
if err := char.NewCharCmd(charctx).Execute(); err != nil {
log.Fatal(err)
}
}

View File

@@ -1,70 +0,0 @@
package charcontext
import (
"context"
"log"
"git.front.kjuulh.io/kjuulh/char/pkg/plugins/provider"
"git.front.kjuulh.io/kjuulh/char/pkg/register"
"git.front.kjuulh.io/kjuulh/char/pkg/schema"
)
type CharContext struct {
contextPath string
pluginRegister *register.PluginRegister
schema *schema.CharSchema
}
func NewCharContext(ctx context.Context) (*CharContext, error) {
localPath, err := FindLocalRoot(ctx)
if err != nil {
return nil, err
}
gpp := provider.NewGitPluginProvider()
s, err := schema.ParseFile(ctx, ".char.yml")
if err != nil {
return nil, err
}
plugins, err := s.GetPlugins(ctx)
if err != nil {
return nil, err
}
err = gpp.FetchPlugins(ctx, s.Registry, plugins)
if err != nil {
return nil, err
}
builder := register.NewPluginRegisterBuilder()
for name, plugin := range plugins {
builder = builder.Add(name.Hash(), plugin.Opts.Path)
}
r, err := builder.Build(ctx)
if err != nil {
return nil, err
}
return &CharContext{
contextPath: localPath,
pluginRegister: r,
schema: s,
}, nil
}
func (cc *CharContext) Close() {
if err := cc.pluginRegister.Close(); err != nil {
log.Fatal(err)
}
}
func (cc *CharContext) About(ctx context.Context) ([]register.AboutItem, error) {
return cc.pluginRegister.About(ctx)
}
func (cc *CharContext) Do(ctx context.Context, clientName string, commandName string, args map[string]string) error {
return cc.pluginRegister.Do(ctx, clientName, commandName, args)
}

View File

@@ -1,57 +0,0 @@
package charcontext
import (
"context"
"errors"
"os"
"path"
)
var ErrNoContextFound = errors.New("could not find project root")
const CharFileName = ".char.yml"
func FindLocalRoot(ctx context.Context) (string, error) {
curdir, err := os.Getwd()
if err != nil {
return "", err
}
return recursiveFindLocalRoot(ctx, curdir)
//output, err := exec.Command("git", "rev-parse", "--show-toplevel").CombinedOutput()
//if err != nil {
// return "", err
//}
//if len(output) == 0 {
// return "", errors.New("could not find absolute path")
//}
//if _, err := os.Stat(string(output)); errors.Is(err, os.ErrNotExist) {
// return "", fmt.Errorf("path does not exist %s", string(output))
//}
//return string(output), nil
}
func recursiveFindLocalRoot(ctx context.Context, localpath string) (string, error) {
entries, err := os.ReadDir(localpath)
if err != nil {
return "", err
}
for _, entry := range entries {
if entry.Name() == CharFileName {
return localpath, nil
}
}
if localpath == "/" {
return "", ErrNoContextFound
}
return recursiveFindLocalRoot(ctx, path.Dir(localpath))
}
func ChangeToPath(_ context.Context, path string) error {
return os.Chdir(path)
}

View File

@@ -1,107 +0,0 @@
package provider
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/exec"
"strings"
"time"
"git.front.kjuulh.io/kjuulh/char/pkg/schema"
"golang.org/x/sync/errgroup"
)
type GitPluginProvider struct{}
func NewGitPluginProvider() *GitPluginProvider {
return &GitPluginProvider{}
}
func (gpp *GitPluginProvider) FetchPlugins(ctx context.Context, registry string, plugins schema.CharSchemaPlugins) error {
errgroup, ctx := errgroup.WithContext(ctx)
baseDir := ".char/plugins"
if os.Getenv("CHAR_DEV_MODE") == "true" {
if err := os.RemoveAll(baseDir); err != nil {
return err
}
}
if err := os.MkdirAll(baseDir, 0755); err != nil {
return fmt.Errorf("path already exists cannot create: %w", err)
}
for n, plugin := range plugins {
n, plugin := n, plugin
errgroup.Go(func() error {
dest := fmt.Sprintf(
"%s/%s",
strings.TrimRight(baseDir, "/"), n.Hash(),
)
fileinfo, err := os.Stat(dest)
if errors.Is(err, os.ErrNotExist) {
log.Printf("fetching git plugin repo: %s", n)
return gpp.FetchPlugin(
ctx,
registry,
plugin,
dest,
)
}
if fileinfo.ModTime().Add(time.Hour * 1).Before(time.Now()) {
log.Printf("fetching git plugin repo: %s as it is stale", n)
return gpp.FetchPlugin(
ctx,
registry,
plugin,
dest,
)
}
return nil
})
}
if err := errgroup.Wait(); err != nil {
return err
}
return nil
}
func (gpp *GitPluginProvider) FetchPlugin(ctx context.Context, registry string, plugin *schema.CharSchemaPlugin, dest string) error {
cloneUrl, err := plugin.Opts.GetCloneUrl(
ctx,
registry,
&schema.CloneUrlOpt{
Protocol: schema.GitProtocolSsh,
SshUser: "git",
},
)
if err != nil {
return err
}
if _, err := os.Stat(dest); !errors.Is(err, os.ErrNotExist) {
if err = os.RemoveAll(dest); err != nil {
return err
}
}
output, err := exec.Command(
"git",
"clone",
"--depth=1",
cloneUrl,
dest,
).CombinedOutput()
if len(output) > 0 {
log.Print(string(output))
}
if err != nil {
return err
}
return nil
}

View File

@@ -1,29 +0,0 @@
package register
import "context"
type AboutCommand struct {
Name string `json:"name" yaml:"name"`
Args []string `json:"args" yaml:"args"`
Required []string `json:"required" yaml:"required"`
}
type About struct {
Name string `json:"name"`
Version string `json:"version"`
About string `json:"about"`
Vars []string `json:"vars"`
Commands []*AboutCommand `json:"commands"`
}
type DoCommand struct {
CommandName string `json:"commandName"`
Args map[string]string `json:"args"`
}
type Plugin interface {
About(ctx context.Context) (*About, error)
Do(ctx context.Context, cmd *DoCommand) error
}
const PluginKey = "plugin"

View File

@@ -1,20 +0,0 @@
package register
import (
"net/rpc"
"github.com/hashicorp/go-plugin"
)
type PluginAPI struct {
path string
Impl Plugin
}
func (pa *PluginAPI) Server(*plugin.MuxBroker) (any, error) {
return &PluginServer{Impl: pa.Impl}, nil
}
func (*PluginAPI) Client(b *plugin.MuxBroker, c *rpc.Client) (any, error) {
return &PluginClient{client: c}, nil
}

View File

@@ -1,48 +0,0 @@
package register
import (
"context"
"os"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
)
type PluginBuilder struct {
serveConfig *plugin.ServeConfig
}
func NewPluginBuilder(p Plugin) *PluginBuilder {
logger := hclog.New(&hclog.LoggerOptions{
Level: hclog.Debug,
Output: os.Stderr,
JSONFormat: false,
})
var pluginMap = map[string]plugin.Plugin{
PluginKey: &PluginAPI{
Impl: p,
},
}
serveConfig := &plugin.ServeConfig{
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "BASIC_PLUGIN",
MagicCookieValue: "char",
},
Plugins: pluginMap,
Logger: logger,
}
return &PluginBuilder{
serveConfig: serveConfig,
}
}
func (pr *PluginBuilder) Serve(ctx context.Context) error {
plugin.Serve(
pr.serveConfig,
)
return nil
}

View File

@@ -1,39 +0,0 @@
package register
import (
"context"
"encoding/json"
"net/rpc"
)
type PluginClient struct {
client *rpc.Client
}
// Do implements Plugin
func (pc *PluginClient) Do(ctx context.Context, cmd *DoCommand) error {
err := pc.client.Call("Plugin.Do", cmd, new(string))
if err != nil {
return err
}
return nil
}
var _ Plugin = &PluginClient{}
func (pc *PluginClient) About(ctx context.Context) (*About, error) {
var resp string
err := pc.client.Call("Plugin.About", new(any), &resp)
if err != nil {
return nil, err
}
var about About
err = json.Unmarshal([]byte(resp), &about)
if err != nil {
return nil, err
}
return &about, nil
}

View File

@@ -1,248 +0,0 @@
package register
import (
"context"
"errors"
"fmt"
"log"
"os"
"os/exec"
"strings"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"golang.org/x/sync/errgroup"
)
type PluginRegisterBuilder struct {
plugins map[string]PluginAPI
}
func NewPluginRegisterBuilder() *PluginRegisterBuilder {
return &PluginRegisterBuilder{
plugins: make(map[string]PluginAPI),
}
}
func (pr *PluginRegisterBuilder) Add(name, path string) *PluginRegisterBuilder {
pr.plugins[name] = PluginAPI{
path: path,
}
return pr
}
func (pr *PluginRegisterBuilder) Build(ctx context.Context) (*PluginRegister, error) {
clients := make(map[string]*pluginClientWrapper, 0)
errgroup, _ := errgroup.WithContext(ctx)
if err := os.MkdirAll(".char/plugins/", 0755); err != nil {
return nil, err
}
for name, p := range pr.plugins {
name, p := name, p
errgroup.Go(func() error {
pluginPath := fmt.Sprintf(".char/plugins/%s/dist/plugin", name)
_, err := os.Stat(pluginPath)
if err != nil || os.Getenv("CHAR_DEV_MODE") == "true" {
log.Printf("building: %s", name)
cmd := exec.Command(
"sh",
"-c",
fmt.Sprintf(
"(cd .char/plugins/%s; go build -o dist/plugin %s/main.go)",
name,
strings.TrimSuffix(
strings.TrimSuffix(
p.path,
"main.go",
),
"/",
),
),
)
output, err := cmd.CombinedOutput()
if len(output) > 0 {
log.Println(string(output))
}
if err != nil {
return fmt.Errorf("could not build plugin: %w", err)
}
}
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "BASIC_PLUGIN",
MagicCookieValue: "char",
},
Logger: hclog.New(&hclog.LoggerOptions{
Name: "char",
Output: os.Stdout,
Level: hclog.Debug,
}),
Cmd: exec.Command(
fmt.Sprintf(
".char/plugins/%s/dist/plugin",
name,
),
),
Plugins: map[string]plugin.Plugin{
PluginKey: &p,
},
})
rpcClient, err := client.Client()
if err != nil {
return err
}
raw, err := rpcClient.Dispense("plugin")
if err != nil {
return err
}
pluginApi, ok := raw.(Plugin)
if !ok {
return errors.New("could not cast as plugin")
}
clients[name] = &pluginClientWrapper{
plugin: pluginApi,
client: client,
}
return nil
})
}
err := errgroup.Wait()
if err != nil {
return nil, err
}
return &PluginRegister{
clients: clients,
}, nil
}
// ---
type pluginClientWrapper struct {
plugin Plugin
client *plugin.Client
}
func (pcw *pluginClientWrapper) Close() {
pcw.client.Kill()
}
// ---
type PluginRegister struct {
clients map[string]*pluginClientWrapper
}
func (pr *PluginRegister) Close() error {
errgroup, _ := errgroup.WithContext(context.Background())
for _, c := range pr.clients {
c := c
errgroup.Go(func() error {
c.Close()
return nil
})
}
if err := errgroup.Wait(); err != nil {
return err
}
return nil
}
type CommandAboutItem struct {
Name string
Args []string
Required []string
}
type CommandAboutItems []*CommandAboutItem
func FromAboutCommands(commands []*AboutCommand) CommandAboutItems {
cai := make(CommandAboutItems, 0)
for _, command := range commands {
cai = append(cai, &CommandAboutItem{
Name: command.Name,
Args: command.Args,
Required: command.Required,
})
}
return cai
}
type AboutItem struct {
Name string
Version string
About string
Vars []string
Commands CommandAboutItems
ClientName string
}
func (pr *PluginRegister) About(ctx context.Context) ([]AboutItem, error) {
list := make([]AboutItem, 0)
errgroup, ctx := errgroup.WithContext(ctx)
for name, c := range pr.clients {
name, c := name, c
errgroup.Go(func() error {
about, err := c.plugin.About(ctx)
if err != nil {
return err
}
list = append(list, AboutItem{
Name: about.Name,
Version: about.Version,
About: about.About,
Vars: about.Vars,
Commands: FromAboutCommands(about.Commands),
ClientName: name,
})
return nil
})
}
if err := errgroup.Wait(); err != nil {
return nil, err
}
return list, nil
}
func (pr *PluginRegister) Do(ctx context.Context, clientName string, commandName string, args map[string]string) error {
errgroup, ctx := errgroup.WithContext(ctx)
client, ok := pr.clients[clientName]
if !ok {
return fmt.Errorf("plugin was not found: %s", clientName)
}
errgroup.Go(func() error {
return client.plugin.Do(ctx, &DoCommand{
CommandName: commandName,
Args: args,
})
})
if err := errgroup.Wait(); err != nil {
return err
}
return nil
}

View File

@@ -1,44 +0,0 @@
package register
import (
"context"
"encoding/json"
)
type PluginServer struct {
Impl Plugin
}
func (ps *PluginServer) Do(args *DoCommand, resp *string) error {
//rawReq, ok := args.(string)
//if !ok {
// return errors.New("args is not a string")
//}
//var doReq DoRequest
//if err := json.Unmarshal([]byte(rawReq), &doReq); err != nil {
// return err
//}
if err := ps.Impl.Do(context.Background(), args); err != nil {
return err
}
*resp = ""
return nil
}
func (ps *PluginServer) About(args any, resp *string) error {
r, err := ps.Impl.About(context.Background())
if err != nil {
return err
}
respB, err := json.Marshal(r)
if err != nil {
return err
}
*resp = string(respB)
return nil
}

View File

@@ -1,50 +0,0 @@
package schema
import (
"context"
"errors"
"fmt"
"os"
"gopkg.in/yaml.v3"
)
type CharSchema struct {
Registry string `json:"registry" yaml:"registry"`
Plugins CharSchemaPlugins `json:"plugins" yaml:"plugins"`
}
func ParseFile(ctx context.Context, path string) (*CharSchema, error) {
if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("could not parse file, as it is not found or permitted: %s", path)
}
file, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("could not read file: %w", err)
}
return Parse(file)
}
func Parse(content []byte) (*CharSchema, error) {
var schema CharSchema
if err := yaml.Unmarshal(content, &schema); err != nil {
return nil, fmt.Errorf("could not deserialize yaml into CharSchema: %w", err)
}
return &schema, nil
}
func (cs *CharSchema) GetPlugins(ctx context.Context) (CharSchemaPlugins, error) {
plugins := make(map[CharSchemaPluginName]*CharSchemaPlugin, len(cs.Plugins))
for n, plugin := range cs.Plugins {
po, err := n.Get()
if err != nil {
return nil, err
}
plugin.Opts = po
plugins[n] = plugin
}
return plugins, nil
}

View File

@@ -1,104 +0,0 @@
package schema
import (
"context"
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"regexp"
"strings"
)
type CharSchemaPluginName string
func (cspn CharSchemaPluginName) Hash() string {
bytes := sha256.Sum256([]byte(cspn))
return hex.EncodeToString(bytes[:])
}
type PluginOps struct {
Org string
RepositoryName string
Path string
Version string
}
type GitProtocol string
const (
GitProtocolHttps GitProtocol = "https"
GitProtocolSsh = "ssh"
)
type CloneUrlOpt struct {
Protocol GitProtocol
SshUser string
}
func (po *PluginOps) GetCloneUrl(ctx context.Context, registry string, opt *CloneUrlOpt) (string, error) {
if opt == nil {
return "", errors.New("opt is required")
}
switch opt.Protocol {
case GitProtocolHttps:
return fmt.Sprintf("https://%s/%s/%s.git", registry, po.Org, po.RepositoryName), nil
case GitProtocolSsh:
return fmt.Sprintf("%s@%s:%s/%s.git", opt.SshUser, registry, po.Org, po.RepositoryName), nil
default:
return "", errors.New("protocol not allowed")
}
}
var memo = map[string]*PluginOps{}
func (cspn CharSchemaPluginName) Get() (*PluginOps, error) {
if m, ok := memo[string(cspn)]; ok {
return m, nil
}
po := &PluginOps{}
reg := regexp.MustCompile(
`(?P<org>[\d\w\-_\.]+)\/(?P<repo>[\d\w\-_\.]+)(?P<path>#[\d\w\-_\.\/]+)?(?P<version>@[\d\w\-_\.\/]+)?(?P<path>#[\d\w\-_\.\/]+)?`,
)
matches := reg.FindStringSubmatch(string(cspn))
tags := reg.SubexpNames()
matchTags := make(map[string]string, len(matches))
for i, match := range matches {
tag := tags[i]
if existingTag, ok := matchTags[tag]; !ok || existingTag == "" {
matchTags[tag] = match
}
}
if org, ok := matchTags["org"]; ok {
po.Org = org
}
if repo, ok := matchTags["repo"]; ok {
po.RepositoryName = repo
}
if path, ok := matchTags["path"]; ok {
po.Path = strings.TrimLeft(path, "#")
}
if version, ok := matchTags["version"]; ok {
po.Version = strings.TrimLeft(version, "@")
}
if po.Org == "" || po.RepositoryName == "" {
return nil, errors.New("could not find org or repository name")
}
memo[string(cspn)] = po
return po, nil
}
type CharSchemaPlugins map[CharSchemaPluginName]*CharSchemaPlugin
type CharSchemaPluginVarName string
type CharSchemaPluginVars map[CharSchemaPluginVarName]string
type CharSchemaPlugin struct {
Opts *PluginOps
Vars CharSchemaPluginVars `json:"vars"`
}

View File

@@ -1,126 +0,0 @@
package schema_test
import (
"context"
"testing"
"git.front.kjuulh.io/kjuulh/char/pkg/schema"
"github.com/stretchr/testify/require"
)
func TestSchemaNameCanParse(t *testing.T) {
t.Parallel()
tt := []struct {
name string
inputString schema.CharSchemaPluginName
expected schema.PluginOps
}{
{
name: "default string",
inputString: `kju123K_-ulh/someRepo-._123`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
},
},
{
name: "default string with path",
inputString: `kju123K_-ulh/someRepo-._123#somepath/sometoherpath/somethridpath`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
Path: "somepath/sometoherpath/somethridpath",
},
},
{
name: "default string with version",
inputString: `kju123K_-ulh/someRepo-._123@12l3.jk1lj`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
Version: "12l3.jk1lj",
},
},
{
name: "default string with version and path",
inputString: `kju123K_-ulh/someRepo-._123@12l3.jk1lj#somepath/sometoherpath/somethridpath`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
Version: "12l3.jk1lj",
Path: "somepath/sometoherpath/somethridpath",
},
},
{
name: "default string with path and version",
inputString: `kju123K_-ulh/someRepo-._123#somepath/sometoherpath/somethridpath@12l3.jk1lj`,
expected: schema.PluginOps{
Org: "kju123K_-ulh",
RepositoryName: "someRepo-._123",
Version: "12l3.jk1lj",
Path: "somepath/sometoherpath/somethridpath",
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
actual, _ := tc.inputString.Get()
require.Equal(t, tc.expected, *actual)
})
}
}
func TestPluginOpt(t *testing.T) {
t.Parallel()
tt := []struct {
name string
pluginOpt schema.PluginOps
cloneUrlOpt schema.CloneUrlOpt
registry string
expected string
}{
{
name: "ssh values",
pluginOpt: schema.PluginOps{
Org: "kjuulh",
RepositoryName: "char",
Path: "",
Version: "",
},
cloneUrlOpt: schema.CloneUrlOpt{
Protocol: schema.GitProtocolSsh,
SshUser: "git",
},
registry: "git.front.kjuulh.io",
expected: "git@git.front.kjuulh.io:kjuulh/char.git",
},
{
name: "https values",
pluginOpt: schema.PluginOps{
Org: "kjuulh",
RepositoryName: "char",
Path: "",
Version: "",
},
cloneUrlOpt: schema.CloneUrlOpt{
Protocol: schema.GitProtocolHttps,
},
registry: "git.front.kjuulh.io",
expected: "https://git.front.kjuulh.io/kjuulh/char.git",
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
url, err := tc.pluginOpt.GetCloneUrl(context.Background(), tc.registry, &tc.cloneUrlOpt)
require.NoError(t, err)
require.Equal(t, tc.expected, url)
})
}
}

View File

@@ -1,93 +0,0 @@
package schema_test
import (
"context"
"testing"
"git.front.kjuulh.io/kjuulh/char/pkg/schema"
"github.com/stretchr/testify/require"
)
func TestSchemaParse(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input string
expected *schema.CharSchema
}{
{
name: "with plugins",
input: `
registry: git.front.kjuulh.io
plugins:
"kjuulh/char#plugins/gocli": {}
"kjuulh/char#plugins/rust": {}
`,
expected: &schema.CharSchema{
Registry: "git.front.kjuulh.io",
Plugins: map[schema.CharSchemaPluginName]*schema.CharSchemaPlugin{
"kjuulh/char#plugins/gocli": {},
"kjuulh/char#plugins/rust": {},
},
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
s, err := schema.Parse([]byte(tc.input))
require.NoError(t, err)
require.Equal(t, tc.expected, s)
})
}
}
func TestGetPlugins(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input string
expected schema.CharSchemaPlugins
}{
{
name: "with plugins",
input: `
registry: git.front.kjuulh.io
plugins:
"kjuulh/char#plugins/gocli@v1.9.0": {}
"kjuulh/char#plugins/rust": {}
`,
expected: map[schema.CharSchemaPluginName]*schema.CharSchemaPlugin{
"kjuulh/char#plugins/gocli@v1.9.0": {
Opts: &schema.PluginOps{
Org: "kjuulh",
RepositoryName: "char",
Path: "plugins/gocli",
Version: "v1.9.0",
},
},
"kjuulh/char#plugins/rust": {
Opts: &schema.PluginOps{
Org: "kjuulh",
RepositoryName: "char",
Path: "plugins/rust",
Version: "",
},
},
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
s, err := schema.Parse([]byte(tc.input))
require.NoError(t, err)
plugins, err := s.GetPlugins(context.Background())
require.NoError(t, err)
require.Equal(t, tc.expected, plugins)
})
}
}

View File

@@ -1,47 +0,0 @@
package main
import (
"context"
"log"
"git.front.kjuulh.io/kjuulh/char/pkg/register"
"github.com/hashicorp/go-hclog"
)
type GoCliPlugin struct{}
// Do implements register.Plugin
func (*GoCliPlugin) Do(ctx context.Context, cmd *register.DoCommand) error {
hclog.L().Info("received command", "commandName", cmd.CommandName)
return nil
}
func (*GoCliPlugin) About(ctx context.Context) (*register.About, error) {
return &register.About{
Name: "gocli",
Version: "v0.0.1",
About: "golang cli provides a set of actions and presets supporting golang development",
Vars: []string{
"dev.mode",
},
Commands: []*register.AboutCommand{
{
Name: "local_up",
Args: []string{"fish"},
Required: []string{"fish"},
},
},
}, nil
}
var _ register.Plugin = &GoCliPlugin{}
func main() {
if err := register.
NewPluginBuilder(
&GoCliPlugin{},
).
Serve(context.Background()); err != nil {
log.Fatal(err)
}
}

View File

@@ -1,37 +0,0 @@
package main
import (
"context"
"log"
"git.front.kjuulh.io/kjuulh/char/pkg/register"
"github.com/hashicorp/go-hclog"
)
type GoCliPlugin struct{}
// Do implements register.Plugin
func (*GoCliPlugin) Do(ctx context.Context, cmd *register.DoCommand) error {
hclog.L().Info("received command", "commandName", cmd.CommandName)
return nil
}
func (*GoCliPlugin) About(ctx context.Context) (*register.About, error) {
return &register.About{
Name: "rust",
Version: "v0.0.1",
About: "rust cli provides a set of actions and presets supporting rust development",
}, nil
}
var _ register.Plugin = &GoCliPlugin{}
func main() {
if err := register.
NewPluginBuilder(
&GoCliPlugin{},
).
Serve(context.Background()); err != nil {
log.Fatal(err)
}
}

View File

@@ -1,3 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}