mirror of
https://github.com/Luzifer/tmux-collector.git
synced 2024-12-22 22:01:19 +00:00
Initial version
This commit is contained in:
commit
12f462d70d
4 changed files with 272 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
tmux-collector
|
42
config.yml
Normal file
42
config.yml
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
# Must match background color of bar or will look weird
|
||||||
|
base_bg_color: colour235
|
||||||
|
|
||||||
|
# Segments to display (first = left, last = right)
|
||||||
|
segments:
|
||||||
|
|
||||||
|
# Arch Linux: Check for Updates
|
||||||
|
- background_success: colour88
|
||||||
|
foreground_success: colour251
|
||||||
|
command: [bash, -c, 'echo "$(checkupdates | wc -l) Updates" | grep -v "^0 Updates" || true']
|
||||||
|
interval: 10m
|
||||||
|
|
||||||
|
# Dropbox CLI: Check daemon status
|
||||||
|
- background_success: colour19
|
||||||
|
foreground_success: colour251
|
||||||
|
command: [bash, -c, 'dropbox.py status | head -n1 | grep -v "Up to date" || true']
|
||||||
|
interval: 30s
|
||||||
|
prefix: ""
|
||||||
|
|
||||||
|
# System: If present display battery status
|
||||||
|
- background_success: colour195
|
||||||
|
foreground_success: colour237
|
||||||
|
command: [tmux-battery]
|
||||||
|
|
||||||
|
# System: Display a stable subset of the uptime
|
||||||
|
- background_success: colour34
|
||||||
|
foreground_success: colour232
|
||||||
|
command: [bash, -c, 'uptime | cut -f 4-5 -d " " | cut -f 1 -d ","']
|
||||||
|
|
||||||
|
# Time
|
||||||
|
- background_success: colour235
|
||||||
|
foreground_success: colour250
|
||||||
|
command: ['date', '+%a, %H:%M:%S']
|
||||||
|
|
||||||
|
# Date
|
||||||
|
- background_success: colour235
|
||||||
|
foreground_success: colour69
|
||||||
|
command: ['date', '+%Y-%m-%d (KW %V)']
|
||||||
|
|
||||||
|
...
|
94
entry.go
Normal file
94
entry.go
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
homedir "github.com/mitchellh/go-homedir"
|
||||||
|
)
|
||||||
|
|
||||||
|
type config struct {
|
||||||
|
BaseBgColor string `yaml:"base_bg_color" json:"base_bg_color"`
|
||||||
|
Segments []*segment `yaml:"segments" json:"segments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type segment struct {
|
||||||
|
BackgroundError string `yaml:"background_error" json:"background_error"`
|
||||||
|
BackgroundSuccess string `yaml:"background_success" json:"background_success"`
|
||||||
|
ForegroundError string `yaml:"foreground_error" json:"foreground_error"`
|
||||||
|
ForegroundSuccess string `yaml:"foreground_success" json:"foreground_success"`
|
||||||
|
Command []string `yaml:"command" json:"command"`
|
||||||
|
Prefix string `yaml:"prefix" json:"prefix"`
|
||||||
|
Interval time.Duration `yaml:"interval" json:"interval"`
|
||||||
|
|
||||||
|
Output string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s segment) cacheKey() string {
|
||||||
|
return fmt.Sprintf("%x.json", sha256.Sum256([]byte(strings.Join(s.Command, " "))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s segment) storeCache() error {
|
||||||
|
if s.Interval == 0 {
|
||||||
|
// No interval defined = execute all the time, no caching
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := homedir.Expand(path.Join("~", ".cache", "tmux-collector", s.cacheKey()))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = os.MkdirAll(path.Dir(p), 0700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fh, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
return json.NewEncoder(fh).Encode(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *segment) loadCache() (bool, error) {
|
||||||
|
if s.Interval == 0 {
|
||||||
|
// No interval defined = execute all the time, no caching
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := homedir.Expand(path.Join("~", ".cache", "tmux-collector", s.cacheKey()))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := os.Stat(p)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if time.Since(stat.ModTime()) > s.Interval {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fh, err := os.Open(p)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
if err = json.NewDecoder(fh).Decode(s); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
135
main.go
Normal file
135
main.go
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/Luzifer/rconfig"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfg = struct {
|
||||||
|
Config string `flag:"config,c" default:"config.yml" description:"Configuration file"`
|
||||||
|
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
||||||
|
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
version = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
||||||
|
log.Fatalf("Unable to parse commandline options: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.VersionAndExit {
|
||||||
|
fmt.Printf("tmux-collector %s\n", version)
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l, err := log.ParseLevel(cfg.LogLevel); err != nil {
|
||||||
|
log.WithError(err).Fatal("Unable to parse log level")
|
||||||
|
} else {
|
||||||
|
log.SetLevel(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
conf, err := loadConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Fatal("Unable to load config")
|
||||||
|
}
|
||||||
|
|
||||||
|
wg := &sync.WaitGroup{}
|
||||||
|
wg.Add(len(conf.Segments))
|
||||||
|
for _, seg := range conf.Segments {
|
||||||
|
go executeSegment(seg, wg)
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
lastBgColor := conf.BaseBgColor
|
||||||
|
output := []string{}
|
||||||
|
|
||||||
|
for _, seg := range conf.Segments {
|
||||||
|
bg := seg.BackgroundSuccess
|
||||||
|
fg := seg.ForegroundSuccess
|
||||||
|
|
||||||
|
if seg.Err != nil {
|
||||||
|
if seg.BackgroundError != "" {
|
||||||
|
bg = seg.BackgroundError
|
||||||
|
}
|
||||||
|
if seg.ForegroundError != "" {
|
||||||
|
fg = seg.ForegroundError
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(seg.Output) == "" {
|
||||||
|
seg.Output = seg.Err.Error()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(seg.Output) == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if seg.Prefix != "" {
|
||||||
|
seg.Output = strings.Join([]string{seg.Prefix, seg.Output}, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if lastBgColor != bg {
|
||||||
|
output = append(output, fmt.Sprintf(" #[fg=%s,bg=%s]#[fg=%s,bg=%s] ",
|
||||||
|
bg, lastBgColor, fg, bg,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
output = append(output, fmt.Sprintf("#[fg=%s,bg=%s] ",
|
||||||
|
fg, bg,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
output = append(output, seg.Output)
|
||||||
|
lastBgColor = bg
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(strings.Join(output, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeSegment(seg *segment, wg *sync.WaitGroup) {
|
||||||
|
defer wg.Done()
|
||||||
|
|
||||||
|
loaded, err := seg.loadCache()
|
||||||
|
if err != nil {
|
||||||
|
log.WithError(err).Error("Unable to load cache")
|
||||||
|
}
|
||||||
|
if loaded {
|
||||||
|
log.WithField("command", strings.Join(seg.Command, " ")).Debug("Loaded from cache")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
cmd := exec.Command(seg.Command[0], seg.Command[1:]...)
|
||||||
|
cmd.Stdout = buf
|
||||||
|
|
||||||
|
seg.Err = cmd.Run()
|
||||||
|
seg.Output = strings.Split(buf.String(), "\n")[0]
|
||||||
|
log.WithField("command", strings.Join(seg.Command, " ")).Debug("Freshly loaded")
|
||||||
|
|
||||||
|
if err = seg.storeCache(); err != nil {
|
||||||
|
log.WithError(err).Error("Unable to store cache")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig() (*config, error) {
|
||||||
|
f, err := os.Open(cfg.Config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
out := &config{}
|
||||||
|
return out, yaml.NewDecoder(f).Decode(out)
|
||||||
|
}
|
Loading…
Reference in a new issue