summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/paulrosania/go-charset/charset/local.go
blob: 9776b962f4cfe4aa6c746b538072ddc80f8a104d (plain) (blame)
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
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
}