You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1591 lines
42 KiB
1591 lines
42 KiB
package config |
|
|
|
import ( |
|
"embed" |
|
"errors" |
|
"fmt" |
|
"io" |
|
"log" |
|
"os" |
|
"path/filepath" |
|
"regexp" |
|
"strings" |
|
"sync" |
|
"text/template" |
|
|
|
"github.com/RasmusLindroth/tut/util" |
|
"github.com/gdamore/tcell/v2" |
|
"github.com/gobwas/glob" |
|
"github.com/pelletier/go-toml/v2" |
|
"gopkg.in/ini.v1" |
|
) |
|
|
|
//go:embed toot.tmpl |
|
var tootTemplate string |
|
|
|
//go:embed user.tmpl |
|
var userTemplate string |
|
|
|
//go:embed help.tmpl |
|
var helpTemplate string |
|
|
|
//go:embed themes/* |
|
var themesFS embed.FS |
|
|
|
type Config struct { |
|
General General |
|
Style Style |
|
Media Media |
|
OpenPattern OpenPattern |
|
OpenCustom OpenCustom |
|
NotificationConfig Notification |
|
Templates Templates |
|
Input Input |
|
} |
|
|
|
type LeaderAction struct { |
|
Command LeaderCommand |
|
Subaction string |
|
Shortcut string |
|
} |
|
|
|
type LeaderCommand uint |
|
|
|
const ( |
|
LeaderNone LeaderCommand = iota |
|
LeaderClearNotifications |
|
LeaderCompose |
|
LeaderEdit |
|
LeaderBlocking |
|
LeaderFavorited |
|
LeaderBoosts |
|
LeaderFavorites |
|
LeaderFollowing |
|
LeaderFollowers |
|
LeaderTags |
|
LeaderListPlacement |
|
LeaderListSplit |
|
LeaderMuting |
|
LeaderPreferences |
|
LeaderProfile |
|
LeaderProportions |
|
LeaderMentions |
|
LeaderRefetch |
|
LeaderStickToTop |
|
LeaderHistory |
|
LeaderLoadNewer |
|
LeaderPane |
|
LeaderClosePane |
|
LeaderMovePaneLeft |
|
LeaderMovePaneRight |
|
LeaderMovePaneHome |
|
LeaderMovePaneEnd |
|
) |
|
|
|
type FeedType uint |
|
|
|
const ( |
|
Favorites FeedType = iota |
|
Favorited |
|
Boosts |
|
Followers |
|
Following |
|
FollowRequests |
|
Blocking |
|
Muting |
|
History |
|
InvalidFeed |
|
Notifications |
|
Saved |
|
Tag |
|
Tags |
|
Thread |
|
TimelineFederated |
|
TimelineHome |
|
TimelineHomeSpecial |
|
TimelineLocal |
|
Mentions |
|
Conversations |
|
User |
|
UserList |
|
Lists |
|
List |
|
ListUsersIn |
|
ListUsersAdd |
|
) |
|
|
|
type NotificationToHide string |
|
|
|
const ( |
|
HideMention NotificationToHide = "mention" |
|
HideStatus NotificationToHide = "status" |
|
HideBoost NotificationToHide = "reblog" |
|
HideFollow NotificationToHide = "follow" |
|
HideFollowRequest NotificationToHide = "follow_request" |
|
HideFavorite NotificationToHide = "favourite" |
|
HidePoll NotificationToHide = "poll" |
|
HideEdited NotificationToHide = "update" |
|
) |
|
|
|
var timelineID uint = 0 |
|
var timelineIDMux sync.Mutex |
|
|
|
func newTimelineID() uint { |
|
timelineIDMux.Lock() |
|
defer timelineIDMux.Unlock() |
|
timelineID = timelineID + 1 |
|
return timelineID |
|
} |
|
|
|
func NewTimeline(tl Timeline) *Timeline { |
|
tl.ID = newTimelineID() |
|
return &tl |
|
} |
|
|
|
type OnTimelineFocus uint |
|
|
|
const ( |
|
TimelineFocusPane OnTimelineFocus = iota |
|
TimelineFocusTimeline |
|
) |
|
|
|
type OnTimelineCreationClosed uint |
|
|
|
const ( |
|
TimelineCreationClosedNewPane OnTimelineCreationClosed = iota |
|
TimelineCreationClosedCurrentPane |
|
) |
|
|
|
type Timeline struct { |
|
ID uint |
|
FeedType FeedType |
|
Subaction string |
|
Name string |
|
Key Key |
|
Shortcut string |
|
HideBoosts bool |
|
HideReplies bool |
|
|
|
Closed bool |
|
OnFocus OnTimelineFocus |
|
OnCreationClosed OnTimelineCreationClosed |
|
} |
|
|
|
type General struct { |
|
Editor string |
|
UseInternalEditor bool |
|
Confirmation bool |
|
MouseSupport bool |
|
DateTodayFormat string |
|
DateFormat string |
|
DateRelative int |
|
MaxWidth int |
|
QuoteReply bool |
|
ShortHints bool |
|
ShowFilterPhrase bool |
|
ListPlacement ListPlacement |
|
ListSplit ListSplit |
|
ListProportion int |
|
ContentProportion int |
|
TerminalTitle int |
|
ShowIcons bool |
|
ShowHelp bool |
|
RedrawUI bool |
|
LeaderKey rune |
|
LeaderTimeout int64 |
|
LeaderActions []LeaderAction |
|
Timelines []*Timeline |
|
StickToTop bool |
|
NotificationsToHide []NotificationToHide |
|
ShowBoostedUser bool |
|
} |
|
|
|
type Style struct { |
|
Theme string |
|
|
|
Background tcell.Color |
|
Text tcell.Color |
|
|
|
Subtle tcell.Color |
|
WarningText tcell.Color |
|
|
|
TextSpecial1 tcell.Color |
|
TextSpecial2 tcell.Color |
|
|
|
TopBarBackground tcell.Color |
|
TopBarText tcell.Color |
|
|
|
StatusBarBackground tcell.Color |
|
StatusBarText tcell.Color |
|
|
|
StatusBarViewBackground tcell.Color |
|
StatusBarViewText tcell.Color |
|
|
|
ListSelectedBackground tcell.Color |
|
ListSelectedText tcell.Color |
|
|
|
ListSelectedInactiveBackground tcell.Color |
|
ListSelectedInactiveText tcell.Color |
|
|
|
ControlsText tcell.Color |
|
ControlsHighlight tcell.Color |
|
|
|
AutocompleteBackground tcell.Color |
|
AutocompleteText tcell.Color |
|
|
|
AutocompleteSelectedBackground tcell.Color |
|
AutocompleteSelectedText tcell.Color |
|
|
|
ButtonColorOne tcell.Color |
|
ButtonColorTwo tcell.Color |
|
|
|
TimelineNameBackground tcell.Color |
|
TimelineNameText tcell.Color |
|
|
|
IconColor tcell.Color |
|
|
|
CommandText tcell.Color |
|
} |
|
|
|
type Media struct { |
|
ImageViewer string |
|
ImageArgs []string |
|
ImageTerminal bool |
|
ImageSingle bool |
|
ImageReverse bool |
|
VideoViewer string |
|
VideoArgs []string |
|
VideoTerminal bool |
|
VideoSingle bool |
|
VideoReverse bool |
|
AudioViewer string |
|
AudioArgs []string |
|
AudioTerminal bool |
|
AudioSingle bool |
|
AudioReverse bool |
|
LinkViewer string |
|
LinkArgs []string |
|
LinkTerminal bool |
|
} |
|
|
|
type Pattern struct { |
|
Compiled glob.Glob |
|
Program string |
|
Args []string |
|
Terminal bool |
|
} |
|
|
|
type OpenPattern struct { |
|
Patterns []Pattern |
|
} |
|
|
|
type Custom struct { |
|
Index int |
|
Name string |
|
Program string |
|
Args []string |
|
Terminal bool |
|
Key Key |
|
} |
|
type OpenCustom struct { |
|
OpenCustoms []Custom |
|
} |
|
|
|
type ListPlacement uint |
|
|
|
const ( |
|
ListPlacementTop ListPlacement = iota |
|
ListPlacementBottom |
|
ListPlacementLeft |
|
ListPlacementRight |
|
) |
|
|
|
type ListSplit uint |
|
|
|
const ( |
|
ListRow ListSplit = iota |
|
ListColumn |
|
) |
|
|
|
type NotificationType uint |
|
|
|
const ( |
|
NotificationFollower NotificationType = iota |
|
NotificationFavorite |
|
NotificationMention |
|
NotificationUpdate |
|
NotificationBoost |
|
NotificationPoll |
|
NotificationPost |
|
) |
|
|
|
type Notification struct { |
|
NotificationFollower bool |
|
NotificationFavorite bool |
|
NotificationMention bool |
|
NotificationUpdate bool |
|
NotificationBoost bool |
|
NotificationPoll bool |
|
NotificationPost bool |
|
} |
|
|
|
type Templates struct { |
|
Toot *template.Template |
|
User *template.Template |
|
Help *template.Template |
|
} |
|
|
|
func NilDefaultBool(x *bool, def *bool) bool { |
|
if x == nil { |
|
return *def |
|
} |
|
return *x |
|
} |
|
|
|
func NilDefaultString(x *string, def *string) string { |
|
if x == nil { |
|
return *def |
|
} |
|
return *x |
|
} |
|
|
|
func NilDefaultInt(x *int, def *int) int { |
|
if x == nil { |
|
return *def |
|
} |
|
return *x |
|
} |
|
|
|
func NilDefaultInt64(x *int64, def *int64) int64 { |
|
if x == nil { |
|
return *def |
|
} |
|
return *x |
|
} |
|
|
|
var keyMatch = regexp.MustCompile(`^(.*?)\[(.*?)\](.*?)$`) |
|
|
|
func newHint(s string) []string { |
|
matches := keyMatch.FindAllStringSubmatch(s, -1) |
|
if len(matches) == 0 { |
|
return []string{"", "", ""} |
|
} |
|
if len(matches[0]) != 4 { |
|
return []string{"", "", ""} |
|
} |
|
return []string{matches[0][1], matches[0][2], matches[0][3]} |
|
} |
|
|
|
func NewKeyT(hint string, hintAlt string, keys []string, special []string) (Key, error) { |
|
k := Key{} |
|
if len(hint) > 0 && len(hintAlt) > 0 { |
|
k.Hint = [][]string{newHint(hint), newHint(hintAlt)} |
|
} else if len(hint) > 0 { |
|
k.Hint = [][]string{newHint(hint), newHint(hintAlt)} |
|
} |
|
var runes []rune |
|
var keysTcell []tcell.Key |
|
for _, r := range keys { |
|
if len(r) > 1 { |
|
return k, fmt.Errorf("key %s can only be one char", r) |
|
} |
|
if len(r) == 0 { |
|
continue |
|
} |
|
runes = append(runes, rune(r[0])) |
|
} |
|
for _, s := range special { |
|
found := false |
|
var fk tcell.Key |
|
for tk, tv := range tcell.KeyNames { |
|
if tv == s { |
|
found = true |
|
fk = tk |
|
break |
|
} |
|
} |
|
if found { |
|
keysTcell = append(keysTcell, fk) |
|
} else { |
|
return k, fmt.Errorf("no key named %s", s) |
|
} |
|
} |
|
k.Runes = runes |
|
k.Keys = keysTcell |
|
|
|
return k, nil |
|
} |
|
|
|
func NewKey(s []string, double bool) (Key, error) { |
|
var k Key |
|
if len(s) < 2 { |
|
return k, errors.New("key must have a minimum length of 2") |
|
} |
|
var start int |
|
if double { |
|
start = 1 |
|
k = Key{ |
|
Hint: [][]string{newHint(s[0]), newHint(s[1])}, |
|
} |
|
} else { |
|
start = 0 |
|
k = Key{ |
|
Hint: [][]string{newHint(s[0])}, |
|
} |
|
} |
|
var runes []rune |
|
var keys []tcell.Key |
|
for _, v := range s[start+1:] { |
|
value := []rune(strings.TrimSpace(v)) |
|
if len(value) < 3 { |
|
return k, errors.New("key value must have a minimum length of 3") |
|
} |
|
if value[0] == '\'' { |
|
if len(value) != 3 { |
|
return k, fmt.Errorf("rune %s must only contain one char", string(value)) |
|
} |
|
runes = append(runes, value[1]) |
|
} else if value[0] == '"' { |
|
if value[len(value)-1] != '"' { |
|
return k, fmt.Errorf("key %s must end with \"", string(value)) |
|
} |
|
keyName := string(value[1 : len(value)-1]) |
|
found := false |
|
var fk tcell.Key |
|
for tk, tv := range tcell.KeyNames { |
|
if tv == keyName { |
|
found = true |
|
fk = tk |
|
break |
|
} |
|
} |
|
if found { |
|
keys = append(keys, fk) |
|
} else { |
|
return k, fmt.Errorf("no key named %s", keyName) |
|
} |
|
} else { |
|
return k, fmt.Errorf("input %s is in the wrong format", string(value)) |
|
} |
|
} |
|
k.Runes = runes |
|
k.Keys = keys |
|
|
|
return k, nil |
|
} |
|
|
|
type Key struct { |
|
Hint [][]string |
|
Runes []rune |
|
Keys []tcell.Key |
|
} |
|
|
|
func (k Key) Match(kb tcell.Key, rb rune) bool { |
|
for _, ka := range k.Keys { |
|
if ka == kb { |
|
return true |
|
} |
|
} |
|
for _, ra := range k.Runes { |
|
if ra == rb { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
type Input struct { |
|
GlobalDown Key |
|
GlobalUp Key |
|
GlobalEnter Key |
|
GlobalBack Key |
|
GlobalExit Key |
|
|
|
MainHome Key |
|
MainEnd Key |
|
MainPrevFeed Key |
|
MainNextFeed Key |
|
MainPrevPane Key |
|
MainNextPane Key |
|
MainCompose Key |
|
MainNextAccount Key |
|
MainPrevAccount Key |
|
|
|
StatusAvatar Key |
|
StatusBoost Key |
|
StatusDelete Key |
|
StatusEdit Key |
|
StatusFavorite Key |
|
StatusMedia Key |
|
StatusLinks Key |
|
StatusPoll Key |
|
StatusReply Key |
|
StatusBookmark Key |
|
StatusThread Key |
|
StatusUser Key |
|
StatusViewFocus Key |
|
StatusYank Key |
|
StatusToggleCW Key |
|
StatusShowFiltered Key |
|
|
|
UserAvatar Key |
|
UserBlock Key |
|
UserFollow Key |
|
UserFollowRequestDecide Key |
|
UserMute Key |
|
UserLinks Key |
|
UserUser Key |
|
UserViewFocus Key |
|
UserYank Key |
|
|
|
ListOpenFeed Key |
|
ListUserList Key |
|
ListUserAdd Key |
|
ListUserDelete Key |
|
|
|
TagOpenFeed Key |
|
TagFollow Key |
|
|
|
LinkOpen Key |
|
LinkYank Key |
|
|
|
ComposeEditCW Key |
|
ComposeEditText Key |
|
ComposeIncludeQuote Key |
|
ComposeMediaFocus Key |
|
ComposePost Key |
|
ComposeToggleContentWarning Key |
|
ComposeVisibility Key |
|
ComposeLanguage Key |
|
ComposePoll Key |
|
|
|
MediaDelete Key |
|
MediaEditDesc Key |
|
MediaAdd Key |
|
|
|
VoteVote Key |
|
VoteSelect Key |
|
|
|
PollAdd Key |
|
PollEdit Key |
|
PollDelete Key |
|
PollMultiToggle Key |
|
PollExpiration Key |
|
|
|
PreferenceName Key |
|
PreferenceVisibility Key |
|
PreferenceBio Key |
|
PreferenceSave Key |
|
PreferenceFields Key |
|
PreferenceFieldsAdd Key |
|
PreferenceFieldsEdit Key |
|
PreferenceFieldsDelete Key |
|
|
|
EditorExit Key |
|
} |
|
|
|
func parseColor(input string, def string, xrdb map[string]string) tcell.Color { |
|
if input == "" { |
|
return tcell.GetColor(def) |
|
} |
|
|
|
if strings.HasPrefix(input, "xrdb:") { |
|
key := strings.TrimPrefix(input, "xrdb:") |
|
if c, ok := xrdb[key]; ok { |
|
return tcell.GetColor(c) |
|
} else { |
|
return tcell.GetColor(def) |
|
} |
|
} |
|
return tcell.GetColor(input) |
|
} |
|
|
|
func parseTheme(cfg StyleTOML, xrdbColors map[string]string) Style { |
|
var style Style |
|
def := ConfigDefault.Style |
|
s := NilDefaultString(cfg.Background, def.Background) |
|
style.Background = parseColor(s, "#27822", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.Text, def.Text) |
|
style.Text = parseColor(s, "#f8f8f2", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.Subtle, def.Subtle) |
|
style.Subtle = parseColor(s, "#808080", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.WarningText, def.WarningText) |
|
style.WarningText = parseColor(s, "#f92672", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.TextSpecial1, def.TextSpecial1) |
|
style.TextSpecial1 = parseColor(s, "#ae81ff", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.TextSpecial2, def.TextSpecial2) |
|
style.TextSpecial2 = parseColor(s, "#a6e22e", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.TopBarBackground, def.TopBarBackground) |
|
style.TopBarBackground = parseColor(s, "#f92672", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.TopBarText, def.TopBarText) |
|
style.TopBarText = parseColor(s, "#f8f8f2", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.StatusBarBackground, def.StatusBarBackground) |
|
style.StatusBarBackground = parseColor(s, "#f92672", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.StatusBarText, def.StatusBarText) |
|
style.StatusBarText = parseColor(s, "#f8f8f3", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.StatusBarViewBackground, def.StatusBarViewBackground) |
|
style.StatusBarViewBackground = parseColor(s, "#ae81ff", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.StatusBarViewText, def.StatusBarViewText) |
|
style.StatusBarViewText = parseColor(s, "#f8f8f2", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.ListSelectedBackground, def.ListSelectedBackground) |
|
style.ListSelectedBackground = parseColor(s, "#f92672", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.ListSelectedText, def.ListSelectedText) |
|
style.ListSelectedText = parseColor(s, "#f8f8f2", xrdbColors) |
|
|
|
s = NilDefaultString(cfg.ListSelectedInactiveBackground, sp("")) |
|
if len(s) > 0 { |
|
style.ListSelectedInactiveBackground = parseColor(s, "#ae81ff", xrdbColors) |
|
} else { |
|
style.ListSelectedInactiveBackground = style.StatusBarViewBackground |
|
} |
|
s = NilDefaultString(cfg.ListSelectedInactiveText, def.ListSelectedInactiveText) |
|
if len(s) > 0 { |
|
style.ListSelectedInactiveText = parseColor(s, "#f8f8f2", xrdbColors) |
|
} else { |
|
style.ListSelectedInactiveText = style.StatusBarViewText |
|
} |
|
|
|
s = NilDefaultString(cfg.ControlsText, sp("")) |
|
if len(s) > 0 { |
|
style.ControlsText = parseColor(s, "#f8f8f2", xrdbColors) |
|
} else { |
|
style.ControlsText = style.Text |
|
} |
|
s = NilDefaultString(cfg.ControlsHighlight, sp("")) |
|
if len(s) > 0 { |
|
style.ControlsHighlight = parseColor(s, "#a6e22e", xrdbColors) |
|
} else { |
|
style.ControlsHighlight = style.TextSpecial2 |
|
} |
|
|
|
s = NilDefaultString(cfg.AutocompleteBackground, sp("")) |
|
if len(s) > 0 { |
|
style.AutocompleteBackground = parseColor(s, "#272822", xrdbColors) |
|
} else { |
|
style.AutocompleteBackground = style.Background |
|
} |
|
s = NilDefaultString(cfg.AutocompleteText, sp("")) |
|
if len(s) > 0 { |
|
style.AutocompleteText = parseColor(s, "#f8f8f2", xrdbColors) |
|
} else { |
|
style.AutocompleteText = style.Text |
|
} |
|
s = NilDefaultString(cfg.AutocompleteSelectedBackground, sp("")) |
|
if len(s) > 0 { |
|
style.AutocompleteSelectedBackground = parseColor(s, "#ae81ff", xrdbColors) |
|
} else { |
|
style.AutocompleteSelectedBackground = style.StatusBarViewBackground |
|
} |
|
s = NilDefaultString(cfg.AutocompleteSelectedText, sp("")) |
|
if len(s) > 0 { |
|
style.AutocompleteSelectedText = parseColor(s, "#f8f8f2", xrdbColors) |
|
} else { |
|
style.AutocompleteSelectedText = style.StatusBarViewText |
|
} |
|
|
|
s = NilDefaultString(cfg.ButtonColorOne, sp("")) |
|
if len(s) > 0 { |
|
style.ButtonColorOne = parseColor(s, "#ae81ff", xrdbColors) |
|
} else { |
|
style.ButtonColorOne = style.StatusBarViewBackground |
|
} |
|
s = NilDefaultString(cfg.ButtonColorTwo, sp("")) |
|
if len(s) > 0 { |
|
style.ButtonColorTwo = parseColor(s, "#272822", xrdbColors) |
|
} else { |
|
style.ButtonColorTwo = style.Background |
|
} |
|
|
|
s = NilDefaultString(cfg.TimelineNameBackground, sp("")) |
|
if len(s) > 0 { |
|
style.TimelineNameBackground = parseColor(s, "#272822", xrdbColors) |
|
} else { |
|
style.TimelineNameBackground = style.Background |
|
} |
|
s = NilDefaultString(cfg.TimelineNameText, sp("")) |
|
if len(s) > 0 { |
|
style.TimelineNameText = parseColor(s, "#808080", xrdbColors) |
|
} else { |
|
style.TimelineNameText = style.Subtle |
|
} |
|
|
|
s = NilDefaultString(cfg.CommandText, sp("")) |
|
if len(s) > 0 { |
|
style.CommandText = parseColor(s, "#f8f8f2", xrdbColors) |
|
} else { |
|
style.CommandText = style.StatusBarText |
|
} |
|
return style |
|
} |
|
|
|
func parseStyle(cfg StyleTOML, cnfPath string, cnfDir string) Style { |
|
var xrdbColors map[string]string |
|
xrdbMap, _ := GetXrdbColors() |
|
def := ConfigDefault.Style |
|
prefix := NilDefaultString(cfg.XrdbPrefix, def.XrdbPrefix) |
|
if prefix == "" { |
|
prefix = "guess" |
|
} |
|
|
|
if prefix == "guess" { |
|
if m, ok := xrdbMap["*"]; ok { |
|
xrdbColors = m |
|
} else if m, ok := xrdbMap["URxvt"]; ok { |
|
xrdbColors = m |
|
} else if m, ok := xrdbMap["XTerm"]; ok { |
|
xrdbColors = m |
|
} |
|
} else { |
|
if m, ok := xrdbMap[prefix]; ok { |
|
xrdbColors = m |
|
} |
|
} |
|
|
|
style := Style{} |
|
theme := NilDefaultString(cfg.Theme, def.Theme) |
|
if theme != "none" && theme != "" { |
|
bundled, local, err := getThemes(cnfPath, cnfDir) |
|
if err != nil { |
|
log.Fatalf("Couldn't load themes. Error: %s\n", err) |
|
} |
|
found := false |
|
isLocal := false |
|
for _, t := range local { |
|
if filepath.Base(t) == fmt.Sprintf("%s.toml", theme) { |
|
found = true |
|
isLocal = true |
|
break |
|
} |
|
} |
|
if !found { |
|
for _, t := range bundled { |
|
if filepath.Base(t) == fmt.Sprintf("%s.toml", theme) { |
|
found = true |
|
break |
|
} |
|
} |
|
} |
|
if !found { |
|
log.Fatalf("Couldn't find theme %s\n", theme) |
|
} |
|
tcfg, err := getTheme(theme, isLocal, cnfDir) |
|
if err != nil { |
|
log.Fatalf("Couldn't load theme. Error: %s\n", err) |
|
} |
|
style = parseTheme(tcfg, xrdbColors) |
|
|
|
} else { |
|
style = parseTheme(cfg, xrdbColors) |
|
} |
|
|
|
return style |
|
} |
|
|
|
func getViewer(v *ViewerTOML, def *ViewerTOML) (program, args string, terminal, single, reverse bool) { |
|
program = *def.Program |
|
args = *def.Args |
|
terminal = *def.Terminal |
|
single = *def.Single |
|
reverse = *def.Reverse |
|
if v == nil { |
|
return |
|
} |
|
if v.Program != nil { |
|
program = *v.Program |
|
} |
|
if v.Args != nil { |
|
args = *v.Args |
|
} |
|
if v.Terminal != nil { |
|
terminal = *v.Terminal |
|
} |
|
if v.Single != nil { |
|
single = *v.Single |
|
} |
|
if v.Reverse != nil { |
|
reverse = *v.Reverse |
|
} |
|
if *v.Program == "TUT_OS_DEFAULT" { |
|
var argsSlice []string |
|
program, argsSlice = util.GetDefaultForOS() |
|
args = strings.Join(argsSlice, " ") |
|
} |
|
return |
|
} |
|
|
|
func parseMedia(cfg MediaTOML) Media { |
|
media := Media{} |
|
var program, args string |
|
var terminal, single, reverse bool |
|
|
|
program, args, terminal, single, reverse = getViewer(cfg.Image, ConfigDefault.Media.Image) |
|
media.ImageViewer = program |
|
media.ImageArgs = strings.Fields(args) |
|
media.ImageTerminal = terminal |
|
media.ImageSingle = single |
|
media.ImageReverse = reverse |
|
|
|
program, args, terminal, single, reverse = getViewer(cfg.Video, ConfigDefault.Media.Video) |
|
media.VideoViewer = program |
|
media.VideoArgs = strings.Fields(args) |
|
media.VideoTerminal = terminal |
|
media.VideoSingle = single |
|
media.VideoReverse = reverse |
|
|
|
program, args, terminal, single, reverse = getViewer(cfg.Audio, ConfigDefault.Media.Audio) |
|
media.AudioViewer = program |
|
media.AudioArgs = strings.Fields(args) |
|
media.AudioTerminal = terminal |
|
media.AudioSingle = single |
|
media.AudioReverse = reverse |
|
|
|
program, args, terminal, _, _ = getViewer(cfg.Link, ConfigDefault.Media.Link) |
|
media.LinkViewer = program |
|
media.LinkArgs = strings.Fields(args) |
|
media.LinkTerminal = terminal |
|
|
|
return media |
|
} |
|
|
|
func parseGeneral(cfg GeneralTOML) General { |
|
general := General{} |
|
|
|
def := ConfigDefault.General |
|
general.Editor = NilDefaultString(cfg.Editor, def.Editor) |
|
if general.Editor == "TUT_USE_INTERNAL" { |
|
general.UseInternalEditor = true |
|
} |
|
general.Confirmation = NilDefaultBool(cfg.Confirmation, def.Confirmation) |
|
general.MouseSupport = NilDefaultBool(cfg.MouseSupport, def.MouseSupport) |
|
|
|
dateFormat := NilDefaultString(cfg.DateFormat, def.DateFormat) |
|
if dateFormat == "" { |
|
dateFormat = "2006-01-02 15:04" |
|
} |
|
general.DateFormat = dateFormat |
|
|
|
dateTodayFormat := NilDefaultString(cfg.DateTodayFormat, def.DateTodayFormat) |
|
if dateTodayFormat == "" { |
|
dateTodayFormat = "15:04" |
|
} |
|
general.DateTodayFormat = dateTodayFormat |
|
|
|
general.DateRelative = NilDefaultInt(cfg.DateRelative, def.DateRelative) |
|
|
|
general.QuoteReply = NilDefaultBool(cfg.QuoteReply, def.QuoteReply) |
|
general.MaxWidth = NilDefaultInt(cfg.MaxWidth, def.MaxWidth) |
|
general.ShortHints = NilDefaultBool(cfg.ShortHints, def.ShortHints) |
|
general.ShowFilterPhrase = NilDefaultBool(cfg.ShowFilterPhrase, def.ShowFilterPhrase) |
|
general.ShowIcons = NilDefaultBool(cfg.ShowIcons, def.ShowIcons) |
|
general.ShowHelp = NilDefaultBool(cfg.ShowHelp, def.ShowHelp) |
|
general.RedrawUI = NilDefaultBool(cfg.RedrawUI, def.RedrawUI) |
|
general.StickToTop = NilDefaultBool(cfg.StickToTop, def.StickToTop) |
|
general.ShowBoostedUser = NilDefaultBool(cfg.ShowBoostedUser, def.ShowBoostedUser) |
|
|
|
lp := NilDefaultString(cfg.ListPlacement, def.ListPlacement) |
|
switch lp { |
|
case "left": |
|
general.ListPlacement = ListPlacementLeft |
|
case "right": |
|
general.ListPlacement = ListPlacementRight |
|
case "top": |
|
general.ListPlacement = ListPlacementTop |
|
case "bottom": |
|
general.ListPlacement = ListPlacementBottom |
|
default: |
|
general.ListPlacement = ListPlacementLeft |
|
} |
|
ls := NilDefaultString(cfg.ListSplit, def.ListSplit) |
|
switch ls { |
|
case "row": |
|
general.ListSplit = ListRow |
|
case "column": |
|
general.ListSplit = ListColumn |
|
} |
|
|
|
listProp := NilDefaultInt(cfg.ListProportion, def.ListProportion) |
|
if listProp < 1 { |
|
listProp = 1 |
|
} |
|
contentProp := NilDefaultInt(cfg.ContentProportion, def.ContentProportion) |
|
if contentProp < 1 { |
|
contentProp = 1 |
|
} |
|
general.ListProportion = listProp |
|
general.ContentProportion = contentProp |
|
|
|
leaderString := NilDefaultString(cfg.LeaderKey, def.LeaderKey) |
|
leaderRunes := []rune(leaderString) |
|
if len(leaderRunes) > 1 { |
|
leaderRunes = []rune(strings.TrimSpace(leaderString)) |
|
} |
|
if len(leaderRunes) > 1 { |
|
fmt.Println("error parsing leader-key. Error: leader-key can only be one char long") |
|
os.Exit(1) |
|
} |
|
if len(leaderRunes) == 1 { |
|
general.LeaderKey = leaderRunes[0] |
|
} |
|
general.LeaderTimeout = NilDefaultInt64(cfg.LeaderTimeout, def.LeaderTimeout) |
|
if general.LeaderKey != rune(0) { |
|
var las []LeaderAction |
|
if cfg.LeaderActions != nil { |
|
lactions := *cfg.LeaderActions |
|
for _, l := range lactions { |
|
la := LeaderAction{} |
|
ltype := NilDefaultString(l.Type, sp("")) |
|
ldata := NilDefaultString(l.Data, sp("")) |
|
lshortcut := NilDefaultString(l.Shortcut, sp("")) |
|
switch ltype { |
|
case "clear-notifications": |
|
la.Command = LeaderClearNotifications |
|
case "compose": |
|
la.Command = LeaderCompose |
|
case "edit": |
|
la.Command = LeaderEdit |
|
case "blocking": |
|
la.Command = LeaderBlocking |
|
case "favorited": |
|
la.Command = LeaderFavorited |
|
case "history": |
|
la.Command = LeaderHistory |
|
case "boosts": |
|
la.Command = LeaderBoosts |
|
case "favorites": |
|
la.Command = LeaderFavorites |
|
case "following": |
|
la.Command = LeaderFollowing |
|
case "followers": |
|
la.Command = LeaderFollowers |
|
case "muting": |
|
la.Command = LeaderMuting |
|
case "preferences": |
|
la.Command = LeaderPreferences |
|
case "profile": |
|
la.Command = LeaderProfile |
|
case "mentions": |
|
la.Command = LeaderMentions |
|
case "stick-to-top": |
|
la.Command = LeaderStickToTop |
|
case "refetch": |
|
la.Command = LeaderRefetch |
|
case "tags": |
|
la.Command = LeaderTags |
|
case "list-placement": |
|
la.Command = LeaderListPlacement |
|
la.Subaction = ldata |
|
case "list-split": |
|
la.Command = LeaderListSplit |
|
la.Subaction = ldata |
|
case "proportions": |
|
la.Command = LeaderProportions |
|
la.Subaction = ldata |
|
case "pane": |
|
la.Command = LeaderPane |
|
la.Subaction = ldata |
|
case "close-pane": |
|
la.Command = LeaderClosePane |
|
case "move-pane-left", "move-pane-up": |
|
la.Command = LeaderMovePaneLeft |
|
case "move-pane-right", "move-pane-down": |
|
la.Command = LeaderMovePaneRight |
|
case "move-pane-home": |
|
la.Command = LeaderMovePaneHome |
|
case "move-pane-end": |
|
la.Command = LeaderMovePaneEnd |
|
case "newer": |
|
la.Command = LeaderLoadNewer |
|
default: |
|
fmt.Printf("leader-action %s is invalid\n", ltype) |
|
os.Exit(1) |
|
} |
|
la.Shortcut = lshortcut |
|
las = append(las, la) |
|
} |
|
} |
|
general.LeaderActions = las |
|
} |
|
|
|
var tls []*Timeline |
|
timelines := cfg.Timelines |
|
if cfg.Timelines != nil { |
|
for _, l := range *timelines { |
|
tl := NewTimeline(Timeline{}) |
|
if l.Type == nil { |
|
fmt.Println("timelines must have a type") |
|
os.Exit(1) |
|
} |
|
switch *l.Type { |
|
case "home": |
|
tl.FeedType = TimelineHome |
|
case "special": |
|
tl.FeedType = TimelineHomeSpecial |
|
case "direct": |
|
tl.FeedType = Conversations |
|
case "local": |
|
tl.FeedType = TimelineLocal |
|
case "federated": |
|
tl.FeedType = TimelineFederated |
|
case "bookmarks": |
|
tl.FeedType = Saved |
|
case "saved": |
|
tl.FeedType = Saved |
|
case "favorited": |
|
tl.FeedType = Favorited |
|
case "notifications": |
|
tl.FeedType = Notifications |
|
case "mentions": |
|
tl.FeedType = Mentions |
|
case "lists": |
|
tl.FeedType = Lists |
|
case "tag": |
|
tl.FeedType = Tag |
|
tl.Subaction = NilDefaultString(l.Data, sp("")) |
|
default: |
|
fmt.Printf("timeline %s is invalid\n", *l.Type) |
|
os.Exit(1) |
|
} |
|
tl.Name = NilDefaultString(l.Name, sp("")) |
|
tl.HideBoosts = NilDefaultBool(l.HideBoosts, bf) |
|
tl.HideReplies = NilDefaultBool(l.HideReplies, bf) |
|
tl.Closed = NilDefaultBool(l.Closed, bf) |
|
tl.Shortcut = NilDefaultString(l.Shortcut, sp("")) |
|
onFocus := NilDefaultString(l.OnFocus, sp("")) |
|
if onFocus != "" { |
|
switch onFocus { |
|
case "focus-pane": |
|
tl.OnFocus = TimelineFocusPane |
|
case "focus-self": |
|
tl.OnFocus = TimelineFocusTimeline |
|
default: |
|
fmt.Printf("on-focus: %s in timelines is invalid\n", onFocus) |
|
os.Exit(1) |
|
} |
|
} |
|
onCreation := NilDefaultString(l.OnCreationClosed, sp("")) |
|
if onCreation != "" { |
|
switch onCreation { |
|
case "current-pane": |
|
tl.OnCreationClosed = TimelineCreationClosedCurrentPane |
|
case "new-pane": |
|
tl.OnCreationClosed = TimelineCreationClosedNewPane |
|
default: |
|
fmt.Printf("on-creation-closed: %s in timelines is invalid\n", onCreation) |
|
os.Exit(1) |
|
} |
|
} |
|
if l.Keys != nil { |
|
var keys []string |
|
var special []string |
|
if l.Keys != nil { |
|
keys = *l.Keys |
|
} |
|
if l.SpecialKeys != nil { |
|
special = *l.SpecialKeys |
|
} |
|
var err error |
|
tl.Key, err = NewKeyT("", "", keys, special) |
|
if err != nil { |
|
fmt.Println(err) |
|
os.Exit(1) |
|
} |
|
} |
|
tls = append(tls, tl) |
|
} |
|
} |
|
if len(tls) == 0 { |
|
tls = append(tls, |
|
NewTimeline(Timeline{ |
|
FeedType: TimelineHome, |
|
Name: "Home", |
|
}), |
|
) |
|
tls = append(tls, |
|
NewTimeline(Timeline{ |
|
FeedType: Notifications, |
|
Name: "[N]otifications", |
|
Key: inputStrOrErr([]string{"", "'n'", "'N'"}, false), |
|
}), |
|
) |
|
} |
|
general.Timelines = tls |
|
|
|
general.TerminalTitle = NilDefaultInt(cfg.TerminalTitle, def.TerminalTitle) |
|
/* |
|
0 = No terminal title |
|
1 = Show title in terminal and top bar |
|
2 = Only show terminal title, and no top bar |
|
*/ |
|
if general.TerminalTitle < 0 || general.TerminalTitle > 2 { |
|
general.TerminalTitle = 0 |
|
} |
|
|
|
nths := []NotificationToHide{} |
|
nth := cfg.NotificationsToHide |
|
if nth != nil { |
|
for _, n := range *nth { |
|
switch n { |
|
case "mention": |
|
nths = append(nths, HideMention) |
|
case "status": |
|
nths = append(nths, HideStatus) |
|
case "boost": |
|
nths = append(nths, HideBoost) |
|
case "follow": |
|
nths = append(nths, HideFollow) |
|
case "follow_request": |
|
nths = append(nths, HideFollowRequest) |
|
case "favorite": |
|
nths = append(nths, HideFavorite) |
|
case "poll": |
|
nths = append(nths, HidePoll) |
|
case "edit": |
|
nths = append(nths, HideEdited) |
|
default: |
|
log.Fatalf("%s in notifications-to-hide is invalid\n", n) |
|
os.Exit(1) |
|
} |
|
general.NotificationsToHide = nths |
|
} |
|
} |
|
return general |
|
} |
|
|
|
func parseOpenPattern(cfg OpenPatternTOML) OpenPattern { |
|
om := OpenPattern{} |
|
if cfg.Patterns == nil { |
|
return om |
|
} |
|
for _, p := range *cfg.Patterns { |
|
pattern := Pattern{ |
|
Program: NilDefaultString(p.Program, sp("")), |
|
Terminal: NilDefaultBool(p.Terminal, bf), |
|
} |
|
if p.Args != nil { |
|
pattern.Args = strings.Fields(*p.Args) |
|
} |
|
pg := NilDefaultString(p.Matching, sp("")) |
|
compiled, err := glob.Compile(pg) |
|
if err != nil { |
|
panic(fmt.Sprintf("Couldn't compile pattern %s in config. Error: %v", pg, err)) |
|
} |
|
pattern.Compiled = compiled |
|
om.Patterns = append(om.Patterns, pattern) |
|
} |
|
|
|
return om |
|
} |
|
|
|
func parseCustom(cfg OpenCustomTOML) OpenCustom { |
|
oc := OpenCustom{} |
|
if cfg.Programs == nil { |
|
return oc |
|
} |
|
for _, x := range *cfg.Programs { |
|
keys, special := []string{}, []string{} |
|
if x.Keys != nil { |
|
keys = *x.Keys |
|
} |
|
if x.SpecialKeys != nil { |
|
special = *x.SpecialKeys |
|
} |
|
key, err := NewKeyT( |
|
NilDefaultString(x.Hint, sp("")), |
|
"", keys, special, |
|
) |
|
if err != nil { |
|
fmt.Println(err) |
|
os.Exit(1) |
|
} |
|
use := NilDefaultString(x.Program, sp("")) |
|
terminal := NilDefaultBool(x.Terminal, bf) |
|
if use == "" { |
|
continue |
|
} |
|
args := strings.Fields(NilDefaultString(x.Args, sp(""))) |
|
c := Custom{ |
|
Program: use, |
|
Args: args, |
|
Terminal: terminal, |
|
Key: key, |
|
} |
|
oc.OpenCustoms = append(oc.OpenCustoms, c) |
|
} |
|
return oc |
|
} |
|
|
|
func parseNotifications(cfg NotificationsTOML) Notification { |
|
nc := Notification{} |
|
def := ConfigDefault.NotificationConfig |
|
nc.NotificationFollower = NilDefaultBool(cfg.Followers, def.Followers) |
|
nc.NotificationFavorite = NilDefaultBool(cfg.Favorite, def.Favorite) |
|
nc.NotificationMention = NilDefaultBool(cfg.Mention, def.Mention) |
|
nc.NotificationUpdate = NilDefaultBool(cfg.Update, def.Update) |
|
nc.NotificationBoost = NilDefaultBool(cfg.Boost, def.Followers) |
|
nc.NotificationPoll = NilDefaultBool(cfg.Poll, def.Poll) |
|
nc.NotificationPost = NilDefaultBool(cfg.Posts, def.Posts) |
|
return nc |
|
} |
|
|
|
func parseTemplates(cfg *ini.File, cnfPath string, cnfDir string) Templates { |
|
var tootTmpl *template.Template |
|
tootTmplPath, exists, err := checkConfig("toot.tmpl", cnfPath, cnfDir) |
|
if err != nil { |
|
log.Fatalf( |
|
fmt.Sprintf("Couldn't access toot.tmpl. Error: %v", err), |
|
) |
|
} |
|
if exists { |
|
tootTmpl, err = template.New("toot.tmpl").Funcs(template.FuncMap{ |
|
"Color": ColorMark, |
|
"Flags": TextFlags, |
|
}).ParseFiles(tootTmplPath) |
|
} |
|
if !exists || err != nil { |
|
tootTmpl, err = template.New("toot.tmpl").Funcs(template.FuncMap{ |
|
"Color": ColorMark, |
|
"Flags": TextFlags, |
|
}).Parse(tootTemplate) |
|
} |
|
if err != nil { |
|
log.Fatalf("Couldn't parse toot.tmpl. Error: %v", err) |
|
} |
|
var userTmpl *template.Template |
|
userTmplPath, exists, err := checkConfig("user.tmpl", cnfPath, cnfDir) |
|
if err != nil { |
|
log.Fatalf( |
|
fmt.Sprintf("Couldn't access user.tmpl. Error: %v", err), |
|
) |
|
} |
|
if exists { |
|
userTmpl, err = template.New("user.tmpl").Funcs(template.FuncMap{ |
|
"Color": ColorMark, |
|
"Flags": TextFlags, |
|
}).ParseFiles(userTmplPath) |
|
} |
|
if !exists || err != nil { |
|
userTmpl, err = template.New("user.tmpl").Funcs(template.FuncMap{ |
|
"Color": ColorMark, |
|
"Flags": TextFlags, |
|
}).Parse(userTemplate) |
|
} |
|
if err != nil { |
|
log.Fatalf("Couldn't parse user.tmpl. Error: %v", err) |
|
} |
|
var helpTmpl *template.Template |
|
helpTmpl, err = template.New("help.tmpl").Funcs(template.FuncMap{ |
|
"Color": ColorMark, |
|
"Flags": TextFlags, |
|
}).Parse(helpTemplate) |
|
if err != nil { |
|
log.Fatalf("Couldn't parse help.tmpl. Error: %v", err) |
|
} |
|
return Templates{ |
|
Toot: tootTmpl, |
|
User: userTmpl, |
|
Help: helpTmpl, |
|
} |
|
} |
|
|
|
func inputStrOrErr(vals []string, double bool) Key { |
|
k, err := NewKey(vals, double) |
|
if err != nil { |
|
fmt.Printf("error parsing config. Error: %v\n", err) |
|
os.Exit(1) |
|
} |
|
return k |
|
} |
|
|
|
func inputOrDef(keyName string, user *KeyHintTOML, def *KeyHintTOML, double bool) Key { |
|
values := *def |
|
if user != nil { |
|
values = *user |
|
} |
|
keys, special := []string{}, []string{} |
|
if values.Keys != nil { |
|
keys = *values.Keys |
|
} |
|
if values.SpecialKeys != nil { |
|
special = *values.SpecialKeys |
|
} |
|
key, err := NewKeyT( |
|
NilDefaultString(values.Hint, sp("")), |
|
NilDefaultString(values.HintAlt, sp("")), |
|
keys, special, |
|
) |
|
if err != nil { |
|
fmt.Printf("error parsing config for key %s. Error: %v\n", keyName, err) |
|
os.Exit(1) |
|
} |
|
return key |
|
} |
|
|
|
func parseInput(cfg InputTOML) Input { |
|
def := ConfigDefault.Input |
|
ic := Input{} |
|
ic.GlobalDown = inputOrDef("global-down", cfg.GlobalDown, def.GlobalDown, false) |
|
ic.GlobalUp = inputOrDef("global-up", cfg.GlobalUp, def.GlobalUp, false) |
|
ic.GlobalEnter = inputOrDef("global-enter", cfg.GlobalEnter, def.GlobalEnter, false) |
|
ic.GlobalBack = inputOrDef("global-back", cfg.GlobalBack, def.GlobalBack, false) |
|
ic.GlobalExit = inputOrDef("global-exit", cfg.GlobalExit, def.GlobalExit, false) |
|
|
|
ic.MainHome = inputOrDef("main-home", cfg.MainHome, def.MainHome, false) |
|
ic.MainEnd = inputOrDef("main-end", cfg.MainEnd, def.MainEnd, false) |
|
ic.MainPrevFeed = inputOrDef("main-prev-feed", cfg.MainPrevFeed, def.MainPrevFeed, false) |
|
ic.MainNextFeed = inputOrDef("main-next-feed", cfg.MainNextFeed, def.MainNextFeed, false) |
|
ic.MainNextPane = inputOrDef("main-next-pane", cfg.MainNextPane, def.MainNextPane, false) |
|
ic.MainPrevPane = inputOrDef("main-prev-pane", cfg.MainPrevPane, def.MainPrevPane, false) |
|
ic.MainCompose = inputOrDef("main-compose", cfg.MainCompose, def.MainCompose, false) |
|
ic.MainNextAccount = inputOrDef("main-next-account", cfg.MainNextAccount, def.MainNextAccount, false) |
|
ic.MainPrevAccount = inputOrDef("main-prev-account", cfg.MainPrevAccount, def.MainPrevAccount, false) |
|
|
|
ic.StatusAvatar = inputOrDef("status-avatar", cfg.StatusAvatar, def.StatusAvatar, false) |
|
ic.StatusBoost = inputOrDef("status-boost", cfg.StatusBoost, def.StatusBoost, true) |
|
ic.StatusDelete = inputOrDef("status-delete", cfg.StatusDelete, def.StatusDelete, false) |
|
ic.StatusEdit = inputOrDef("status-edit", cfg.StatusEdit, def.StatusEdit, false) |
|
ic.StatusFavorite = inputOrDef("status-favorite", cfg.StatusFavorite, def.StatusFavorite, true) |
|
ic.StatusMedia = inputOrDef("status-media", cfg.StatusMedia, def.StatusMedia, false) |
|
ic.StatusLinks = inputOrDef("status-links", cfg.StatusLinks, def.StatusLinks, false) |
|
ic.StatusPoll = inputOrDef("status-poll", cfg.StatusPoll, def.StatusPoll, false) |
|
ic.StatusReply = inputOrDef("status-reply", cfg.StatusReply, def.StatusReply, false) |
|
ic.StatusBookmark = inputOrDef("status-bookmark", cfg.StatusBookmark, def.StatusBookmark, true) |
|
ic.StatusThread = inputOrDef("status-thread", cfg.StatusThread, def.StatusThread, false) |
|
ic.StatusUser = inputOrDef("status-user", cfg.StatusUser, def.StatusUser, false) |
|
ic.StatusViewFocus = inputOrDef("status-view-focus", cfg.StatusViewFocus, def.StatusViewFocus, false) |
|
ic.StatusYank = inputOrDef("status-yank", cfg.StatusYank, def.StatusYank, false) |
|
ic.StatusToggleCW = inputOrDef("status-toggle-cw", cfg.StatusToggleCW, def.StatusToggleCW, false) |
|
ic.StatusShowFiltered = inputOrDef("status-show-filtered", cfg.StatusShowFiltered, def.StatusShowFiltered, false) |
|
|
|
ic.UserAvatar = inputOrDef("user-avatar", cfg.UserAvatar, def.UserAvatar, false) |
|
ic.UserBlock = inputOrDef("user-block", cfg.UserBlock, def.UserBlock, true) |
|
ic.UserFollow = inputOrDef("user-follow", cfg.UserFollow, def.UserFollow, true) |
|
ic.UserFollowRequestDecide = inputOrDef("user-follow-request-decide", cfg.UserFollowRequestDecide, def.UserFollowRequestDecide, true) |
|
ic.UserMute = inputOrDef("user-mute", cfg.UserMute, def.UserMute, true) |
|
ic.UserLinks = inputOrDef("user-links", cfg.UserLinks, def.UserLinks, false) |
|
ic.UserUser = inputOrDef("user-user", cfg.UserUser, def.UserUser, false) |
|
ic.UserViewFocus = inputOrDef("user-view-focus", cfg.UserViewFocus, def.UserViewFocus, false) |
|
ic.UserYank = inputOrDef("user-yank", cfg.UserYank, def.UserYank, false) |
|
|
|
ic.ListOpenFeed = inputOrDef("list-open-feed", cfg.ListOpenFeed, def.ListOpenFeed, false) |
|
ic.ListUserList = inputOrDef("list-user-list", cfg.ListUserList, def.ListUserList, false) |
|
ic.ListUserAdd = inputOrDef("list-user-add", cfg.ListUserAdd, def.ListUserAdd, false) |
|
ic.ListUserDelete = inputOrDef("list-user-delete", cfg.ListUserDelete, def.ListUserDelete, false) |
|
|
|
ic.TagOpenFeed = inputOrDef("tag-open-feed", cfg.TagOpenFeed, def.TagOpenFeed, false) |
|
ic.TagFollow = inputOrDef("tag-follow", cfg.TagFollow, def.TagFollow, true) |
|
ic.LinkOpen = inputOrDef("link-open", cfg.LinkOpen, def.LinkOpen, false) |
|
ic.LinkYank = inputOrDef("link-yank", cfg.LinkYank, def.LinkYank, false) |
|
|
|
ic.ComposeEditCW = inputOrDef("compose-edit-cw", cfg.ComposeEditCW, def.ComposeEditCW, false) |
|
ic.ComposeEditText = inputOrDef("compose-edit-text", cfg.ComposeEditText, def.ComposeEditText, false) |
|
ic.ComposeIncludeQuote = inputOrDef("compose-include-quote", cfg.ComposeIncludeQuote, def.ComposeIncludeQuote, false) |
|
ic.ComposeMediaFocus = inputOrDef("compose-media-focus", cfg.ComposeMediaFocus, def.ComposeMediaFocus, false) |
|
ic.ComposePost = inputOrDef("compose-post", cfg.ComposePost, def.ComposePost, false) |
|
ic.ComposeToggleContentWarning = inputOrDef("compose-toggle-content-warning", cfg.ComposeToggleContentWarning, def.ComposeToggleContentWarning, false) |
|
ic.ComposeVisibility = inputOrDef("compose-visibility", cfg.ComposeVisibility, def.ComposeVisibility, false) |
|
ic.ComposeLanguage = inputOrDef("compose-language", cfg.ComposeLanguage, def.ComposeLanguage, false) |
|
ic.ComposePoll = inputOrDef("compose-poll", cfg.ComposePoll, def.ComposePoll, false) |
|
|
|
ic.MediaDelete = inputOrDef("media-delete", cfg.MediaDelete, def.MediaDelete, false) |
|
ic.MediaEditDesc = inputOrDef("media-edit-desc", cfg.MediaEditDesc, def.MediaEditDesc, false) |
|
ic.MediaAdd = inputOrDef("media-add", cfg.MediaAdd, def.MediaAdd, false) |
|
|
|
ic.VoteVote = inputOrDef("vote-vote", cfg.VoteVote, def.VoteVote, false) |
|
ic.VoteSelect = inputOrDef("vote-select", cfg.VoteSelect, def.VoteSelect, false) |
|
|
|
ic.PollAdd = inputOrDef("poll-add", cfg.PollAdd, def.PollAdd, false) |
|
ic.PollEdit = inputOrDef("poll-edit", cfg.PollEdit, def.PollEdit, false) |
|
ic.PollDelete = inputOrDef("poll-delete", cfg.PollDelete, def.PollDelete, false) |
|
ic.PollMultiToggle = inputOrDef("poll-multi-toggle", cfg.PollMultiToggle, def.PollMultiToggle, false) |
|
ic.PollExpiration = inputOrDef("poll-expiration", cfg.PollExpiration, def.PollExpiration, false) |
|
|
|
ic.PreferenceName = inputOrDef("preference-name", cfg.PreferenceName, def.PreferenceName, false) |
|
ic.PreferenceVisibility = inputOrDef("preference-visibility", cfg.PreferenceVisibility, def.PreferenceVisibility, false) |
|
ic.PreferenceBio = inputOrDef("preference-bio", cfg.PreferenceBio, def.PreferenceBio, false) |
|
ic.PreferenceSave = inputOrDef("preference-save", cfg.PreferenceSave, def.PreferenceSave, false) |
|
ic.PreferenceFields = inputOrDef("preference-fields", cfg.PreferenceFields, def.PreferenceFields, false) |
|
ic.PreferenceFieldsAdd = inputOrDef("preference-fields-add", cfg.PreferenceFieldsAdd, def.PreferenceFieldsAdd, false) |
|
ic.PreferenceFieldsEdit = inputOrDef("preference-fields-edit", cfg.PreferenceFieldsEdit, def.PreferenceFieldsEdit, false) |
|
ic.PreferenceFieldsDelete = inputOrDef("preference-fields-delete", cfg.PreferenceFieldsDelete, def.PreferenceFieldsDelete, false) |
|
|
|
ic.EditorExit = inputOrDef("editor-exit", cfg.EditorExit, def.EditorExit, false) |
|
return ic |
|
} |
|
|
|
func parseConfig(filepath string, cnfPath string, cnfDir string) (Config, error) { |
|
cfg, err := ini.LoadSources(ini.LoadOptions{ |
|
SpaceBeforeInlineComment: true, |
|
AllowShadows: true, |
|
}, filepath) |
|
conf := Config{} |
|
if err != nil { |
|
return conf, err |
|
} |
|
f, err := os.Open(filepath) |
|
if err != nil { |
|
log.Fatalln(err) |
|
} |
|
var a ConfigTOML |
|
toml.NewDecoder(f).Decode(&a) |
|
if err != nil { |
|
log.Fatalln(err) |
|
} |
|
f.Close() |
|
conf.General = parseGeneral(a.General) |
|
conf.Media = parseMedia(a.Media) |
|
conf.Style = parseStyle(a.Style, cnfPath, cnfDir) |
|
conf.OpenPattern = parseOpenPattern(a.OpenPattern) |
|
conf.OpenCustom = parseCustom(a.OpenCustom) |
|
conf.NotificationConfig = parseNotifications(a.NotificationConfig) |
|
conf.Templates = parseTemplates(cfg, cnfPath, cnfDir) |
|
conf.Input = parseInput(a.Input) |
|
|
|
return conf, nil |
|
} |
|
|
|
func createConfigDir() error { |
|
cd, err := util.GetConfigDir() |
|
if err != nil { |
|
log.Fatalf("couldn't find config dir. Err %v", err) |
|
} |
|
path := cd + "/tut" |
|
return os.MkdirAll(path, os.ModePerm) |
|
} |
|
|
|
func checkConfig(filename string, cnfPath string, cnfDir string) (path string, exists bool, err error) { |
|
if cnfPath != "" && filename == "config.toml" { |
|
_, err = os.Stat(cnfPath) |
|
if os.IsNotExist(err) { |
|
return cnfPath, false, nil |
|
} else if err != nil { |
|
return cnfPath, true, err |
|
} |
|
return cnfPath, true, err |
|
} |
|
if cnfDir != "" { |
|
p := filepath.Join(cnfDir, filename) |
|
if os.IsNotExist(err) { |
|
return p, false, nil |
|
} else if err != nil { |
|
return p, true, err |
|
} |
|
return p, true, err |
|
} |
|
cd, err := util.GetConfigDir() |
|
if err != nil { |
|
log.Fatalf("couldn't find config dir. Err %v", err) |
|
} |
|
dir := cd + "/tut/" |
|
path = dir + filename |
|
_, err = os.Stat(path) |
|
if os.IsNotExist(err) { |
|
return path, false, nil |
|
} else if err != nil { |
|
return path, true, err |
|
} |
|
return path, true, err |
|
} |
|
|
|
func CreateDefaultConfig(filepath string) error { |
|
f, err := os.Create(filepath) |
|
if err != nil { |
|
return err |
|
} |
|
defer f.Close() |
|
_, err = f.WriteString(conftext) |
|
if err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
func getThemes(cnfPath string, cnfDir string) (bundled []string, local []string, err error) { |
|
entries, err := themesFS.ReadDir("themes") |
|
if err != nil { |
|
return bundled, local, err |
|
} |
|
for _, entry := range entries { |
|
if entry.IsDir() { |
|
continue |
|
} |
|
fp := filepath.Join("themes/", entry.Name()) |
|
bundled = append(bundled, fp) |
|
} |
|
_, exists, err := checkConfig("themes", cnfPath, cnfDir) |
|
if err != nil { |
|
return bundled, local, err |
|
} |
|
if !exists { |
|
return bundled, local, err |
|
} |
|
var dir string |
|
if cnfDir != "" { |
|
dir = filepath.Join(cnfDir, "themes") |
|
} else { |
|
cd, err := util.GetConfigDir() |
|
if err != nil { |
|
log.Fatalf("couldn't find config dir. Err %v", err) |
|
} |
|
dir = filepath.Join(cd, "/tut/themes") |
|
} |
|
entries, err = os.ReadDir(dir) |
|
if err != nil { |
|
return bundled, local, err |
|
} |
|
for _, entry := range entries { |
|
if entry.IsDir() { |
|
continue |
|
} |
|
fp := filepath.Join(dir, entry.Name()) |
|
local = append(local, fp) |
|
} |
|
return bundled, local, nil |
|
} |
|
|
|
func getTheme(fname string, isLocal bool, cnfDir string) (StyleTOML, error) { |
|
var f io.Reader |
|
var err error |
|
if isLocal { |
|
var dir string |
|
if cnfDir != "" { |
|
dir = filepath.Join(cnfDir, "themes") |
|
} else { |
|
cd, err := util.GetConfigDir() |
|
if err != nil { |
|
log.Fatalf("couldn't find config dir. Err %v", err) |
|
} |
|
dir = filepath.Join(cd, "/tut/themes") |
|
} |
|
f, err = os.Open( |
|
filepath.Join(dir, fmt.Sprintf("%s.toml", strings.TrimSpace(fname))), |
|
) |
|
} else { |
|
f, err = themesFS.Open(fmt.Sprintf("themes/%s.toml", strings.TrimSpace(fname))) |
|
} |
|
if err != nil { |
|
return StyleTOML{}, err |
|
} |
|
var style StyleTOML |
|
toml.NewDecoder(f).Decode(&style) |
|
if err != nil { |
|
return style, err |
|
} |
|
switch x := f.(type) { |
|
case *os.File: |
|
x.Close() |
|
} |
|
return style, nil |
|
}
|
|
|