-
Notifications
You must be signed in to change notification settings - Fork 3
/
ContentEntry.go
221 lines (187 loc) · 6.87 KB
/
ContentEntry.go
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package main
import (
"fmt"
"reflect"
"strings"
)
// ContentData is a generic data-holding struct with lots of fields
// to fit all the supported tpyes of Content
// ContentData is a generic struct with lots of fields to fit all
// supported types of Content. Each type will only check its required
// fields. So basically only field "Type" always has to be provided,
// everything else depends on the concrete type.
type ContentData struct {
Type string // the type which this entry represents
Desc string // Description of this parameter
X1, Y1 float64 // first set of coordinates
X2, Y2 float64 // second set of coordinates
XPivot float64 // pivot point on X axis
Font string // the name of the font (if any) that should be used to display the content
Fontsize float64 // size of the font in points
Align string // Alignment of the content: L/C/R + T/M/B
Example string // Example value to be displayed to users
//Flags *[]string
}
// ContentEntry wraps a ContentData object and adds some additional data.
// It also provides a bunch of functions to validate and access the data
// and to perform operations on it.
type ContentEntry struct {
id string
data ContentData
}
// NewContentEntry creates a new ContentEntry object.
func NewContentEntry(id string, data ContentData) (ce ContentEntry) {
Assert(IsSet(id), "ID should always be present here")
ce.id = id
ce.data = data
return
}
// ID returns the id of this ContentEntry object
func (ce *ContentEntry) ID() (result string) {
return ce.id
}
// Type returns the type value this ContentEntry object
func (ce *ContentEntry) Type() (result string) {
return ce.data.Type
}
// Description returns the description of this ContentEntry object
func (ce *ContentEntry) Description() (result string) {
return ce.data.Desc
}
// X1 returns the x1 value of this ContentEntry object
func (ce *ContentEntry) X1() (result float64) {
return ce.data.X1
}
// Y1 returns the y1 value of this ContentEntry object
func (ce *ContentEntry) Y1() (result float64) {
return ce.data.Y1
}
// X2 returns the x2 value of this ContentEntry object
func (ce *ContentEntry) X2() (result float64) {
return ce.data.X2
}
// Y2 returns the y2 value of this ContentEntry object
func (ce *ContentEntry) Y2() (result float64) {
return ce.data.Y2
}
// XPivot returns the xpivot of this ContentEntry object
func (ce *ContentEntry) XPivot() (result float64) {
return ce.data.XPivot
}
// Font returns the font of this ContentEntry object
func (ce *ContentEntry) Font() (result string) {
return ce.data.Font
}
// Fontsize returns of this ContentEntry object
func (ce *ContentEntry) Fontsize() (result float64) {
return ce.data.Fontsize
}
// Align returns the alignment string of this ContentEntry object
func (ce *ContentEntry) Align() (result string) {
return ce.data.Align
}
// Example returns the example of this ContentEntry object
func (ce *ContentEntry) Example() (result string) {
return ce.data.Example
}
// checkThatValuesArePresent takes a list of field names from the ContentData struct and checks
// that these fields neither point to a nil ptr nor that the values behind the pointers contain the
// corresponding types zero value.
func (cd ContentData) checkThatValuesArePresent(names ...string) (isValid bool, err error) {
r := reflect.ValueOf(cd)
for _, name := range names {
field := r.FieldByName(name)
Assert(field.IsValid(), fmt.Sprintf("ContentData does not contain a field with name '%v'", name))
if !IsSet(field.Interface()) {
return false, fmt.Errorf("ContentData object does not contain a value for field '%v'", name)
}
}
return true, nil
}
// IsValid checks whether a ContentEntry object is valid. This means that it
// must contain type information, and depending on the type information
// a certain set of other fields must be set.
func (ce ContentEntry) IsValid() (isValid bool, err error) {
// Type must be checked first, as we decide by that value on which fields to check
isValid, err = ce.data.checkThatValuesArePresent("Type")
if !isValid {
return isValid, err
}
switch ce.Type() {
case "textCell":
return ce.data.checkThatValuesArePresent("X1", "Y1", "X2", "Y2", "Font", "Fontsize", "Align")
default:
return false, fmt.Errorf("ContentData object contains unknown content type '%v'", ce.Type())
}
}
// Describe describes a single ContentEntry object. It returns the
// description as a multi-line string
func (ce *ContentEntry) Describe(verbose bool) (result string) {
var sb strings.Builder
if !verbose {
fmt.Fprintf(&sb, "- %v", ce.ID())
if IsSet(ce.Description()) {
fmt.Fprintf(&sb, ": %v", ce.Description())
}
} else {
fmt.Fprintf(&sb, "- %v\n", ce.ID())
fmt.Fprintf(&sb, "\tDesc: %v\n", ce.Description())
fmt.Fprintf(&sb, "\tType: %v\n", ce.Type())
fmt.Fprintf(&sb, "\tExample: %v", ce.UsageExample())
}
return sb.String()
}
// UsageExample returns an example call for the current ContentEntry object.
// If this is not possible, e.g because no example value is included or
// this is in general not possible for the given type, then a string
// containing "Not available" is returned instead.
func (ce *ContentEntry) UsageExample() (result string) {
switch ce.Type() {
case "textCell":
if !IsSet(ce.Example) {
return fmt.Sprintf("Not available")
}
return fmt.Sprintf("%v=%v", ce.id, QuoteStringIfRequired(ce.Example()))
default:
panic("Unknown ContentEntry type")
}
}
// EntriesAreNotContradicting checks if the provided ContentEntry objects are
// contradicting or not. They are not contradicting all values that are set
// (i.e. contain a non-zero value) within the objects contain the same value.
func EntriesAreNotContradicting(left, right *ContentEntry) (err error) {
vLeft := reflect.ValueOf(left.data)
vRight := reflect.ValueOf(right.data)
for i := 0; i < vLeft.NumField(); i++ {
fieldLeft := vLeft.Field(i)
fieldRight := vRight.Field(i)
fieldName := vLeft.Type().Field(i).Name
if fieldLeft.IsZero() || fieldRight.IsZero() {
continue
}
if fieldLeft.Interface() != fieldRight.Interface() {
return fmt.Errorf("Contradicting data for field '%v':\n- '%v': %v\n- '%v': %v", fieldName, left.ID(), fieldLeft.Interface(), right.ID(), fieldRight.Interface())
}
}
return nil
}
// AddMissingValuesFrom wants to have a documentation
func (ce *ContentEntry) AddMissingValuesFrom(other *ContentEntry) {
vSrc := reflect.ValueOf(other.data)
vDst := reflect.ValueOf(&ce.data).Elem() // go over pointer instead of value as we want to modify
for i := 0; i < vDst.NumField(); i++ {
fieldDst := vDst.Field(i)
fieldSrc := vSrc.Field(i)
if fieldDst.IsZero() && !fieldSrc.IsZero() {
Assert(fieldDst.CanSet(), fmt.Sprintf("Field with index %v must be settable", i))
switch fieldDst.Kind() {
case reflect.String:
fallthrough
case reflect.Float64:
fieldDst.Set(fieldSrc)
default:
panic(fmt.Sprintf("Unsupported struct type '%v', update function", fieldDst.Kind()))
}
}
}
}