package charset
import (
"encoding/json"
"fmt"
"os"
"sync"
)
var (
readLocalCharsetsOnce sync.Once
localCharsets = make(map[string]*localCharset)
)
type localCharset struct {
Charset
arg string
*class
}
// A class of character sets.
// Each class can be instantiated with an argument specified in the config file.
// Many character sets can use a single class.
type class struct {
from, to func(arg string) (Translator, error)
}
// The set of classes, indexed by class name.
var classes = make(map[string]*class)
func registerClass(charset string, from, to func(arg string) (Translator, error)) {
classes[charset] = &class{from, to}
}
type localFactory struct{}
func (f localFactory) TranslatorFrom(name string) (Translator, error) {
f.init()
name = NormalizedName(name)
cs := localCharsets[name]
if cs == nil {
return nil, fmt.Errorf("character set %q not found", name)
}
if cs.from == nil {
return nil, fmt.Errorf("cannot translate from %q", name)
}
return cs.from(cs.arg)
}
func (f localFactory) TranslatorTo(name string) (Translator, error) {
f.init()
name = NormalizedName(name)
cs := localCharsets[name]
if cs == nil {
return nil, fmt.Errorf("character set %q not found", name)
}
if cs.to == nil {
return nil, fmt.Errorf("cannot translate to %q", name)
}
return cs.to(cs.arg)
}
func (f localFactory) Names() []string {
f.init()
var names []string
for name, cs := range localCharsets {
// add names only for non-aliases.
if localCharsets[cs.Name] == cs {
names = append(names, name)
}
}
return names
}
func (f localFactory) Info(name string) *Charset {
f.init()
lcs := localCharsets[NormalizedName(name)]
if lcs == nil {
return nil
}
// copy the charset info so that callers can't mess with it.
cs := lcs.Charset
return &cs
}
func (f localFactory) init() {
readLocalCharsetsOnce.Do(readLocalCharsets)
}
// charsetEntry is the data structure for one entry in the JSON config file.
// If Alias is non-empty, it should be the canonical name of another
// character set; otherwise Class should be the name
// of an entry in classes, and Arg is the argument for
// instantiating it.
type charsetEntry struct {
Aliases []string
Desc string
Class string
Arg string
}
// readCharsets reads the JSON config file.
// It's done once only, when first needed.
func readLocalCharsets() {
csdata, err := readFile("charsets.json")
if err != nil {
fmt.Fprintf(os.Stderr, "charset: cannot open \"charsets.json\": %v\n", err)
return
}
var entries map[string]charsetEntry
err = json.Unmarshal(csdata, &entries)
if err != nil {
fmt.Fprintf(os.Stderr, "charset: cannot decode config file: %v\n", err)
}
for name, e := range entries {
class := classes[e.Class]
if class == nil {
continue
}
name = NormalizedName(name)
for i, a := range e.Aliases {
e.Aliases[i] = NormalizedName(a)
}
cs := &localCharset{
Charset: Charset{
Name: name,
Aliases: e.Aliases,
Desc: e.Desc,
NoFrom: class.from == nil,
NoTo: class.to == nil,
},
arg: e.Arg,
class: class,
}
localCharsets[cs.Name] = cs
for _, a := range cs.Aliases {
localCharsets[a] = cs
}
}
}
// A general cache store that local character set translators
// can use for persistent storage of data.
var (
cacheMutex sync.Mutex
cacheStore = make(map[interface{}]interface{})
)
func cache(key interface{}, f func() (interface{}, error)) (interface{}, error) {
cacheMutex.Lock()
defer cacheMutex.Unlock()
if x := cacheStore[key]; x != nil {
return x, nil
}
x, err := f()
if err != nil {
return nil, err
}
cacheStore[key] = x
return x, err
}