github.com/oam-dev/kubevela@v1.9.11/references/cli/top/config/color.go (about)

     1  /*
     2  Copyright 2022 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  	http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"embed"
    21  	"fmt"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  
    26  	"github.com/gdamore/tcell/v2"
    27  	"gopkg.in/yaml.v3"
    28  )
    29  
    30  // Color is a color string.
    31  type Color string
    32  
    33  // ThemeConfig is the theme config.
    34  type ThemeConfig struct {
    35  	Info struct {
    36  		Title Color `yaml:"title"`
    37  		Text  Color `yaml:"text"`
    38  	} `yaml:"info"`
    39  	Menu struct {
    40  		Description Color `yaml:"description"`
    41  		Key         Color `yaml:"key"`
    42  	} `yaml:"menu"`
    43  	Logo struct {
    44  		Text Color `yaml:"text"`
    45  	} `yaml:"logo"`
    46  	Crumbs struct {
    47  		Foreground Color `yaml:"foreground"`
    48  		Background Color `yaml:"background"`
    49  	} `yaml:"crumbs"`
    50  	Border struct {
    51  		App   Color `yaml:"app"`
    52  		Table Color `yaml:"table"`
    53  	} `yaml:"border"`
    54  	Table struct {
    55  		Title    Color `yaml:"title"`
    56  		Header   Color `yaml:"header"`
    57  		Body     Color `yaml:"body"`
    58  		CursorBg Color `yaml:"cursorbg"`
    59  		CursorFg Color `yaml:"cursorfg"`
    60  	} `yaml:"table"`
    61  	Status struct {
    62  		Starting  Color `yaml:"starting"`
    63  		Healthy   Color `yaml:"healthy"`
    64  		UnHealthy Color `yaml:"unhealthy"`
    65  		Waiting   Color `yaml:"waiting"`
    66  		Succeeded Color `yaml:"succeeded"`
    67  		Failed    Color `yaml:"failed"`
    68  		Unknown   Color `yaml:"unknown"`
    69  	} `yaml:"status"`
    70  	Yaml struct {
    71  		Key   Color `yaml:"key"`
    72  		Colon Color `yaml:"colon"`
    73  		Value Color `yaml:"value"`
    74  	} `yaml:"yaml"`
    75  	Topology struct {
    76  		Line      Color `yaml:"line"`
    77  		App       Color `yaml:"app"`
    78  		Workflow  Color `yaml:"workflow"`
    79  		Component Color `yaml:"component"`
    80  		Policy    Color `yaml:"policy"`
    81  		Trait     Color `yaml:"trait"`
    82  		Kind      Color `yaml:"kind"`
    83  	} `yaml:"topology"`
    84  }
    85  
    86  var (
    87  	// ThemeConfigFS is the theme config file system.
    88  	//go:embed theme/*
    89  	ThemeConfigFS embed.FS
    90  	// ThemeMap is the theme map.
    91  	ThemeMap = make(map[string]ThemeConfig)
    92  	// ThemeNameArray is the theme name array.
    93  	ThemeNameArray []string
    94  	// homePath is the home path.
    95  	homePath string
    96  	// diyThemeDirPath is the diy theme dir path like ~/.vela/theme/themes
    97  	diyThemeDirPath string
    98  	// themeConfigFilePath is the theme config file path like ~/.vela/theme/_config.yaml
    99  	themeConfigFilePath string
   100  )
   101  
   102  const (
   103  	// DefaultColor represents a default color.
   104  	DefaultColor Color = "default"
   105  	// DefaultTheme represents a default theme.
   106  	DefaultTheme     = "default"
   107  	embedThemePath   = "theme"
   108  	themeHomeDirPath = ".vela/theme"
   109  	diyThemeDir      = "themes"
   110  	themeConfigFile  = "_config.yaml"
   111  )
   112  
   113  func init() {
   114  	homePath, _ = os.UserHomeDir()
   115  	diyThemeDirPath = filepath.Join(homePath, themeHomeDirPath, diyThemeDir)
   116  	themeConfigFilePath = filepath.Join(homePath, themeHomeDirPath, themeConfigFile)
   117  
   118  	dir, err := ThemeConfigFS.ReadDir(embedThemePath)
   119  	if err != nil {
   120  		return
   121  	}
   122  
   123  	// embed theme config
   124  	for _, item := range dir {
   125  		content, err := ThemeConfigFS.ReadFile(filepath.Join(embedThemePath, item.Name()))
   126  		if err != nil {
   127  			continue
   128  		}
   129  		var t ThemeConfig
   130  		err = yaml.Unmarshal(content, &t)
   131  		if err != nil {
   132  			continue
   133  		}
   134  		themeName := strings.Split(item.Name(), ".")[0]
   135  		ThemeMap[themeName] = t
   136  		ThemeNameArray = append(ThemeNameArray, themeName)
   137  	}
   138  
   139  	// load diy theme config
   140  	dir, err = os.ReadDir(diyThemeDirPath)
   141  	if err != nil {
   142  		return
   143  	}
   144  	for _, item := range dir {
   145  		content, err := os.ReadFile(filepath.Clean(filepath.Join(diyThemeDirPath, item.Name())))
   146  		if err != nil {
   147  			continue
   148  		}
   149  		var t ThemeConfig
   150  		err = yaml.Unmarshal(content, &t)
   151  		if err != nil {
   152  			continue
   153  		}
   154  		themeName := strings.Split(item.Name(), ".")[0]
   155  		ThemeMap[themeName] = t
   156  		ThemeNameArray = append(ThemeNameArray, themeName)
   157  	}
   158  }
   159  
   160  // LoadThemeConfig loads theme config from env or use the default setting
   161  func LoadThemeConfig() *ThemeConfig {
   162  	themeConfigName := struct {
   163  		Name string `yaml:"name"`
   164  	}{}
   165  	// returns default theme if config file not exist
   166  	if !makeThemeConfigFileIfNotExist() {
   167  		return defaultTheme()
   168  	}
   169  
   170  	content, err := os.ReadFile(filepath.Clean(themeConfigFilePath))
   171  	if err != nil {
   172  		return defaultTheme()
   173  	}
   174  	err = yaml.Unmarshal(content, &themeConfigName)
   175  	if err != nil {
   176  		return defaultTheme()
   177  	}
   178  	if themeConfigName.Name == DefaultTheme {
   179  		return defaultTheme()
   180  	}
   181  
   182  	if config, ok := ThemeMap[themeConfigName.Name]; ok {
   183  		return &config
   184  	}
   185  	return defaultTheme()
   186  }
   187  
   188  func defaultTheme() *ThemeConfig {
   189  	return &ThemeConfig{
   190  		Info: struct {
   191  			Title Color `yaml:"title"`
   192  			Text  Color `yaml:"text"`
   193  		}{
   194  			Title: "royalblue",
   195  			Text:  "lightgray",
   196  		},
   197  		Menu: struct {
   198  			Description Color `yaml:"description"`
   199  			Key         Color `yaml:"key"`
   200  		}{
   201  			Description: "gray",
   202  			Key:         "royalblue",
   203  		},
   204  		Logo: struct {
   205  			Text Color `yaml:"text"`
   206  		}{
   207  			Text: "royalblue",
   208  		},
   209  		Crumbs: struct {
   210  			Foreground Color `yaml:"foreground"`
   211  			Background Color `yaml:"background"`
   212  		}{
   213  			Foreground: "white",
   214  			Background: "royalblue",
   215  		},
   216  		Border: struct {
   217  			App   Color `yaml:"app"`
   218  			Table Color `yaml:"table"`
   219  		}{
   220  			App:   "black",
   221  			Table: "lightgray",
   222  		},
   223  		Table: struct {
   224  			Title    Color `yaml:"title"`
   225  			Header   Color `yaml:"header"`
   226  			Body     Color `yaml:"body"`
   227  			CursorBg Color `yaml:"cursorbg"`
   228  			CursorFg Color `yaml:"cursorfg"`
   229  		}{
   230  			Title:    "royalblue",
   231  			Header:   "white",
   232  			Body:     "blue",
   233  			CursorBg: "blue",
   234  			CursorFg: "black",
   235  		},
   236  		Yaml: struct {
   237  			Key   Color `yaml:"key"`
   238  			Colon Color `yaml:"colon"`
   239  			Value Color `yaml:"value"`
   240  		}{
   241  			Key:   "#d33582",
   242  			Colon: "lightgray",
   243  			Value: "#839495",
   244  		},
   245  		Status: struct {
   246  			Starting  Color `yaml:"starting"`
   247  			Healthy   Color `yaml:"healthy"`
   248  			UnHealthy Color `yaml:"unhealthy"`
   249  			Waiting   Color `yaml:"waiting"`
   250  			Succeeded Color `yaml:"succeeded"`
   251  			Failed    Color `yaml:"failed"`
   252  			Unknown   Color `yaml:"unknown"`
   253  		}{
   254  			Starting:  "blue",
   255  			Healthy:   "green",
   256  			UnHealthy: "red",
   257  			Waiting:   "yellow",
   258  			Succeeded: "orange",
   259  			Failed:    "purple",
   260  			Unknown:   "gray",
   261  		},
   262  		Topology: struct {
   263  			Line      Color `yaml:"line"`
   264  			App       Color `yaml:"app"`
   265  			Workflow  Color `yaml:"workflow"`
   266  			Component Color `yaml:"component"`
   267  			Policy    Color `yaml:"policy"`
   268  			Trait     Color `yaml:"trait"`
   269  			Kind      Color `yaml:"kind"`
   270  		}{
   271  			Line:      "cadetblue",
   272  			App:       "red",
   273  			Workflow:  "orange",
   274  			Component: "green",
   275  			Policy:    "yellow",
   276  			Trait:     "lightseagreen",
   277  			Kind:      "orange",
   278  		},
   279  	}
   280  }
   281  
   282  // PersistentThemeConfig saves theme config to file
   283  func PersistentThemeConfig(themeName string) {
   284  	makeThemeConfigFileIfNotExist()
   285  	_ = os.WriteFile(themeConfigFilePath, []byte("name : "+themeName), 0600)
   286  }
   287  
   288  // makeThemeConfigFileIfNotExist makes theme config file and write default content if not exist
   289  func makeThemeConfigFileIfNotExist() bool {
   290  	velaThemeHome := filepath.Clean(filepath.Join(homePath, themeHomeDirPath))
   291  	if _, err := os.Open(filepath.Clean(themeConfigFilePath)); err != nil {
   292  		if os.IsNotExist(err) {
   293  			// make file if not exist
   294  			_ = os.MkdirAll(filepath.Clean(velaThemeHome), 0700)
   295  			_ = os.WriteFile(filepath.Clean(themeConfigFilePath), []byte("name : "+DefaultTheme), 0600)
   296  		}
   297  		return false
   298  	}
   299  	return true
   300  }
   301  
   302  // String returns color as string.
   303  func (c Color) String() string {
   304  	if c.isHex() {
   305  		return string(c)
   306  	}
   307  	if c == DefaultColor {
   308  		return "-"
   309  	}
   310  	col := c.Color().TrueColor().Hex()
   311  	if col < 0 {
   312  		return "-"
   313  	}
   314  
   315  	return fmt.Sprintf("#%06x", col)
   316  }
   317  
   318  func (c Color) isHex() bool {
   319  	return len(c) == 7 && c[0] == '#'
   320  }
   321  
   322  // Color returns a view color.
   323  func (c Color) Color() tcell.Color {
   324  	if c == DefaultColor {
   325  		return tcell.ColorDefault
   326  	}
   327  
   328  	return tcell.GetColor(string(c)).TrueColor()
   329  }