Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
91 lines
2.7 KiB
Go
91 lines
2.7 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Shell is the public presentation configuration, independent of deployment topology and auth.
|
|
type Shell struct {
|
|
Mode string `yaml:"mode" json:"mode"`
|
|
DefaultLocale string `yaml:"defaultLocale" json:"defaultLocale"`
|
|
Adapter string `yaml:"adapter,omitempty" json:"adapter,omitempty"`
|
|
}
|
|
|
|
// NormalizeDefaults resolves omitted installation settings before projecting them to the browser.
|
|
func (s *Shell) NormalizeDefaults() error {
|
|
if s.Mode == "" {
|
|
s.Mode = "embedded"
|
|
}
|
|
if s.DefaultLocale == "" {
|
|
s.DefaultLocale = "en"
|
|
}
|
|
if s.Mode != "embedded" && s.Mode != "full" {
|
|
return errors.New("shell.mode must be full or embedded")
|
|
}
|
|
if !wellFormedLocale(s.DefaultLocale) {
|
|
return errors.New("shell.defaultLocale must be a well-formed BCP47 language tag")
|
|
}
|
|
if s.Adapter != "" && s.Adapter != "omics-portal" {
|
|
return errors.New("shell.adapter must be omics-portal")
|
|
}
|
|
if s.Mode == "full" {
|
|
s.Adapter = ""
|
|
return nil
|
|
}
|
|
if s.Adapter == "" {
|
|
s.Adapter = "omics-portal"
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RFC 5646 section 2.1 grammar. Capture variants and extensions to reject duplicates.
|
|
// Validate syntax without consulting a language registry or the frontend's available catalogs.
|
|
var localePattern = regexp.MustCompile(`^(?:` +
|
|
`(?:[a-z]{2,3}(?:-[a-z]{3}){0,3}|[a-z]{4}|[a-z]{5,8})` + // language / extlang
|
|
`(?:-[a-z]{4})?(?:-(?:[a-z]{2}|[0-9]{3}))?` + // script / region
|
|
`((?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))*)` + // variants
|
|
`((?:-[0-9a-wy-z](?:-[a-z0-9]{2,8})+)*)` + // extensions
|
|
`(?:-x(?:-[a-z0-9]{1,8})+)?` + // private use after a language
|
|
`|x(?:-[a-z0-9]{1,8})+)$`) // private use alone
|
|
|
|
func wellFormedLocale(locale string) bool {
|
|
// BCP47 is ASCII. Check before case folding, which could otherwise map Unicode to ASCII.
|
|
for _, character := range locale {
|
|
if character > 127 {
|
|
return false
|
|
}
|
|
}
|
|
tag := strings.ToLower(locale)
|
|
// These grandfathered irregular tags do not match the normal language-tag grammar.
|
|
switch tag {
|
|
case "en-gb-oed", "i-ami", "i-bnn", "i-default", "i-enochian", "i-hak", "i-klingon",
|
|
"i-lux", "i-mingo", "i-navajo", "i-pwn", "i-tao", "i-tay", "i-tsu",
|
|
"sgn-be-fr", "sgn-be-nl", "sgn-ch-de":
|
|
return true
|
|
}
|
|
parts := localePattern.FindStringSubmatch(tag)
|
|
if parts == nil {
|
|
return false
|
|
}
|
|
variants := make(map[string]bool)
|
|
for _, variant := range strings.Split(strings.TrimPrefix(parts[1], "-"), "-") {
|
|
if variants[variant] {
|
|
return false
|
|
}
|
|
variants[variant] = true
|
|
}
|
|
singletons := make(map[string]bool)
|
|
for _, subtag := range strings.Split(parts[2], "-") {
|
|
if len(subtag) != 1 {
|
|
continue
|
|
}
|
|
if singletons[subtag] {
|
|
return false
|
|
}
|
|
singletons[subtag] = true
|
|
}
|
|
return true
|
|
}
|