-
Notifications
You must be signed in to change notification settings - Fork 1
/
mesostic.go
292 lines (251 loc) · 8.77 KB
/
mesostic.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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
/*
Mesostic Engine
*/
package main
import (
"fmt"
"io/ioutil"
"os"
"sort"
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/rs/zerolog/log"
)
// LineFrag ::: Data model describing a processed LineFragment.
type LineFrag struct {
Index int // Line number from the original text.
LineNum int // The assigned line number for these fragments.
WChars int // WestSide character count.
Data string // The new Mesostic line.
}
// LineFrags ::: string slice for the collection of LineFrag entries to be sorted
type LineFrags []LineFrag
// Hash table of line fragments
var fragMents = make(map[string]LineFrag)
// this will need to be passed as a pointer like the ictus/nexus stuff <<< ??? why did i write this
// for some reason when i pull this into the mesoLine() function,
// the Ictus rotation breaks, or the display of it breaks. not sure.
// but it only works when this is a global.
var fragCount int // global to count total fragment combinations (i.e. lines)
// Spine ::: Process the SpineString
// Construct a slice of lowercase SpineString characters that can be rotated by Ictus().
func Spine(z string) []string {
var zch []string
for h := 0; h < len(z); h++ {
zch = append(zch, strings.ToLower(string(z[h])))
}
return zch
}
// mesoLine ::: finds the current SpineString character in the current line
//
// The West Fragment is everything to the left of the SpineString character.
// The East Fragment is everything to the right of the SpineString character.
//
// s == the current line to process
// z == slice of SpineString characters
// c == line number
// ict == ictus of the SpineString characters
// nex == next ictus (not always ict + 1)
// spaces == Pointer ::: current left-aligned whitespace
//
func mesoLine(s string, z []string, c int, ict *int, nex *int, spaces *int) bool {
hTimer := prometheus.NewTimer(hpschdMesolineTimer)
defer hTimer.ObserveDuration()
var wstack []string // slice for rebuilding the west fragment
var estack []string // slice for rebuilding the east fragment
var found bool // the character was found in this line
mode := 0 // the Mesostic algorithm mode, always starts with 0
CharLoop:
// step through the current string and process mesostic rules
for i := 0; i < len(s); i++ {
char := string(s[i])
/*
WestSide ::: Everything to the LEFT AND INCLUDING the SpineString, this is mode 0
EastSide ::: Everything to the RIGHT AND NOT the SpineString, includes:
mode 1 50% Mesostic ::: No occurance of the current Spine String Character in back of it.
mode 2 100% Mesostic ::: No occurance of the current Spine String Character in back OR in front of it.
mode 3 Meso-Acrostic ::: No pre/post rules, any character can appear before or after the Spine String Char.
*/
switch mode {
case 0:
switch {
case char != z[*ict]:
// not found
log.Debug().Str("z", z[*ict]).Msg("notfound")
if i == len(s)-1 {
// last character of the line
if char != z[*ict] {
// the final character is not the SpineString
// in this version, the line is thrown out
// in future versions, the line may only be thrown out
// if a certain tolerance is met
// e.g.: the number of false successes
log.Debug().Str("SSCHAR", z[*ict]).Str("LNCHAR", char).Msg("EOL")
found = false
wstack = nil
break CharLoop // We're done.
}
}
wstack = append(wstack, char)
case char == z[*ict]:
// SpineString hit!
log.Debug().Str("z", z[*ict]).Msg("spinestr")
found = true
char = strings.ToUpper(char) // Spine Character is capitalized
wstack = append(wstack, char) // Appended to the string
mode = 1
break // re-evaluate the switch with mode set
}
case 1:
/*
Allow the current SSchar in the remainder of the line,
but do not print anything on this line at or beyond the next SSchar
because that will appear on the next line and cannot have itself preceeding it.
This method preserves the line returns found in the source.
*/
if char != z[*nex] {
estack = append(estack, char)
} else {
break CharLoop // We're done.
}
}
}
// Post processing
fragmentW := strings.Join(wstack, "") // WestSide fragment
fragmentE := strings.Join(estack, "") // EastSide fragment
fragkey := shakey(fragmentW + fmt.Sprint(fragCount)) // unique identifier and consistent key sizes
fragCount++
// Add results to a new map entry
// Some or all of this might be better off as pointers...
fragMents[fragkey] = LineFrag{Index: c, LineNum: fragCount, WChars: len(fragmentW), Data: fragmentW + fragmentE}
// record the longest WestSide fragment length, but calculate it from the passed value
if len(fragmentW) > *spaces {
*spaces = len(fragmentW)
}
return found
}
// Ictus ::: Enables the rotation of SpineString characters by operating on the index.
//
// lss == length of SpineString
// isp == pointer to the ictus
// nsp == pointer to the next ictus
//
func Ictus(lss int, isp *int, nsp *int) {
// a mesostic line has been finished,
// increase ictus, i.e. the current character position
if *isp < lss-1 {
*isp++
} else if *isp == lss-1 { // last element, rotate
*isp = 0
}
// this is used for lookahead
// update nexus, i.e. the next character position
// if we've hit the end, we wrap to the first character
if *isp == lss-1 { // now last element, next is 0
*nsp = 0
} else {
*nsp = *isp + 1
}
}
// Preus ::: Ictus, but rewind.
func Preus(lss int, isp *int, nsp *int) {
if *isp > 0 { // not first element, simple subtraction
*isp--
} else { // now first element, rotate backwards
*isp = lss - 1
}
// nexus is still calcualted the same way
if *isp == lss-1 {
*nsp = 0
} else {
*nsp = *isp + 1
}
}
// Sort Interface ::: linefragments by LineNum (lnc)
func (ls LineFrags) Len() int {
return len(ls)
}
func (ls LineFrags) Swap(i, j int) {
ls[i], ls[j] = ls[j], ls[i]
}
func (ls LineFrags) Less(i, j int) bool {
return ls[i].LineNum < ls[j].LineNum
}
// mesoMain ::: Takes a filename as input for processing.
// Alternate main()
// TODO: only launch the api if a "server" flag is given.
// Otherwise, it's a standalone CLI tool.
//
// f == filename for processing
// z == Spine String
// o == channel for return
//
func mesoMain(f string, z string, o chan<- string) {
var lnc int // line counts for the Index
var ictus int // SpineString character address
var nexus int = ictus + 1 // Next SpineString character address
var spaces int = 0 // Left-aligned whitespace for all lines
// split the SpineString into a slice of characters
spineChars := Spine(z)
spineString := strings.Join(spineChars, "")
// DEBUG ::: fmt.Sprint(spineString)
source, err := ioutil.ReadFile(f)
if err != nil {
log.Error()
}
/*
Break down the file into lines and manipulate them into a new unordered mesostic.
mesoLine() populates a global map, returning a boolean success status.
If the SpineString character was found,
Ictus() rotates the SpineString position forward one spot,
and the next line processed will have the next character to match.
If the SpineString character wasn't found,
rewind the SpineString with Preus(),
making the SpineString character "stay in place", so it is matched in the next line.
Currently, if the SSchar is never found, the Mesostic will be effectively blank.
There might need to be a tolerance setting here:
If not found X number of times (say, a fraction of the length of source), then rotate fwd.
*/
for _, sline := range strings.Split(string(source), "\n") {
lnc++
success := mesoLine(strings.ToLower(sline), spineChars, lnc, &ictus, &nexus, &spaces)
if !success {
Preus(len(spineString), &ictus, &nexus)
}
Ictus(len(spineString), &ictus, &nexus)
log.Debug().
Int("lnc", lnc).
Int("ictus", ictus).
Int("spaces", spaces).
Bool("success", success).
Msg("")
}
// Sort & Print //
// Lines are moved from the global map to a slice to be sorted.
var fragstack []string
var linefragments LineFrags
for k := range fragMents {
linefragments = append(linefragments, fragMents[k])
delete(fragMents, k)
}
// Sort is configured on LineNum.
sort.Sort(linefragments)
for i := 0; i < len(linefragments); i++ {
// define 'West Side' whitespace as
// (length of the longest fragment) - (length of the current fragment)
padMe := spaces - linefragments[i].WChars
printspace := strings.Repeat(" ", padMe)
// format the new line with leading whitespace and trailing line return
fragstack = append(fragstack, printspace)
fragstack = append(fragstack, linefragments[i].Data)
fragstack = append(fragstack, "\n")
}
mesostic := strings.Join(fragstack, "")
o <- fmt.Sprint(mesostic)
close(o)
// tmp scratch is no longer needed
var ferr = os.Remove(f)
if ferr != nil {
log.Error()
}
}