-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathpokesay.go
250 lines (211 loc) · 8.93 KB
/
pokesay.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
package main
import (
"embed"
"fmt"
"strings"
"github.com/pborman/getopt/v2"
"github.com/tmck-code/pokesay/src/pokedex"
"github.com/tmck-code/pokesay/src/pokesay"
"github.com/tmck-code/pokesay/src/timer"
)
var (
//go:embed build/assets/category_keys.txt
GOBCategoryKeys []byte
//go:embed build/assets/names.txt
GOBAllNames []byte
//go:embed build/assets/total.txt
GOBTotal []byte
//go:embed build/assets/cows/*cow
GOBCowData embed.FS
//go:embed build/assets/metadata/*metadata
GOBCowNames embed.FS
//go:embed all:build/assets/categories
GOBCategories embed.FS
CategoryRoot string = "build/assets/categories" // the root directory of the pokemon categories
MetadataRoot string = "build/assets/metadata" // the root directory of the pokemon metadata
CowDataRoot string = "build/assets/cows" // the root directory of the pokemon cow data
)
// parseFlags parses the command line flags and returns a pokesay.Args struct
func parseFlags() pokesay.Args {
help := getopt.BoolLong("help", 'h', "display this help message")
// print verbose output (currently timer output)
verbose := getopt.BoolLong("verbose", 'v', "print verbose output", "verbose")
// selection/filtering
name := getopt.StringLong("name", 'n', "", "choose a pokemon from a specific name")
category := getopt.StringLong("category", 'c', "", "choose a pokemon from a specific category")
// list operations
listNames := getopt.BoolLong("list-names", 'l', "list all available names")
listCategories := getopt.BoolLong("list-categories", 'L', "list all available categories")
width := getopt.IntLong("width", 'w', 80, "the max speech bubble width")
// speech bubble options
tabWidth := getopt.IntLong("tab-width", 't', 4, "replace any tab characters with N spaces")
noWrap := getopt.BoolLong("no-wrap", 'W', "disable text wrapping (fastest)")
noTabSpaces := getopt.BoolLong("no-tab-spaces", 's', "do not replace tab characters (fastest)")
fastest := getopt.BoolLong("fastest", 'f', "run with the fastest possible configuration (--nowrap & --notabspaces)")
noBubble := getopt.BoolLong("no-bubble", 'B', "do not draw the speech bubble")
// info box options
japaneseName := getopt.BoolLong("japanese-name", 'j', "print the japanese name in the info box")
noCategoryInfo := getopt.BoolLong("no-category-info", 'C', "do not print pokemon category information in the info box")
drawInfoBorder := getopt.BoolLong("info-border", 'b', "draw a border around the info box")
// other option
unicodeBorders := getopt.BoolLong("unicode-borders", 'u', "use unicode characters to draw the border around the speech box (and info box if --info-border is enabled)")
getopt.Parse()
var args pokesay.Args
if *fastest {
args = pokesay.Args{
Width: *width,
NoWrap: true,
TabSpaces: " ",
NoTabSpaces: true,
BoxChars: pokesay.DetermineBoxChars(false),
Help: *help,
Verbose: *verbose,
}
} else {
args = pokesay.Args{
Width: *width,
NoWrap: *noWrap,
DrawBubble: !*noBubble,
TabSpaces: strings.Repeat(" ", *tabWidth),
NoTabSpaces: *noTabSpaces,
NoCategoryInfo: *noCategoryInfo,
ListCategories: *listCategories,
ListNames: *listNames,
Category: *category,
NameToken: *name,
JapaneseName: *japaneseName,
BoxChars: pokesay.DetermineBoxChars(*unicodeBorders),
DrawInfoBorder: *drawInfoBorder,
Help: *help,
Verbose: *verbose,
}
}
return args
}
// runListCategories prints all available categories
// - This reads a list of categories from the embedded filesystem
// - prints the list of categories, and the total number of categories
func runListCategories() {
categories := pokedex.ReadStructFromBytes[[]string](GOBCategoryKeys)
fmt.Printf("%s\n%d %s\n", strings.Join(categories, " "), len(categories), "total categories")
}
// runListNames prints all available pokemon names
// - This reads a struct of {name -> metadata indexes} from the embedded filesystem
// - prints all the keys of the struct, and the total number of names
func runListNames() {
names := pokesay.ListNames(
pokedex.ReadStructFromBytes[map[string][]int](GOBAllNames),
)
fmt.Printf("%s\n%d %s\n", strings.Join(names, " "), len(names), "total names")
}
// GenerateNames returns a list of names to print
// - If the japanese name flag is set, it returns both the english and japanese names
// - Otherwise, it returns just the english name
func GenerateNames(metadata pokedex.PokemonMetadata, args pokesay.Args) []string {
if args.JapaneseName {
return []string{
metadata.Name,
fmt.Sprintf("%s (%s)", metadata.JapaneseName, metadata.JapanesePhonetic),
}
} else {
return []string{metadata.Name}
}
}
// runPrintByName prints a pokemon matched by a name
// The name must match the lowercase name of the pokemon (TODO: improve this behaviour)
// - This reads a struct of {name -> metadata indexes} from the embedded filesystem
// - It matches the name to a metadata index, loads the corresponding metadata file, and then chooses a random entry
// - Finally, it prints the pokemon
func runPrintByName(args pokesay.Args) {
t := timer.NewTimer("runPrintByName", true)
names := pokedex.ReadStructFromBytes[map[string][]int](GOBAllNames)
t.Mark("read name struct")
metadata, final := pokesay.ChooseByName(names, args.NameToken, GOBCowNames, MetadataRoot)
t.Mark("find/read metadata")
pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args), final.Categories, GOBCowData)
t.Mark("print")
t.Stop()
t.PrintJson()
}
// runPrintByCategory prints a pokemon matched by a category
// - This loads a GOB file containing a pokemon "category" search struct from the embedded filesystem
// - It chooses a random category file from the corresponding category directory
// - It reads the category file and chooses a random pokemon from the category
// - # TODO: as each category dir contains files with a singular entry of {metadata index/entry index},
// - # this means that pokemon that are in the same category multiple times will be chosen more often
//
// - It reads the metadata file of the chosen pokemon and chooses the corresponding entry from the category search
// - Finally, it prints the pokemon
func runPrintByCategory(args pokesay.Args) {
t := timer.NewTimer("runPrintByCategory", true)
dirPath := pokedex.CategoryDirpath(CategoryRoot, args.Category)
dir, _ := GOBCategories.ReadDir(dirPath)
metadata, final := pokesay.ChooseByCategory(args.Category, dir, GOBCategories, CategoryRoot, GOBCowNames, MetadataRoot)
pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args), final.Categories, GOBCowData)
t.Mark("print")
t.Stop()
t.PrintJson()
}
// runPrintByNameAndCategory prints a pokemon matched by a name and category
// - This reads a struct of {name -> metadata indexes} from the embedded filesystem
// - It matches the name to a metadata index, loads the corresponding metadata file, and then randomly chooses an entry that matches the category
// - Finally, it prints the pokemon
func runPrintByNameAndCategory(args pokesay.Args) {
t := timer.NewTimer("runPrintByNameAndCategory", true)
names := pokedex.ReadStructFromBytes[map[string][]int](GOBAllNames)
t.Mark("read name struct")
metadata, final := pokesay.ChooseByNameAndCategory(names, args.NameToken, GOBCowNames, MetadataRoot, args.Category)
t.Mark("find/read metadata")
pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args), final.Categories, GOBCowData)
t.Mark("print")
t.Stop()
t.PrintJson()
}
// runPrintRandom prints a random pokemon
// - This loads a specific GOB file from the embedded filesystem that contains the number of pokemon
// - generates a random number between 0 and the number of pokemon
// - reads the metadata file of at `<index>.metadata` as a PokemonMetadata struct
// - chooses a random entry from the metadata file
// - finally prints the pokemon
func runPrintRandom(args pokesay.Args) {
t := timer.NewTimer("runPrintRandom", true)
choice := pokesay.RandomInt(pokedex.ReadIntFromBytes(GOBTotal))
t.Mark("choose index")
metadata := pokedex.ReadMetadataFromEmbedded(GOBCowNames, pokedex.MetadataFpath(MetadataRoot, choice))
t.Mark("read metadata")
final := metadata.Entries[pokesay.RandomInt(len(metadata.Entries))]
t.Mark("choose entry")
pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args), final.Categories, GOBCowData)
t.Mark("print")
t.Stop()
t.PrintJson()
}
func main() {
args := parseFlags()
// if the -h/--help flag is set, print usage and exit
if args.Help {
getopt.Usage()
return
}
if args.Verbose {
fmt.Println("Verbose output enabled")
timer.DEBUG = true
}
t := timer.NewTimer("main", true)
if args.ListCategories {
runListCategories()
} else if args.ListNames {
runListNames()
} else if args.NameToken != "" && args.Category != "" {
runPrintByNameAndCategory(args)
} else if args.NameToken != "" {
runPrintByName(args)
} else if args.Category != "" {
runPrintByCategory(args)
} else {
runPrintRandom(args)
}
t.Mark("op")
t.Stop()
t.PrintJson()
}