feat: add open & diff command

This commit is contained in:
Kevin Franklin Kim 2025-06-05 11:53:37 +02:00
parent c6f5292df3
commit 5fdd26d867
No known key found for this signature in database
13 changed files with 781 additions and 22 deletions

23
cmd/diff.go Normal file
View File

@ -0,0 +1,23 @@
package cmd
import (
"log/slog"
"github.com/foomo/sesamy-cli/cmd/diff"
"github.com/spf13/cobra"
)
// NewDiff represents the diff command
func NewDiff(l *slog.Logger) *cobra.Command {
cmd := &cobra.Command{
Use: "diff",
Short: "Print Google Tag Manager container status diff",
}
cmd.AddCommand(
diff.NewWeb(l),
diff.NewServer(l),
)
return cmd
}

396
cmd/diff/diff.go Normal file
View File

@ -0,0 +1,396 @@
package diff
import (
"bytes"
"context"
"log/slog"
"strings"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
"github.com/itchyny/json2yaml"
"github.com/sters/yaml-diff/yamldiff"
)
func diff(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager) (string, error) {
l.Info("└ ⬇︎ Loading status")
s, err := tm.Service().Accounts.Containers.Workspaces.GetStatus(tm.WorkspacePath()).Context(ctx).Do()
if err != nil {
return "", err
} else if len(s.WorkspaceChange) == 0 {
return "", nil
}
l.Info("└ ⬇︎ Loading live version")
live, err := tm.Service().Accounts.Containers.Versions.Live(tm.ContainerPath()).Do()
if err != nil {
return "", err
}
var res []string
for _, entity := range s.WorkspaceChange {
switch {
case entity.Tag != nil:
res = append(res, " # Tag: "+entity.Tag.Name+" ("+entity.ChangeStatus+")\n")
// unset props
entity.Tag.Path = ""
entity.Tag.Fingerprint = ""
entity.Tag.WorkspaceId = ""
entity.Tag.TagManagerUrl = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.Tag)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.Tag {
if value.Name == entity.Tag.Name {
// unset props
value.Fingerprint = ""
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
case entity.Folder != nil:
res = append(res, " # Folder: "+entity.Folder.Name+" ("+entity.ChangeStatus+")")
// unset props
entity.Folder.Path = ""
entity.Folder.Fingerprint = ""
entity.Folder.WorkspaceId = ""
entity.Folder.TagManagerUrl = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.Folder)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.Folder {
if value.Name == entity.Folder.Name {
// unset props
value.Fingerprint = ""
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
case entity.Trigger != nil:
res = append(res, " # Trigger: "+entity.Trigger.Name+" ("+entity.ChangeStatus+")")
// unset props
entity.Trigger.Path = ""
entity.Trigger.Fingerprint = ""
entity.Trigger.WorkspaceId = ""
entity.Trigger.TagManagerUrl = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.Trigger)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.Trigger {
if value.Name == entity.Trigger.Name {
// unset props
value.Fingerprint = ""
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
case entity.Variable != nil:
res = append(res, " # Variable: "+entity.Variable.Name+" ("+entity.ChangeStatus+")")
// unset props
entity.Variable.Path = ""
entity.Variable.Fingerprint = ""
entity.Variable.WorkspaceId = ""
entity.Variable.TagManagerUrl = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.Variable)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.Variable {
if value.Name == entity.Variable.Name {
// unset props
value.Fingerprint = ""
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
case entity.Client != nil:
res = append(res, " # Client: "+entity.Client.Name+" ("+entity.ChangeStatus+")")
// unset props
entity.Client.Path = ""
entity.Client.Fingerprint = ""
entity.Client.WorkspaceId = ""
entity.Client.TagManagerUrl = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.Client)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.Client {
if value.Name == entity.Client.Name {
// unset props
value.Fingerprint = ""
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
case entity.GtagConfig != nil:
res = append(res, " # GtagConfig: "+entity.GtagConfig.AccountId+" ("+entity.ChangeStatus+")")
// unset props
entity.GtagConfig.Path = ""
entity.GtagConfig.Fingerprint = ""
entity.GtagConfig.WorkspaceId = ""
entity.GtagConfig.TagManagerUrl = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.GtagConfig)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.GtagConfig {
if value.AccountId == entity.GtagConfig.AccountId {
// unset props
value.Fingerprint = ""
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
case entity.BuiltInVariable != nil:
res = append(res, " # BuiltInVariable: "+entity.BuiltInVariable.Name+" ("+entity.ChangeStatus+")")
// unset props
entity.BuiltInVariable.Path = ""
entity.BuiltInVariable.WorkspaceId = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.BuiltInVariable)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.BuiltInVariable {
if value.Name == entity.BuiltInVariable.Name {
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
case entity.CustomTemplate != nil:
res = append(res, " # CustomTemplate: "+entity.CustomTemplate.Name+" ("+entity.ChangeStatus+")")
// unset props
entity.CustomTemplate.Path = ""
entity.CustomTemplate.Fingerprint = ""
entity.CustomTemplate.WorkspaceId = ""
entity.CustomTemplate.TagManagerUrl = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.CustomTemplate)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.CustomTemplate {
if value.Name == entity.CustomTemplate.Name {
// unset props
value.Fingerprint = ""
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
case entity.Transformation != nil:
res = append(res, " # Transformation: "+entity.Transformation.Name+" ("+entity.ChangeStatus+")")
// unset props
entity.Transformation.Path = ""
entity.Transformation.Fingerprint = ""
entity.Transformation.WorkspaceId = ""
entity.Transformation.TagManagerUrl = ""
var changed string
if entity.ChangeStatus != "deleted" {
changed, err = ToYalm(entity.Transformation)
if err != nil {
return "", err
}
}
var original string
for _, value := range live.Transformation {
if value.Name == entity.Transformation.Name {
// unset props
value.Fingerprint = ""
original, err = ToYalm(value)
if err != nil {
return "", err
}
break
}
}
d, err := ToDiff(original, changed)
if err != nil {
return "", err
}
res = append(res, d...)
}
}
return strings.Join(res, " ---\n"), nil
}
type Marshelable interface {
MarshalJSON() ([]byte, error)
}
func ToDiff(original, changed string) ([]string, error) {
yamls1, err := yamldiff.Load(original)
if err != nil {
return nil, err
}
yamls2, err := yamldiff.Load(changed)
if err != nil {
return nil, err
}
var ret []string
for _, d := range yamldiff.Do(yamls1, yamls2) {
if value := d.Dump(); len(value) > 4 {
ret = append(ret, value)
}
}
return ret, nil
}
func ToYalm(m Marshelable) (string, error) {
if m == nil {
return "", nil
}
out, err := m.MarshalJSON()
if err != nil {
return "", err
}
var ret bytes.Buffer
if err := json2yaml.Convert(&ret, bytes.NewBuffer(out)); err != nil {
return "", err
}
return ret.String(), nil
}

68
cmd/diff/server.go Normal file
View File

@ -0,0 +1,68 @@
package diff
import (
"fmt"
"log/slog"
pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
"github.com/foomo/sesamy-cli/pkg/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// NewServer represents the server command
func NewServer(l *slog.Logger) *cobra.Command {
c := viper.New()
cmd := &cobra.Command{
Use: "server",
Short: "Print Google Tag Manager Server Container status diff",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
l.Info("☕ Retrieving Server Container status")
cfg, err := pkgcmd.ReadConfig(l, c, cmd)
if err != nil {
return err
}
tm, err := tagmanager.New(
cmd.Context(),
l,
cfg.GoogleTagManager.AccountID,
cfg.GoogleTagManager.ServerContainer,
tagmanager.WithRequestQuota(cfg.GoogleAPI.RequestQuota),
tagmanager.WithClientOptions(cfg.GoogleAPI.GetClientOption()),
)
if err != nil {
return err
}
if err := tm.EnsureWorkspaceID(cmd.Context()); err != nil {
return err
}
out, err := diff(cmd.Context(), l, tm)
if err != nil {
return err
}
if !c.GetBool("raw") {
out = utils.Highlight(out)
}
_, err = fmt.Println(out)
return err
},
}
flags := cmd.Flags()
flags.Bool("raw", false, "print raw output")
_ = c.BindPFlag("raw", flags.Lookup("raw"))
flags.StringSliceP("config", "c", []string{"sesamy.yaml"}, "config files (default is sesamy.yaml)")
_ = c.BindPFlag("config", flags.Lookup("config"))
return cmd
}

68
cmd/diff/web.go Normal file
View File

@ -0,0 +1,68 @@
package diff
import (
"fmt"
"log/slog"
pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
"github.com/foomo/sesamy-cli/pkg/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// NewWeb represents the web command
func NewWeb(l *slog.Logger) *cobra.Command {
c := viper.New()
cmd := &cobra.Command{
Use: "web",
Short: "Print Google Tag Manager Web Container status diff",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
l.Info("☕ Retrieving Web Container status")
cfg, err := pkgcmd.ReadConfig(l, c, cmd)
if err != nil {
return err
}
tm, err := tagmanager.New(
cmd.Context(),
l,
cfg.GoogleTagManager.AccountID,
cfg.GoogleTagManager.WebContainer,
tagmanager.WithRequestQuota(cfg.GoogleAPI.RequestQuota),
tagmanager.WithClientOptions(cfg.GoogleAPI.GetClientOption()),
)
if err != nil {
return err
}
if err := tm.EnsureWorkspaceID(cmd.Context()); err != nil {
return err
}
out, err := diff(cmd.Context(), l, tm)
if err != nil {
return err
}
if !c.GetBool("raw") {
out = utils.Highlight(out)
}
_, err = fmt.Println(out)
return err
},
}
flags := cmd.Flags()
flags.Bool("raw", false, "print raw output")
_ = c.BindPFlag("raw", flags.Lookup("raw"))
flags.StringSliceP("config", "c", []string{"sesamy.yaml"}, "config files (default is sesamy.yaml)")
_ = c.BindPFlag("config", flags.Lookup("config"))
return cmd
}

View File

@ -5,42 +5,34 @@ import (
"context"
"fmt"
"log/slog"
"os"
"strings"
"github.com/alecthomas/chroma/quick"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
"github.com/itchyny/json2yaml"
)
func dump(i interface{ MarshalJSON() ([]byte, error) }, err error) error {
func dump(i interface{ MarshalJSON() ([]byte, error) }, err error) (string, error) {
if err != nil {
return err
return "", err
}
out, err := i.MarshalJSON()
if err != nil {
return err
}
// if err := json.Indent(ret, out, "", " "); err != nil {
// return err
// }
// fmt.Println(ret.String())
var output strings.Builder
if err := json2yaml.Convert(&output, bytes.NewBuffer(out)); err != nil {
return err
}
// fmt.Print(output.String())
return quick.Highlight(os.Stdout, output.String(), "yaml", "terminal", "monokai")
return "", err
}
func list(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, resource string) error {
var output strings.Builder
if err := json2yaml.Convert(&output, bytes.NewBuffer(out)); err != nil {
return "", err
}
return output.String(), nil
}
func list(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, resource string) (string, error) {
switch resource {
case "environments":
return dump(tm.Service().Accounts.Containers.Environments.List(tm.ContainerPath()).Context(ctx).Do())
case "workspaces":
return dump(tm.Service().Accounts.Containers.Workspaces.List(tm.ContainerPath()).Context(ctx).Do())
case "status":
return dump(tm.Service().Accounts.Containers.Workspaces.GetStatus(tm.WorkspacePath()).Context(ctx).Do())
case "clients":
return dump(tm.Service().Accounts.Containers.Workspaces.Clients.List(tm.WorkspacePath()).Context(ctx).Do())
case "tags":
@ -56,13 +48,14 @@ func list(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, resour
case "templates-data":
r, err := tm.Service().Accounts.Containers.Workspaces.Templates.List(tm.WorkspacePath()).Context(ctx).Do()
if err != nil {
return err
return "", err
}
var ret strings.Builder
for _, template := range r.Template {
l.Info("---- Template data: " + template.Name + " ----------------------")
fmt.Println(template.TemplateData)
ret.WriteString("---- Template data: " + template.Name + " ----------------------\n")
ret.WriteString(template.TemplateData + "\n")
}
return nil
return ret.String(), nil
case "gtag-config":
return dump(tm.Service().Accounts.Containers.Workspaces.GtagConfig.List(tm.WorkspacePath()).Context(ctx).Do())
case "triggers":
@ -72,6 +65,6 @@ func list(ctx context.Context, l *slog.Logger, tm *tagmanager.TagManager, resour
case "zones":
return dump(tm.Service().Accounts.Containers.Workspaces.Zones.List(tm.WorkspacePath()).Context(ctx).Do())
default:
return fmt.Errorf("unknown resource %s", resource)
return "", fmt.Errorf("unknown resource %s", resource)
}
}

View File

@ -1,10 +1,12 @@
package list
import (
"fmt"
"log/slog"
pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
"github.com/foomo/sesamy-cli/pkg/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -58,12 +60,24 @@ func NewServer(l *slog.Logger) *cobra.Command {
return err
}
return list(cmd.Context(), l, tm, resource)
out, err := list(cmd.Context(), l, tm, resource)
if err != nil {
return err
}
if !c.GetBool("raw") {
out = utils.Highlight(out)
}
_, err = fmt.Println(out)
return err
},
}
flags := cmd.Flags()
flags.Bool("raw", false, "print raw output")
_ = c.BindPFlag("raw", flags.Lookup("raw"))
flags.StringSliceP("config", "c", []string{"sesamy.yaml"}, "config files (default is sesamy.yaml)")
_ = c.BindPFlag("config", flags.Lookup("config"))

View File

@ -1,10 +1,12 @@
package list
import (
"fmt"
"log/slog"
pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd"
"github.com/foomo/sesamy-cli/pkg/tagmanager"
"github.com/foomo/sesamy-cli/pkg/utils"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
@ -57,12 +59,24 @@ func NewWeb(l *slog.Logger) *cobra.Command {
return err
}
return list(cmd.Context(), l, tm, resource)
out, err := list(cmd.Context(), l, tm, resource)
if err != nil {
return err
}
if !c.GetBool("raw") {
out = utils.Highlight(out)
}
_, err = fmt.Println(out)
return err
},
}
flags := cmd.Flags()
flags.Bool("raw", false, "print raw output")
_ = c.BindPFlag("raw", flags.Lookup("raw"))
flags.StringSliceP("config", "c", []string{"sesamy.yaml"}, "config files (default is sesamy.yaml)")
_ = c.BindPFlag("config", flags.Lookup("config"))

70
cmd/open.go Normal file
View File

@ -0,0 +1,70 @@
package cmd
import (
"fmt"
"log/slog"
pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd"
"github.com/pkg/browser"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// NewOpen represents the open command
func NewOpen(l *slog.Logger) *cobra.Command {
c := viper.New()
cmd := &cobra.Command{
Use: "open",
Short: "Open links in the browser",
Args: cobra.OnlyValidArgs,
ValidArgs: []cobra.Completion{
"ga",
"gtm-web",
"gtm-server",
},
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := pkgcmd.ReadConfig(l, c, cmd)
if err != nil {
return err
}
var url string
switch args[0] {
case "ga":
if cfg.GoogleAnalytics.PropertyID == "" {
return fmt.Errorf("missing Google Analytics Property ID")
}
url = fmt.Sprintf(
"https://analytics.google.com/analytics/web/#/p%s/",
cfg.GoogleAnalytics.PropertyID,
)
case "gtm-web":
url = fmt.Sprintf(
"https://tagmanager.google.com/#/container/accounts/%s/containers/%s/",
cfg.GoogleTagManager.AccountID,
cfg.GoogleTagManager.WebContainer.ContainerID,
)
case "gtm-server":
url = fmt.Sprintf(
"https://tagmanager.google.com/#/container/accounts/%s/containers/%s/",
cfg.GoogleTagManager.AccountID,
cfg.GoogleTagManager.ServerContainer.ContainerID,
)
default:
return fmt.Errorf("invalid container type: %s", args[0])
}
l.Info("↗ Navigating to Google Tag Manager Container: " + url)
return browser.OpenURL(url)
},
}
flags := cmd.Flags()
flags.StringSliceP("config", "c", []string{"sesamy.yaml"}, "config files (default is sesamy.yaml)")
_ = c.BindPFlag("config", flags.Lookup("config"))
return cmd
}

3
go.mod
View File

@ -16,10 +16,12 @@ require (
github.com/knadh/koanf/providers/file v1.2.0
github.com/knadh/koanf/providers/rawbytes v1.0.0
github.com/knadh/koanf/v2 v2.2.0
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
github.com/pkg/errors v0.9.1
github.com/pterm/pterm v0.12.80
github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.1
github.com/sters/yaml-diff v1.4.1
github.com/stoewer/go-strcase v1.3.0
github.com/stretchr/testify v1.10.0
github.com/wissance/stringFormatter v1.4.1
@ -45,6 +47,7 @@ require (
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/goccy/go-yaml v1.15.6 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect

6
go.sum
View File

@ -66,6 +66,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-yaml v1.15.6 h1:gy5kf1yjMia3/c3wWD+u1z3lU5XlhpT8FZGaLJU9cOA=
github.com/goccy/go-yaml v1.15.6/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@ -129,6 +131,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -165,6 +169,8 @@ github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/sters/yaml-diff v1.4.1 h1:0W3jnFKCu8/DV7nh2aXSDA2VVfxfHu2+qdh81CuFmZo=
github.com/sters/yaml-diff v1.4.1/go.mod h1:K286Xp2z+aGkok7z9k3zXcq0ZsrDaDp7/wyGwFjM9Y8=
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -19,6 +19,8 @@ func main() {
root.AddCommand(
cmd.NewConfig(l),
cmd.NewList(l),
cmd.NewDiff(l),
cmd.NewOpen(l),
cmd.NewProvision(l),
cmd.NewTags(l),
cmd.NewTypeScript(l),

View File

@ -7,6 +7,10 @@ import (
type GoogleAnalytics struct {
// Enable provider
Enabled bool `json:"enabled" yaml:"enabled"`
// Google Analytics account id
AccountID string `json:"accountId" yaml:"accountId"`
// Google Analytics property id
PropertyID string `json:"propertyId" yaml:"propertyId"`
// Google Consent settings
GoogleConsent GoogleConsent `json:"googleConsent" yaml:"googleConsent"`
// GTag.js override configuration

98
pkg/utils/highlight.go Normal file
View File

@ -0,0 +1,98 @@
package utils
import (
"bytes"
"fmt"
"github.com/alecthomas/chroma"
"github.com/alecthomas/chroma/formatters"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
"github.com/pterm/pterm"
)
func Highlight(source string) string {
out := &numberWriter{
w: bytes.NewBufferString(""),
currentLine: 1,
}
// Determine lexer.
l := lexers.Get("yaml")
if l == nil {
l = lexers.Analyse(source)
}
if l == nil {
l = lexers.Fallback
}
l = chroma.Coalesce(l)
// Determine formatter.
f := formatters.Get("terminal256")
if f == nil {
f = formatters.Fallback
}
// Determine style.
s := styles.Get("monokai")
if s == nil {
s = styles.Fallback
}
it, err := l.Tokenise(nil, source)
if err != nil {
pterm.Error.Println(err.Error())
}
if err = f.Format(out, s, it); err != nil {
pterm.Error.Println(err.Error())
}
return out.w.String()
}
type numberWriter struct {
w *bytes.Buffer
currentLine uint64
buf []byte
}
func (w *numberWriter) Write(p []byte) (int, error) {
// Early return.
// Can't calculate the line numbers until the line breaks are made, so store them all in a buffer.
if !bytes.Contains(p, []byte{'\n'}) {
w.buf = append(w.buf, p...)
return len(p), nil
}
var (
original = p
tokenLen uint
)
for i, c := range original {
tokenLen++
if c != '\n' {
continue
}
token := p[:tokenLen]
p = original[i+1:]
tokenLen = 0
format := "%4d |\t%s%s"
if w.currentLine > 9999 {
format = "%d |\t%s%s"
}
format = "\033[34m" + format + "\033[0m"
if _, err := fmt.Fprintf(w.w, format, w.currentLine, string(w.buf), string(token)); err != nil {
return i + 1, err
}
w.buf = w.buf[:0]
w.currentLine++
}
if len(p) > 0 {
w.buf = append(w.buf, p...)
}
return len(original), nil
}