Skip to content

Commit

Permalink
work on generic text view
Browse files Browse the repository at this point in the history
  • Loading branch information
aanamshaikh committed Feb 7, 2023
1 parent 9d7977b commit 0ad022e
Show file tree
Hide file tree
Showing 16 changed files with 807 additions and 4 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/cenkalti/backoff/v4 v4.2.0
github.com/dustin/go-humanize v1.0.1
github.com/gdamore/encoding v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
Expand All @@ -40,6 +41,7 @@ require (
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sahilm/fuzzy v0.1.0
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.1.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKO
github.com/brianvoe/gofakeit v3.18.0+incompatible/go.mod h1:kfwdRA90vvNhPutZWfH7WPaDzUjz+CZFqG+rPkOjGOc=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -82,6 +84,8 @@ github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
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=
Expand Down
6 changes: 6 additions & 0 deletions internal/dao/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package dao

// Describe describes a resource.
func Describe(resource, path string) (string, error) {
return "Hello", nil
}
15 changes: 15 additions & 0 deletions internal/dao/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package dao

import "regexp"

var (
fuzzyRx = regexp.MustCompile(`\A\-f`)
)

// IsFuzzySelector checks if filter is fuzzy or not.
func IsFuzzySelector(s string) bool {
if s == "" {
return false
}
return fuzzyRx.MatchString(s)
}
7 changes: 7 additions & 0 deletions internal/dao/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,10 @@ type Accessor interface {
Lister
Getter
}
type Describer interface {
// Describe describes a resource.
Describe(path string) (string, error)

// ToYAML dumps a resource to YAML.
// ToYAML(path string, showManaged bool) (string, error)
}
1 change: 1 addition & 0 deletions internal/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type ContextKey string

// A collection of context keys.
const (
KeyFactory ContextKey = "factory"
KeyApp ContextKey = "app"
KeySession ContextKey = "session"
)
207 changes: 207 additions & 0 deletions internal/model/describe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package model

import (
"context"
"fmt"
"reflect"
"regexp"
"strings"
"sync/atomic"
"time"

backoff "github.com/cenkalti/backoff/v4"
"github.com/one2nc/cloud-lens/internal/dao"
"github.com/rs/zerolog/log"
"github.com/sahilm/fuzzy"
)

// Describe tracks describable resources.
type Describe struct {
resource string
inUpdate int32
path string
query string
lines []string
refreshRate time.Duration
listeners []ResourceViewerListener
}

// NewDescribe returns a new describe resource model.
func NewDescribe(res string, path string) *Describe {
return &Describe{
resource: res,
path: path,
refreshRate: defaultReaderRefreshRate,
}
}

// GetPath returns the active resource path.
func (d *Describe) GetPath() string {
return d.path
}

// SetOptions toggle model options.
func (d *Describe) SetOptions(context.Context, ViewerToggleOpts) {}

// Filter filters the model.
func (d *Describe) Filter(q string) {
d.query = q
d.filterChanged(d.lines)
}

func (d *Describe) filterChanged(lines []string) {
d.fireResourceChanged(lines, d.filter(d.query, lines))
}

func (d *Describe) filter(q string, lines []string) fuzzy.Matches {
if q == "" {
return nil
}
if dao.IsFuzzySelector(q) {
return d.fuzzyFilter(strings.TrimSpace(q[2:]), lines)
}
return d.rxFilter(q, lines)
}

func (*Describe) fuzzyFilter(q string, lines []string) fuzzy.Matches {
return fuzzy.Find(q, lines)
}

func (*Describe) rxFilter(q string, lines []string) fuzzy.Matches {
rx, err := regexp.Compile(`(?i)` + q)
if err != nil {
return nil
}
matches := make(fuzzy.Matches, 0, len(lines))
for i, l := range lines {
if loc := rx.FindStringIndex(l); len(loc) == 2 {
matches = append(matches, fuzzy.Match{Str: q, Index: i, MatchedIndexes: loc})
}
}

return matches
}

func (d *Describe) fireResourceChanged(lines []string, matches fuzzy.Matches) {
for _, l := range d.listeners {
l.ResourceChanged(lines, matches)
}
}

func (d *Describe) fireResourceFailed(err error) {
for _, l := range d.listeners {
l.ResourceFailed(err)
}
}

// ClearFilter clear out the filter.
func (d *Describe) ClearFilter() {
}

// Peek returns current model state.
func (d *Describe) Peek() []string {
return d.lines
}

// Refresh updates model data.
func (d *Describe) Refresh(ctx context.Context) error {
return d.refresh(ctx)
}

// Watch watches for describe data changes.
func (d *Describe) Watch(ctx context.Context) error {
if err := d.refresh(ctx); err != nil {
return err
}
go d.updater(ctx)
return nil
}

func (d *Describe) updater(ctx context.Context) {
defer log.Debug().Msgf("Describe canceled -- %q", d.resource)

backOff := NewExpBackOff(ctx, defaultReaderRefreshRate, maxReaderRetryInterval)
delay := defaultReaderRefreshRate
for {
select {
case <-ctx.Done():
return
case <-time.After(delay):
if err := d.refresh(ctx); err != nil {
d.fireResourceFailed(err)
if delay = backOff.NextBackOff(); delay == backoff.Stop {
log.Error().Err(err).Msgf("Describe gave up!")
return
}
} else {
backOff.Reset()
delay = defaultReaderRefreshRate
}
}
}
}

func (d *Describe) refresh(ctx context.Context) error {
if !atomic.CompareAndSwapInt32(&d.inUpdate, 0, 1) {
log.Debug().Msgf("Dropping update...")
return nil
}
defer atomic.StoreInt32(&d.inUpdate, 0)

if err := d.reconcile(ctx); err != nil {
log.Error().Err(err).Msgf("reconcile failed %q", d.resource)
d.fireResourceFailed(err)
return err
}

return nil
}

func (d *Describe) reconcile(ctx context.Context) error {
s, err := d.describe(ctx, d.resource, d.path)
if err != nil {
return err
}
lines := strings.Split(s, "\n")
if reflect.DeepEqual(lines, d.lines) {
return nil
}
d.lines = lines
d.fireResourceChanged(d.lines, d.filter(d.query, d.lines))

return nil
}

// Describe describes a given resource.
func (d *Describe) describe(ctx context.Context, resource, path string) (string, error) {
meta, err := getMeta(ctx, resource)
if err != nil {
return "", err
}
desc, ok := meta.DAO.(dao.Describer)
if !ok {
return "", fmt.Errorf("no describer")
}

return desc.Describe(path)
}

// AddListener adds a new model listener.
func (d *Describe) AddListener(l ResourceViewerListener) {
d.listeners = append(d.listeners, l)
}

// RemoveListener delete a listener from the list.
func (d *Describe) RemoveListener(l ResourceViewerListener) {
victim := -1
for i, lis := range d.listeners {
if lis == l {
victim = i
break
}
}

if victim >= 0 {
d.listeners = append(d.listeners[:victim], d.listeners[victim+1:]...)
}
}
14 changes: 14 additions & 0 deletions internal/model/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package model

import (
"context"
"time"

"github.com/cenkalti/backoff"
)

func NewExpBackOff(ctx context.Context, start, max time.Duration) backoff.BackOffContext {
bf := backoff.NewExponentialBackOff()
bf.InitialInterval, bf.MaxElapsedTime = start, max
return backoff.WithContext(bf, ctx)
}
20 changes: 20 additions & 0 deletions internal/model/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/derailed/tview"
"github.com/one2nc/cloud-lens/internal/dao"
"github.com/one2nc/cloud-lens/internal/render"
"github.com/sahilm/fuzzy"
)

const (
Expand Down Expand Up @@ -61,3 +62,22 @@ type ResourceMeta struct {
DAO dao.Accessor
Renderer Renderer
}

type ResourceViewerListener interface {
ResourceChanged(lines []string, matches fuzzy.Matches)
ResourceFailed(error)
}

type ViewerToggleOpts map[string]bool

type ResourceViewer interface {
GetPath() string
Filter(string)
ClearFilter()
Peek() []string
SetOptions(context.Context, ViewerToggleOpts)
Watch(context.Context) error
Refresh(context.Context) error
AddListener(ResourceViewerListener)
RemoveListener(ResourceViewerListener)
}
10 changes: 10 additions & 0 deletions internal/ui/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/one2nc/cloud-lens/internal/dao"
"github.com/one2nc/cloud-lens/internal/model"
"github.com/one2nc/cloud-lens/internal/render"
"github.com/sahilm/fuzzy"
)

type (
Expand Down Expand Up @@ -54,3 +55,12 @@ type Tabular interface {
// RemoveListener unregister a model listener.
RemoveListener(model.TableListener)
}

type Viewer interface {
Lister
}

type ResourceViewerListener interface {
ResourceChanged(lines []string, matches fuzzy.Matches)
ResourceFailed(error)
}
Loading

0 comments on commit 0ad022e

Please sign in to comment.