back up project
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
)
|
||||
|
||||
type themePair struct {
|
||||
Name string `toml:"name"`
|
||||
Dark string `toml:"dark"`
|
||||
Light string `toml:"light"`
|
||||
}
|
||||
type storedCatalog struct {
|
||||
Pairs []themePair `toml:"pairs"`
|
||||
Themes []string `toml:"themes"`
|
||||
}
|
||||
|
||||
type currentPair struct {
|
||||
Light string `toml:"light"`
|
||||
Dark string `toml:"dark"`
|
||||
}
|
||||
|
||||
type catalog struct {
|
||||
FavoritePairs []themePair
|
||||
FavoriteThemes []string
|
||||
OtherPairs []themePair
|
||||
OtherThemes []string
|
||||
}
|
||||
|
||||
type state struct {
|
||||
Current currentPair
|
||||
Catalog catalog
|
||||
}
|
||||
|
||||
type storedState struct {
|
||||
Current currentPair `toml:"current"`
|
||||
Favorites storedCatalog `toml:"favorites"`
|
||||
Others storedCatalog `toml:"others"`
|
||||
}
|
||||
|
||||
func defaultStatePath() string {
|
||||
if path := os.Getenv("TINT_STATE_FILE"); path != "" {
|
||||
return path
|
||||
}
|
||||
config := os.Getenv("XDG_CONFIG_HOME")
|
||||
if config == "" {
|
||||
config = filepath.Join(homeDir(), ".config")
|
||||
}
|
||||
return filepath.Join(config, "tinted-theming", "tinty", "state.toml")
|
||||
}
|
||||
|
||||
func loadState(path string) (state, error) {
|
||||
var stored storedState
|
||||
metadata, err := toml.DecodeFile(path, &stored)
|
||||
if err != nil {
|
||||
return state{}, err
|
||||
}
|
||||
if unknown := metadata.Undecoded(); len(unknown) != 0 {
|
||||
return state{}, fmt.Errorf("unknown state keys: %s", unknownKeys(unknown))
|
||||
}
|
||||
value := state{
|
||||
Current: stored.Current,
|
||||
Catalog: catalog{
|
||||
FavoritePairs: stored.Favorites.Pairs,
|
||||
FavoriteThemes: stored.Favorites.Themes,
|
||||
OtherPairs: stored.Others.Pairs,
|
||||
OtherThemes: stored.Others.Themes,
|
||||
},
|
||||
}
|
||||
value.Catalog.normalize()
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func saveState(path string, value state) error {
|
||||
return writeAtomic(path, []byte(formatState(value)), 0o644)
|
||||
}
|
||||
|
||||
func formatState(value state) string {
|
||||
value.Catalog.normalize()
|
||||
var output strings.Builder
|
||||
fmt.Fprintf(&output, "[current]\nlight = %s\ndark = %s\n\n",
|
||||
strconv.Quote(value.Current.Light), strconv.Quote(value.Current.Dark))
|
||||
writePairs := func(name string, pairs []themePair) {
|
||||
for _, pair := range pairs {
|
||||
fmt.Fprintf(&output, "[[%s]]\nname = %s\ndark = %s\nlight = %s\n\n",
|
||||
name, strconv.Quote(pair.Name), strconv.Quote(pair.Dark), strconv.Quote(pair.Light))
|
||||
}
|
||||
}
|
||||
writeThemes := func(name string, themes []string) {
|
||||
fmt.Fprintf(&output, "[%s]\nthemes = [\n", name)
|
||||
for _, theme := range themes {
|
||||
fmt.Fprintf(&output, "%s,\n", strconv.Quote(theme))
|
||||
}
|
||||
output.WriteString("]\n\n")
|
||||
}
|
||||
writePairs("favorites.pairs", value.Catalog.FavoritePairs)
|
||||
writeThemes("favorites", value.Catalog.FavoriteThemes)
|
||||
writePairs("others.pairs", value.Catalog.OtherPairs)
|
||||
writeThemes("others", value.Catalog.OtherThemes)
|
||||
return strings.TrimRight(output.String(), "\n") + "\n"
|
||||
}
|
||||
|
||||
func unknownKeys(keys []toml.Key) string {
|
||||
values := make([]string, len(keys))
|
||||
for i, key := range keys {
|
||||
values[i] = key.String()
|
||||
}
|
||||
return strings.Join(values, ", ")
|
||||
}
|
||||
|
||||
func (c *catalog) normalize() {
|
||||
c.FavoritePairs = uniquePairs(c.FavoritePairs)
|
||||
favoritePairNames := make(map[string]bool, len(c.FavoritePairs))
|
||||
for _, pair := range c.FavoritePairs {
|
||||
favoritePairNames[pair.Name] = true
|
||||
}
|
||||
otherPairs := uniquePairs(c.OtherPairs)
|
||||
c.OtherPairs = c.OtherPairs[:0]
|
||||
for _, pair := range otherPairs {
|
||||
if !favoritePairNames[pair.Name] {
|
||||
c.OtherPairs = append(c.OtherPairs, pair)
|
||||
}
|
||||
}
|
||||
|
||||
c.FavoriteThemes = uniqueStrings(c.FavoriteThemes)
|
||||
blocked := make(map[string]bool)
|
||||
for _, theme := range c.FavoriteThemes {
|
||||
blocked[theme] = true
|
||||
}
|
||||
for _, pair := range append(slices.Clone(c.FavoritePairs), c.OtherPairs...) {
|
||||
blocked[pair.Dark] = true
|
||||
blocked[pair.Light] = true
|
||||
}
|
||||
otherThemes := uniqueStrings(c.OtherThemes)
|
||||
c.OtherThemes = c.OtherThemes[:0]
|
||||
for _, theme := range otherThemes {
|
||||
if !blocked[theme] {
|
||||
c.OtherThemes = append(c.OtherThemes, theme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func uniquePairs(pairs []themePair) []themePair {
|
||||
byName := make(map[string]themePair, len(pairs))
|
||||
for _, pair := range pairs {
|
||||
byName[pair.Name] = pair
|
||||
}
|
||||
result := make([]themePair, 0, len(byName))
|
||||
for _, pair := range byName {
|
||||
result = append(result, pair)
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
|
||||
return result
|
||||
}
|
||||
|
||||
func uniqueStrings(values []string) []string {
|
||||
seen := make(map[string]bool, len(values))
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if value != "" && !seen[value] {
|
||||
seen[value] = true
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *catalog) findPair(name string) (themePair, bool, bool) {
|
||||
for _, pair := range c.FavoritePairs {
|
||||
if pair.Name == name {
|
||||
return pair, true, true
|
||||
}
|
||||
}
|
||||
for _, pair := range c.OtherPairs {
|
||||
if pair.Name == name {
|
||||
return pair, false, true
|
||||
}
|
||||
}
|
||||
return themePair{}, false, false
|
||||
}
|
||||
|
||||
func (c *catalog) configurePair(pair themePair) {
|
||||
_, favorite, exists := c.findPair(pair.Name)
|
||||
if exists {
|
||||
for _, old := range append(slices.Clone(c.FavoritePairs), c.OtherPairs...) {
|
||||
if old.Name == pair.Name {
|
||||
c.OtherThemes = append(c.OtherThemes, old.Dark, old.Light)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.FavoritePairs = removePair(c.FavoritePairs, pair.Name)
|
||||
c.OtherPairs = removePair(c.OtherPairs, pair.Name)
|
||||
if favorite {
|
||||
c.FavoritePairs = append(c.FavoritePairs, pair)
|
||||
} else {
|
||||
c.OtherPairs = append(c.OtherPairs, pair)
|
||||
}
|
||||
c.normalize()
|
||||
}
|
||||
|
||||
func (c *catalog) favorite(item string) bool {
|
||||
pair, _, found := c.findPair(item)
|
||||
if found {
|
||||
c.OtherPairs = removePair(c.OtherPairs, item)
|
||||
c.FavoritePairs = append(c.FavoritePairs, pair)
|
||||
c.normalize()
|
||||
return true
|
||||
}
|
||||
c.OtherThemes = removeString(c.OtherThemes, item)
|
||||
c.FavoriteThemes = append(c.FavoriteThemes, item)
|
||||
c.normalize()
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *catalog) unfavorite(item string) bool {
|
||||
pair, _, found := c.findPair(item)
|
||||
if found {
|
||||
c.FavoritePairs = removePair(c.FavoritePairs, item)
|
||||
c.OtherPairs = append(c.OtherPairs, pair)
|
||||
c.normalize()
|
||||
return true
|
||||
}
|
||||
c.FavoriteThemes = removeString(c.FavoriteThemes, item)
|
||||
c.OtherThemes = append(c.OtherThemes, item)
|
||||
c.normalize()
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *catalog) record(pair currentPair) {
|
||||
c.OtherThemes = append(c.OtherThemes, pair.Light, pair.Dark)
|
||||
c.normalize()
|
||||
}
|
||||
|
||||
func removePair(pairs []themePair, name string) []themePair {
|
||||
result := pairs[:0]
|
||||
for _, pair := range pairs {
|
||||
if pair.Name != name {
|
||||
result = append(result, pair)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func removeString(values []string, target string) []string {
|
||||
result := values[:0]
|
||||
for _, value := range values {
|
||||
if value != target {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func writeAtomic(path string, data []byte, mode os.FileMode) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := os.CreateTemp(filepath.Dir(path), ".tint-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmp := file.Name()
|
||||
defer os.Remove(tmp)
|
||||
if _, err := file.Write(data); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
if err := file.Chmod(mode); err != nil {
|
||||
file.Close()
|
||||
return err
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, path)
|
||||
}
|
||||
|
||||
func homeDir() string {
|
||||
if home := os.Getenv("HOME"); home != "" {
|
||||
return home
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return home
|
||||
}
|
||||
Reference in New Issue
Block a user