Repository: go-gitea/gitea
File: services/webtheme/webtheme.go (Go)

Code before:
		FileName:     fileName,
		InternalName: strings.TrimSuffix(strings.TrimPrefix(fileName, fileNamePrefix), fileNameSuffix),
	}
	themeInfo.DisplayName = themeInfo.InternalName
	return themeInfo
}

func defaultThemeMetaInfoByInternalName(fileName string) *ThemeMetaInfo {
	return defaultThemeMetaInfoByFileName(fileNamePrefix + fileName + fileNameSuffix)
}

func parseThemeMetaInfo(fileName, cssContent string) *ThemeMetaInfo {
	themeInfo := defaultThemeMetaInfoByFileName(fileName)
	m := parseThemeMetaInfoToMap(cssContent)
	if m == nil {
		return themeInfo
	}
	themeInfo.DisplayName = m["--theme-display-name"]
	themeInfo.IsDarkTheme = strings.EqualFold(m["--is-dark-theme"], "true")
	return themeInfo
}

func initThemes() {
	availableThemes = nil
	defer func() {
		availableThemeInternalNames = container.Set[string]{}
		for _, theme := range availableThemes {
			availableThemeInternalNames.Add(theme.InternalName)
		}
		if !availableThemeInternalNames.Contains(setting.UI.DefaultTheme) {
			setting.LogStartupProblem(1, log.ERROR, "Default theme %q is not available, please correct the '[ui].DEFAULT_THEME' setting in the config file", setting.UI.DefaultTheme)
		}
	}()
	cssFiles, err := public.AssetFS().ListFiles("/assets/css")
	if err != nil {
		log.Error("Failed to list themes: %v", err)
		availableThemes = []*ThemeMetaInfo{defaultThemeMetaInfoByInternalName(setting.UI.DefaultTheme)}
		return
	}
	var foundThemes []*ThemeMetaInfo
	for _, fileName := range cssFiles {
		if strings.HasPrefix(fileName, fileNamePrefix) && strings.HasSuffix(fileName, fileNameSuffix) {
			content, err := public.AssetFS().ReadFile("/assets/css/" + fileName)
			if err != nil {
				log.Error("Failed to read theme file %q: %v", fileName, err)
				continue
			}
			foundThemes = append(foundThemes, parseThemeMetaInfo(fileName, util.UnsafeBytesToString(content)))
		}
	}
	if len(setting.UI.Themes) > 0 {

Review comment (bug):
OK, now I see your approach and I could suggest my opinion.

I didn't use this approach because:

1. It is much slower, the regexp matches runs n times on the full content, n means the number of keys in the future.
2. It is uncontrollable, in `:root` or not, the variables are all used.
3. I do not think "inheriting" is right for this case. Even if it is the CSS's behavior, but we shouldn't inherit non-existing variables from other themes, users should define them explicitly. Inheriting only introduces uncontrollable behavior, but doesn't really bring benefit.
    * For example: `theme-website` or something in the future, they shouldn't be inherited.
5. `--is-dark-theme` is wrong and shouldn't be used. Because it needs to support "auto" in the future, it should be something like `--theme-scheme-mode`.


I do not think this approach is right, and I strongly prefer my solution, fast, stable and controllable.


Code after:
|('(\\'|[^'])*')
|([^'";]+)
)
\s*;
\s*
)
`
	reMetaInfoItem = strings.ReplaceAll(reMetaInfoItem, "\n", "")
	reMetaInfoBlock := `\bgitea-theme-meta-info\s*\{(` + reMetaInfoItem + `+)\}`
	re := regexp.MustCompile(reMetaInfoBlock)
	matchedMetaInfoBlock := re.FindAllStringSubmatch(metaInfoContent, -1)
	if len(matchedMetaInfoBlock) == 0 {
		return nil
	}
	re = regexp.MustCompile(strings.ReplaceAll(reMetaInfoItem, "\n", ""))
	matchedItems := re.FindAllStringSubmatch(matchedMetaInfoBlock[0][1], -1)
	m := map[string]string{}
	for _, item := range matchedItems {
		v := item[3]
		if strings.HasPrefix(v, `"`) {
			v = strings.TrimSuffix(strings.TrimPrefix(v, `"`), `"`)
			v = strings.ReplaceAll(v, `\"`, `"`)
		} else if strings.HasPrefix(v, `'`) {
			v = strings.TrimSuffix(strings.TrimPrefix(v, `'`), `'`)
			v = strings.ReplaceAll(v, `\'`, `'`)
		}
		m[item[2]] = v
	}
	return m
}

func defaultThemeMetaInfoByFileName(fileName string) *ThemeMetaInfo {
	themeInfo := &ThemeMetaInfo{
		FileName:     fileName,
		InternalName: strings.TrimSuffix(strings.TrimPrefix(fileName, fileNamePrefix), fileNameSuffix),
	}
	themeInfo.DisplayName = themeInfo.InternalName
	return themeInfo
}

func defaultThemeMetaInfoByInternalName(fileName string) *ThemeMetaInfo {
	return defaultThemeMetaInfoByFileName(fileNamePrefix + fileName + fileNameSuffix)
}

func parseThemeMetaInfo(fileName, cssContent string) *ThemeMetaInfo {
	themeInfo := defaultThemeMetaInfoByFileName(fileName)
	m := parseThemeMetaInfoToMap(cssContent)
	if m == nil {
		return themeInfo
	}
	themeInfo.DisplayName = m["--theme-display-name"]