1package ini
2
3import (
4	"fmt"
5	"sort"
6)
7
8// Visitor is an interface used by walkers that will
9// traverse an array of ASTs.
10type Visitor interface {
11	VisitExpr(AST) error
12	VisitStatement(AST) error
13}
14
15// DefaultVisitor is used to visit statements and expressions
16// and ensure that they are both of the correct format.
17// In addition, upon visiting this will build sections and populate
18// the Sections field which can be used to retrieve profile
19// configuration.
20type DefaultVisitor struct {
21	scope    string
22	Sections Sections
23}
24
25// NewDefaultVisitor return a DefaultVisitor
26func NewDefaultVisitor() *DefaultVisitor {
27	return &DefaultVisitor{
28		Sections: Sections{
29			container: map[string]Section{},
30		},
31	}
32}
33
34// VisitExpr visits expressions...
35func (v *DefaultVisitor) VisitExpr(expr AST) error {
36	t := v.Sections.container[v.scope]
37	if t.values == nil {
38		t.values = values{}
39	}
40
41	switch expr.Kind {
42	case ASTKindExprStatement:
43		opExpr := expr.GetRoot()
44		switch opExpr.Kind {
45		case ASTKindEqualExpr:
46			children := opExpr.GetChildren()
47			if len(children) <= 1 {
48				return NewParseError("unexpected token type")
49			}
50
51			rhs := children[1]
52
53			if rhs.Root.Type() != TokenLit {
54				return NewParseError("unexpected token type")
55			}
56
57			key := EqualExprKey(opExpr)
58			v, err := newValue(rhs.Root.ValueType, rhs.Root.base, rhs.Root.Raw())
59			if err != nil {
60				return err
61			}
62
63			t.values[key] = v
64		default:
65			return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
66		}
67	default:
68		return NewParseError(fmt.Sprintf("unsupported expression %v", expr))
69	}
70
71	v.Sections.container[v.scope] = t
72	return nil
73}
74
75// VisitStatement visits statements...
76func (v *DefaultVisitor) VisitStatement(stmt AST) error {
77	switch stmt.Kind {
78	case ASTKindCompletedSectionStatement:
79		child := stmt.GetRoot()
80		if child.Kind != ASTKindSectionStatement {
81			return NewParseError(fmt.Sprintf("unsupported child statement: %T", child))
82		}
83
84		name := string(child.Root.Raw())
85		v.Sections.container[name] = Section{}
86		v.scope = name
87	default:
88		return NewParseError(fmt.Sprintf("unsupported statement: %s", stmt.Kind))
89	}
90
91	return nil
92}
93
94// Sections is a map of Section structures that represent
95// a configuration.
96type Sections struct {
97	container map[string]Section
98}
99
100// GetSection will return section p. If section p does not exist,
101// false will be returned in the second parameter.
102func (t Sections) GetSection(p string) (Section, bool) {
103	v, ok := t.container[p]
104	return v, ok
105}
106
107// values represents a map of union values.
108type values map[string]Value
109
110// List will return a list of all sections that were successfully
111// parsed.
112func (t Sections) List() []string {
113	keys := make([]string, len(t.container))
114	i := 0
115	for k := range t.container {
116		keys[i] = k
117		i++
118	}
119
120	sort.Strings(keys)
121	return keys
122}
123
124// Section contains a name and values. This represent
125// a sectioned entry in a configuration file.
126type Section struct {
127	Name   string
128	values values
129}
130
131// Has will return whether or not an entry exists in a given section
132func (t Section) Has(k string) bool {
133	_, ok := t.values[k]
134	return ok
135}
136
137// ValueType will returned what type the union is set to. If
138// k was not found, the NoneType will be returned.
139func (t Section) ValueType(k string) (ValueType, bool) {
140	v, ok := t.values[k]
141	return v.Type, ok
142}
143
144// Bool returns a bool value at k
145func (t Section) Bool(k string) bool {
146	return t.values[k].BoolValue()
147}
148
149// Int returns an integer value at k
150func (t Section) Int(k string) int64 {
151	return t.values[k].IntValue()
152}
153
154// Float64 returns a float value at k
155func (t Section) Float64(k string) float64 {
156	return t.values[k].FloatValue()
157}
158
159// String returns the string value at k
160func (t Section) String(k string) string {
161	_, ok := t.values[k]
162	if !ok {
163		return ""
164	}
165	return t.values[k].StringValue()
166}
167