coverage.go 4.96 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package language

import (
	"fmt"
	"sort"
)

// The Coverage interface is used to define the level of coverage of an
// internationalization service. Note that not all types are supported by all
// services. As lists may be generated on the fly, it is recommended that users
// of a Coverage cache the results.
type Coverage interface {
	// Tags returns the list of supported tags.
	Tags() []Tag

	// BaseLanguages returns the list of supported base languages.
	BaseLanguages() []Base

	// Scripts returns the list of supported scripts.
	Scripts() []Script

	// Regions returns the list of supported regions.
	Regions() []Region
}

var (
	// Supported defines a Coverage that lists all supported subtags. Tags
	// always returns nil.
	Supported Coverage = allSubtags{}
)

// TODO:
// - Support Variants, numbering systems.
// - CLDR coverage levels.
// - Set of common tags defined in this package.

type allSubtags struct{}

// Regions returns the list of supported regions. As all regions are in a
// consecutive range, it simply returns a slice of numbers in increasing order.
// The "undefined" region is not returned.
func (s allSubtags) Regions() []Region {
	reg := make([]Region, numRegions)
	for i := range reg {
		reg[i] = Region{regionID(i + 1)}
	}
	return reg
}

// Scripts returns the list of supported scripts. As all scripts are in a
// consecutive range, it simply returns a slice of numbers in increasing order.
// The "undefined" script is not returned.
func (s allSubtags) Scripts() []Script {
	scr := make([]Script, numScripts)
	for i := range scr {
		scr[i] = Script{scriptID(i + 1)}
	}
	return scr
}

// BaseLanguages returns the list of all supported base languages. It generates
// the list by traversing the internal structures.
func (s allSubtags) BaseLanguages() []Base {
	base := make([]Base, 0, numLanguages)
	for i := 0; i < langNoIndexOffset; i++ {
		// We included "und" already for the value 0.
		if i != nonCanonicalUnd {
			base = append(base, Base{langID(i)})
		}
	}
	i := langNoIndexOffset
	for _, v := range langNoIndex {
		for k := 0; k < 8; k++ {
			if v&1 == 1 {
				base = append(base, Base{langID(i)})
			}
			v >>= 1
			i++
		}
	}
	return base
}

// Tags always returns nil.
func (s allSubtags) Tags() []Tag {
	return nil
}

// coverage is used used by NewCoverage which is used as a convenient way for
// creating Coverage implementations for partially defined data. Very often a
// package will only need to define a subset of slices. coverage provides a
// convenient way to do this. Moreover, packages using NewCoverage, instead of
// their own implementation, will not break if later new slice types are added.
type coverage struct {
	tags    func() []Tag
	bases   func() []Base
	scripts func() []Script
	regions func() []Region
}

func (s *coverage) Tags() []Tag {
	if s.tags == nil {
		return nil
	}
	return s.tags()
}

// bases implements sort.Interface and is used to sort base languages.
type bases []Base

func (b bases) Len() int {
	return len(b)
}

func (b bases) Swap(i, j int) {
	b[i], b[j] = b[j], b[i]
}

func (b bases) Less(i, j int) bool {
	return b[i].langID < b[j].langID
}

// BaseLanguages returns the result from calling s.bases if it is specified or
// otherwise derives the set of supported base languages from tags.
func (s *coverage) BaseLanguages() []Base {
	if s.bases == nil {
		tags := s.Tags()
		if len(tags) == 0 {
			return nil
		}
		a := make([]Base, len(tags))
		for i, t := range tags {
			a[i] = Base{langID(t.lang)}
		}
		sort.Sort(bases(a))
		k := 0
		for i := 1; i < len(a); i++ {
			if a[k] != a[i] {
				k++
				a[k] = a[i]
			}
		}
		return a[:k+1]
	}
	return s.bases()
}

func (s *coverage) Scripts() []Script {
	if s.scripts == nil {
		return nil
	}
	return s.scripts()
}

func (s *coverage) Regions() []Region {
	if s.regions == nil {
		return nil
	}
	return s.regions()
}

// NewCoverage returns a Coverage for the given lists. It is typically used by
// packages providing internationalization services to define their level of
// coverage. A list may be of type []T or func() []T, where T is either Tag,
// Base, Script or Region. The returned Coverage derives the value for Bases
// from Tags if no func or slice for []Base is specified. For other unspecified
// types the returned Coverage will return nil for the respective methods.
func NewCoverage(list ...interface{}) Coverage {
	s := &coverage{}
	for _, x := range list {
		switch v := x.(type) {
		case func() []Base:
			s.bases = v
		case func() []Script:
			s.scripts = v
		case func() []Region:
			s.regions = v
		case func() []Tag:
			s.tags = v
		case []Base:
			s.bases = func() []Base { return v }
		case []Script:
			s.scripts = func() []Script { return v }
		case []Region:
			s.regions = func() []Region { return v }
		case []Tag:
			s.tags = func() []Tag { return v }
		default:
			panic(fmt.Sprintf("language: unsupported set type %T", v))
		}
	}
	return s
}