163 lines
3.6 KiB
Go
163 lines
3.6 KiB
Go
|
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
|
||
|
}
|