Skip to content

Commit

Permalink
feat: reorganise notifications into a map that allow modifications
Browse files Browse the repository at this point in the history
I couldn't find a way to update notifications in-place since jq requires
constant unmarshalling/re-marshalling. For now the notifications will
be mapped by Id and copied whenever changed. It's not as efficient but
it works.
  • Loading branch information
nobe4 committed May 18, 2024
1 parent 7733d42 commit 0b7cb53
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 112 deletions.
33 changes: 10 additions & 23 deletions cmd/gh-not/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ package main

import (
"fmt"
"log"
"time"

"github.com/nobe4/gh-not/internal/actors"
"github.com/nobe4/gh-not/internal/cache"
"github.com/nobe4/gh-not/internal/config"
"github.com/nobe4/gh-not/internal/gh"
"github.com/nobe4/gh-not/internal/jq"
"github.com/nobe4/gh-not/internal/notifications"
)

const CacheTTL = time.Hour * 4
Expand All @@ -28,37 +25,27 @@ func main() {
panic(err)
}

fmt.Printf("all notifications %v\n", allNotifications)

filteredNotifications := []notifications.Notification{}
fmt.Println(allNotifications.ToString())

actorsMap := map[string]actors.Actor{
"debug": &actors.DebugActor{},
"print": &actors.PrintActor{},
"hide": &actors.HideActor{},
}

config, err := config.Load("config.json")
config, err := config.New("config.json")
if err != nil {
panic(err)
}

for _, group := range config.Groups {
for _, filter := range group.Filters {
selectedNotifications, err := jq.Filter(filter, allNotifications)
if err != nil {
panic(err)
}
allNotifications, err = config.Apply(allNotifications, actorsMap)
if err != nil {
panic(err)
}

filteredNotifications = append(filteredNotifications, selectedNotifications...)
}
filteredNotifications = notifications.Uniq(filteredNotifications)
fmt.Println(allNotifications.ToString())

for _, notification := range filteredNotifications {
if actor, ok := actorsMap[group.Action]; ok == true {
actor.Run(notification)
} else {
log.Fatalf("unknown action '%s'", group.Action)
}
}
if err := cache.Write(allNotifications); err != nil {
panic(err)
}
}
26 changes: 19 additions & 7 deletions internal/actors/actors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,31 @@ import (
)

type Actor interface {
Run(notifications.Notification) error
Run(notifications.Notification) (notifications.Notification, error)
}

type DebugActor struct{}

func (_ *DebugActor) Run(n notifications.Notification) error {
fmt.Printf("DEBUG Run %#v\n", n)
return nil
func (_ *DebugActor) Run(n notifications.Notification) (notifications.Notification, error) {
fmt.Printf("DEBUG Run %s\n", n.ToString())
return n, nil
}

type PrintActor struct{}

func (_ *PrintActor) Run(n notifications.Notification) error {
fmt.Printf("%#v\n", n)
return nil
func (_ *PrintActor) Run(n notifications.Notification) (notifications.Notification, error) {
if n.Meta.Hidden {
return n, nil
}

fmt.Println(n.ToString())
return n, nil
}

type HideActor struct{}

func (_ *HideActor) Run(n notifications.Notification) (notifications.Notification, error) {
// fmt.Printf("HIDDING %s\n", n.ToString())
n.Meta.Hidden = !n.Meta.Hidden
return n, nil
}
31 changes: 22 additions & 9 deletions internal/cache/cache.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package cache

import (
"encoding/json"
"errors"
"os"
"time"

"github.com/nobe4/gh-not/internal/notifications"
)

type ExpiringReadWriter interface {
Read() ([]byte, error)
Write([]byte) error
Read() (notifications.NotificationMap, error)
Write(notifications.NotificationMap) error
Expired() (bool, error)
}

Expand All @@ -24,17 +27,31 @@ func NewFileCache(ttl time.Duration, path string) *FileCache {
}
}

func (c *FileCache) Read() ([]byte, error) {
func (c *FileCache) Read() (notifications.NotificationMap, error) {
content, err := os.ReadFile(c.path)

if err != nil {
if errors.Is(err, os.ErrNotExist) {
return []byte{}, nil
return nil, nil
}
return nil, err
}

return content, nil
notifications := notifications.NotificationMap{}
if err := json.Unmarshal(content, &notifications); err != nil {
return nil, err
}

return notifications, nil
}

func (c *FileCache) Write(n notifications.NotificationMap) error {
marshalled, err := json.Marshal(n)
if err != nil {
return err
}

return os.WriteFile(c.path, marshalled, 0644)
}

func (c *FileCache) Expired() (bool, error) {
Expand All @@ -52,7 +69,3 @@ func (c *FileCache) Expired() (bool, error) {

return expired, nil
}

func (c *FileCache) Write(content []byte) error {
return os.WriteFile(c.path, content, 0644)
}
45 changes: 40 additions & 5 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ package config

import (
"encoding/json"
"log"
"os"

"github.com/nobe4/gh-not/internal/actors"
"github.com/nobe4/gh-not/internal/jq"
"github.com/nobe4/gh-not/internal/notifications"
)

type Config struct {
Expand All @@ -15,17 +20,47 @@ type Group struct {
Action string `json:"action"`
}

func Load(path string) (Config, error) {
var config Config
func New(path string) (*Config, error) {
config := &Config{}

content, err := os.ReadFile(path)
if err != nil {
return config, err
return nil, err
}

if err := json.Unmarshal(content, &config); err != nil {
return config, err
if err := json.Unmarshal(content, config); err != nil {
return nil, err
}

return config, nil
}

func (c *Config) Apply(n notifications.NotificationMap, actors map[string]actors.Actor) (notifications.NotificationMap, error) {
err := error(nil)
for _, group := range c.Groups {
notificationList := n.ToSlice()
selectedNotifications := notifications.Notifications{}

for _, filter := range group.Filters {
selectedNotifications, err = jq.Filter(filter, notificationList)
if err != nil {
return nil, err
}
}

for _, notification := range selectedNotifications {
if actor, ok := actors[group.Action]; ok == true {
notification, err = actor.Run(notification)
if err != nil {
log.Fatalf("action '%s' failed: %v", group.Action, err)
}

n[notification.Id] = notification
} else {
log.Fatalf("unknown action '%s'", group.Action)
}
}
}

return n, nil
}
38 changes: 12 additions & 26 deletions internal/gh/gh.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package gh

import (
"encoding/json"
"fmt"

"github.com/cli/go-gh/v2/pkg/api"
Expand All @@ -26,42 +25,28 @@ func NewClient(cache cache.ExpiringReadWriter) (*Client, error) {
}, err
}

func (c *Client) loadCache() ([]notifications.Notification, bool, error) {
func (c *Client) loadCache() (notifications.NotificationMap, bool, error) {
expired, err := c.cache.Expired()
if err != nil {
return nil, false, err
}

content, err := c.cache.Read()
n, err := c.cache.Read()
if err != nil {
return nil, expired, err
}

notifications := []notifications.Notification{}
if err := json.Unmarshal(content, &notifications); err != nil {
return nil, expired, err
}

return notifications, expired, nil
}

func (c *Client) writeCache(n []notifications.Notification) error {
marshalled, err := json.Marshal(n)
if err != nil {
return err
}

return c.cache.Write(marshalled)
return n, expired, nil
}

func (c *Client) Notifications() ([]notifications.Notification, error) {
allNotifications := []notifications.Notification{}
func (c *Client) Notifications() (notifications.NotificationMap, error) {
allNotifications := make(notifications.NotificationMap)

cachedNotifications, expired, err := c.loadCache()
if err != nil {
fmt.Printf("Error while reading the cache: %#v\n", err)
} else {
allNotifications = append(allNotifications, cachedNotifications...)
} else if cachedNotifications != nil {
allNotifications = cachedNotifications
}

if expired {
Expand All @@ -71,13 +56,14 @@ func (c *Client) Notifications() ([]notifications.Notification, error) {
return nil, err
}

allNotifications = append(allNotifications, pulledNotifications...)
allNotifications.Append(pulledNotifications)

allNotifications = notifications.Uniq(allNotifications)
// This will favor the cached notifications as they are first into the
// slice.
// allNotifications = allNotifications.Uniq()

if err := c.writeCache(allNotifications); err != nil {
if err := c.cache.Write(allNotifications); err != nil {
fmt.Printf("Error while writing the cache: %#v", err)
cachedNotifications = []notifications.Notification{}
}
}

Expand Down
45 changes: 10 additions & 35 deletions internal/jq/jq.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
package jq

import (
"encoding/json"
"fmt"

"github.com/itchyny/gojq"
"github.com/nobe4/gh-not/internal/notifications"
)

// Filter applies a `.[] | select(filter)` on the notifications.
func Filter(filter string, n []notifications.Notification) ([]notifications.Notification, error) {
func Filter(filter string, n notifications.Notifications) (notifications.Notifications, error) {
if filter == "" {
return n, nil
}

query, err := gojq.Parse(fmt.Sprintf(".[] | select(%s)", filter))
if err != nil {
panic(err)
}

notificationsRaw, err := notificationsToInterface(n)
// gojq works only on any data, so we need to convert Notifications to
// interface{}.
// This also gives us back the JSON fields from the API.
notificationsRaw, err := n.ToInterface()
if err != nil {
return nil, err
}
Expand All @@ -37,41 +43,10 @@ func Filter(filter string, n []notifications.Notification) ([]notifications.Noti
fitleredNotificationsRaw = append(fitleredNotificationsRaw, v)
}

filteredNotifications, err := interfaceToNotifications(fitleredNotificationsRaw)
filteredNotifications, err := notifications.FromInterface(fitleredNotificationsRaw)
if err != nil {
return nil, err
}

return filteredNotifications, nil
}

func notificationsToInterface(n []notifications.Notification) (interface{}, error) {
// gojq works only on any data, so we need to convert []Notifications to
// interface{}
// This also gives us back the JSON fields from the API.
marshalled, err := json.Marshal(n)
if err != nil {
return nil, fmt.Errorf("cannot marshal notifications: %w", err)
}

var i interface{}
if err := json.Unmarshal(marshalled, &i); err != nil {
return nil, fmt.Errorf("cannot unmarshal interface: %w", err)
}

return i, nil
}

func interfaceToNotifications(n interface{}) ([]notifications.Notification, error) {
marshalled, err := json.Marshal(n)
if err != nil {
return nil, fmt.Errorf("cannot marshall interface: %w", err)
}

var notifications []notifications.Notification
if err := json.Unmarshal(marshalled, &notifications); err != nil {
return nil, fmt.Errorf("cannot unmarshall into notification: %w", err)
}

return notifications, nil
}
Loading

0 comments on commit 0b7cb53

Please sign in to comment.