Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support printing IDs and selecting by ID #62

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
105 changes: 86 additions & 19 deletions pokesay.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package main

import (
"embed"
"encoding/json"
"fmt"
"log"
"os"
"strconv"
"strings"

"github.com/pborman/getopt/v2"
Expand Down Expand Up @@ -39,10 +43,12 @@ func parseFlags() pokesay.Args {

// selection/filtering
name := getopt.StringLong("name", 'n', "", "choose a pokemon from a specific name")
id := getopt.StringLong("id", 'i', "", "choose a pokemon from a specific ID (see `pokesay -l` for IDs)")
category := getopt.StringLong("category", 'c', "", "choose a pokemon from a specific category")

// list operations
listNames := getopt.BoolLong("list-names", 'l', "list all available names")
listNames := getopt.StringLong("list-names", 'l', "", "list all available names")
getopt.Lookup('l').SetOptional()
listCategories := getopt.BoolLong("list-categories", 'L', "list all available categories")

width := getopt.IntLong("width", 'w', 80, "the max speech bubble width")
Expand All @@ -55,6 +61,7 @@ func parseFlags() pokesay.Args {

// info box options
japaneseName := getopt.BoolLong("japanese-name", 'j', "print the japanese name in the info box")
showId := getopt.BoolLong("id-info", 'I', "print the pokemon ID 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")

Expand Down Expand Up @@ -82,10 +89,13 @@ func parseFlags() pokesay.Args {
NoTabSpaces: *noTabSpaces,
NoCategoryInfo: *noCategoryInfo,
ListCategories: *listCategories,
ListNames: *listNames,
ListNames: getopt.GetCount("list-names") > 0,
ListNameToken: *listNames,
Category: *category,
NameToken: *name,
IDToken: *id,
JapaneseName: *japaneseName,
ShowID: *showId,
BoxCharacters: pokesay.DetermineBoxCharacters(*unicodeBorders),
DrawInfoBorder: *drawInfoBorder,
Help: *help,
Expand All @@ -106,25 +116,52 @@ func runListCategories() {
// 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")
func runListNames(token string) {
t := timer.NewTimer("runListNames", true)

pokedex.ReadStructFromBytes[map[string][]int](GOBAllNames)
namesSorted := pokedex.GatherMapKeys(pokedex.ReadStructFromBytes[map[string][]int](GOBAllNames))
t.Mark("read metadata")

s := make(map[string]map[string]string)
exit := false
for i, name := range namesSorted {
metadata := pokedex.ReadMetadataFromEmbedded(GOBCowNames, pokedex.MetadataFpath(MetadataRoot, i))

entries := make(map[string]string, 0)

for _, entry := range metadata.Entries {
k := fmt.Sprintf("%04d.%04d", i, entry.EntryIndex)
entries[k] = strings.Join(entry.Categories, ", ")
}
if token != "" && token == name {
json, _ := json.MarshalIndent(map[string]map[string]string{name: entries}, "", strings.Repeat(" ", 2))
fmt.Fprintln(os.Stdout, string(json))
exit = true
break
}
s[name] = entries
}
t.Mark("read metadata")
if exit {
return
}
json, _ := json.MarshalIndent(s, "", strings.Repeat(" ", 2))
fmt.Fprintln(os.Stdout, string(json))
}

// 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 {
func GenerateNames(metadata pokedex.PokemonMetadata, args pokesay.Args, final pokedex.PokemonEntryMapping) []string {
nameParts := []string{metadata.Name}
if args.JapaneseName {
return []string{
metadata.Name,
fmt.Sprintf("%s (%s)", metadata.JapaneseName, metadata.JapanesePhonetic),
}
} else {
return []string{metadata.Name}
nameParts = append(nameParts, fmt.Sprintf("%s (%s)", metadata.JapaneseName, metadata.JapanesePhonetic))
}
if args.ShowID {
nameParts = append(nameParts, fmt.Sprintf("%s.%04d", metadata.Idx, final.EntryIndex))
}
return nameParts
}

// runPrintByName prints a pokemon matched by a name
Expand All @@ -141,7 +178,35 @@ func runPrintByName(args pokesay.Args) {
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)
pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args, final), final.Categories, GOBCowData)
t.Mark("print")

t.Stop()
t.PrintJson()
}

// runPrintByID prints a pokemon corresponding to a specific ID
// - This reads a list of names from the embedded filesystem
// - It finds the name at alphabetical index `IDToken`
// - 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 runPrintByID(args pokesay.Args) {
t := timer.NewTimer("runPrintByName", true)

idxs := strings.Split(args.IDToken, ".")

idx, _ := strconv.Atoi(idxs[0])
subIdx, _ := strconv.Atoi(idxs[1])

t.Mark("format IDs")

metadata, final, err := pokesay.ChooseByIndex(idx, subIdx, GOBCowNames, MetadataRoot)
if err != nil {
log.Fatal(err)
}
t.Mark("find/read metadata")

pokesay.Print(args, subIdx, GenerateNames(metadata, args, final), final.Categories, GOBCowData)
t.Mark("print")

t.Stop()
Expand All @@ -164,7 +229,7 @@ func runPrintByCategory(args pokesay.Args) {
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)
pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args, final), final.Categories, GOBCowData)
t.Mark("print")

t.Stop()
Expand All @@ -184,7 +249,7 @@ func runPrintByNameAndCategory(args pokesay.Args) {
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)
pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args, final), final.Categories, GOBCowData)
t.Mark("print")

t.Stop()
Expand All @@ -207,7 +272,7 @@ func runPrintRandom(args pokesay.Args) {
final := metadata.Entries[pokesay.RandomInt(len(metadata.Entries))]
t.Mark("choose entry")

pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args), final.Categories, GOBCowData)
pokesay.Print(args, final.EntryIndex, GenerateNames(metadata, args, final), final.Categories, GOBCowData)
t.Mark("print")

t.Stop()
Expand All @@ -231,11 +296,13 @@ func main() {
if args.ListCategories {
runListCategories()
} else if args.ListNames {
runListNames()
runListNames(args.ListNameToken)
} else if args.NameToken != "" && args.Category != "" {
runPrintByNameAndCategory(args)
} else if args.NameToken != "" {
runPrintByName(args)
} else if args.IDToken != "" {
runPrintByID(args)
} else if args.Category != "" {
runPrintByCategory(args)
} else {
Expand Down
49 changes: 36 additions & 13 deletions src/bin/pokedex/pokedex.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path"
"sort"
"strings"

"github.com/tmck-code/pokesay/src/bin"
Expand Down Expand Up @@ -98,34 +99,56 @@ func main() {
fmt.Println("- Found", len(cowfileFpaths), "cowfiles")
// Read pokemon names
pokemonNames := pokedex.ReadNames(args.FromMetadataFname)
fmt.Println("- Read", len(pokemonNames), "pokemon names from", args.FromMetadataFname)

fmt.Println("- Writing entries to file")
pbar := bin.NewProgressBar(len(cowfileFpaths))
for i, fpath := range cowfileFpaths {
data, err := os.ReadFile(fpath)
pokedex.Check(err)
nameTokens := pokedex.GatherMapKeys(pokemonNames)
sort.Strings(nameTokens)
fmt.Println("names:", nameTokens)

pokedex.WriteBytesToFile(data, pokedex.EntryFpath(paths.EntryDirPath, i), true)
pbar.Add(1)
}
fmt.Println("- Read", len(pokemonNames), "pokemon names from", args.FromMetadataFname)

// 1. For each pokemon name, write a metadata file, containing the name information, and
// links to all of the matching cowfile indexes
fmt.Println("- Writing metadata to file")
pokemonMetadata := make([]pokedex.PokemonMetadata, 0)
uniqueNames := make(map[string][]int)
nameVariants := make(map[string][]string)
i := 0
pbar = bin.NewProgressBar(len(pokemonNames))
for key, name := range pokemonNames {
metadata := pokedex.CreateNameMetadata(i, key, name, args.FromDir, cowfileFpaths)
pbar := bin.NewProgressBar(len(pokemonNames))
for i, key := range nameTokens {
name := pokemonNames[key]
// add variant
metadata := pokedex.CreateNameMetadata(fmt.Sprintf("%04d", i), key, name, args.FromDir, cowfileFpaths)
pokedex.WriteStructToFile(metadata, pokedex.MetadataFpath(paths.MetadataDirPath, i))
pokemonMetadata = append(pokemonMetadata, *metadata)
uniqueNames[name.Slug] = append(uniqueNames[name.Slug], i)
i++
pbar.Add(1)
}

fmt.Println("- Writing entries to file")
pbar = bin.NewProgressBar(len(cowfileFpaths))
for i, fpath := range cowfileFpaths {
data, err := os.ReadFile(fpath)
pokedex.Check(err)
entryFpath := pokedex.EntryFpath(paths.EntryDirPath, i)
fmt.Println("writing", fpath, ">", entryFpath)

fpathParts := strings.Split(fpath, "/")
basename := fpathParts[len(fpathParts)-1]
name := strings.SplitN(strings.TrimSuffix(basename, ".cow"), "-", 2)[0]
fmt.Println("name", name)
for i, key := range nameTokens {
if name == key {
fmt.Println("found", name, key, i, pokemonNames[key], pokemonMetadata[i])
nameVariants[name] = append(nameVariants[name], basename)
fmt.Println("name ++", name, fmt.Sprintf("%04d.%04d", i, len(nameVariants[name])))
break
}
}

pokedex.WriteBytesToFile(data, entryFpath, true)
pbar.Add(1)
}

pokedex.WriteStructToFile(uniqueNames, "build/assets/names.txt")

// 2. Create the category struct using the cowfile paths, pokemon names and indexes\
Expand Down
4 changes: 3 additions & 1 deletion src/pokedex/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ type PokemonEntryMapping struct {
}

type PokemonMetadata struct {
Idx string
Name string
JapaneseName string
JapanesePhonetic string
Entries []PokemonEntryMapping
}

func NewMetadata(name string, japaneseName string, japanesePhonetic string, entryMap map[int][][]string) *PokemonMetadata {
func NewMetadata(idx string, name string, japaneseName string, japanesePhonetic string, entryMap map[int][][]string) *PokemonMetadata {

entries := make([]PokemonEntryMapping, 0)

Expand All @@ -30,6 +31,7 @@ func NewMetadata(name string, japaneseName string, japanesePhonetic string, entr
}

return &PokemonMetadata{
Idx: idx,
Name: name,
JapaneseName: japaneseName,
JapanesePhonetic: japanesePhonetic,
Expand Down
30 changes: 25 additions & 5 deletions src/pokedex/pokedex.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,19 +130,21 @@ func Decompress(data []byte) []byte {
return resB.Bytes()
}

func CreateNameMetadata(idx int, key string, name PokemonName, rootDir string, fpaths []string) *PokemonMetadata {
func CreateNameMetadata(idx string, key string, name PokemonName, rootDir string, fpaths []string) *PokemonMetadata {
entryCategories := make(map[int][][]string, 0)

for i, fpath := range fpaths {
basename := strings.TrimPrefix(fpath, rootDir)
if strings.Contains(basename, strings.ToLower(name.Slug)) {
localFpath := strings.TrimPrefix(fpath, rootDir)

if strings.Contains(localFpath, strings.ToLower(name.Slug)) {
data, err := os.ReadFile(fpath)
Check(err)
cats := createCategories(strings.TrimPrefix(fpath, rootDir), data)
cats := createCategories(localFpath, data)
entryCategories[i] = append(entryCategories[i], cats)
}
}
return NewMetadata(
idx,
name.English,
name.Japanese,
name.JapanesePhonetic,
Expand All @@ -169,10 +171,28 @@ func CreateCategoryStruct(rootDir string, metadata []PokemonMetadata, debug bool
}

func createCategories(fpath string, data []byte) []string {
// split the path into parts, e.g. "gen7x/shiny/"
parts := strings.Split(fpath, "/")
// Get the height of the cowfile by counting the number of lines
height := sizeCategory(len(strings.Split(string(data), "\n")))

return append([]string{height}, parts[0:len(parts)-1]...)
// split the fname into parts, e.g. "charizard-mega-y.cow"
// fmt.Println(fpath)
fpathParts := strings.Split(fpath, "/")
basename := fpathParts[len(fpathParts)-1]
names := strings.SplitN(strings.TrimSuffix(basename, ".cow"), "-", 2)
// fmt.Printf("created names from %s: %v\n", fpath, names)

// return a slice of the parts
// height, names from the 2nd to the 2nd to last, and parts from the 1st to the 2nd to last

c := []string{height}
if len(names) > 1 {
c = append(c, names[1:]...)
}
c = append(c, parts[0:len(parts)-1]...)
fmt.Printf("fpath %s, names: %v, categories: %v\n", fpath, names, c)
return c
}

func sizeCategory(height int) string {
Expand Down
13 changes: 13 additions & 0 deletions src/pokesay/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pokesay

import (
"embed"
"errors"
"fmt"
"io/fs"
"log"
Expand Down Expand Up @@ -76,7 +77,19 @@ func fetchMetadataByName(names map[string][]int, nameToken string, metadataFiles
pokedex.MetadataFpath(metadataRootDir, nameChoice),
)
return metadata
}

func ChooseByIndex(idx int, entryIdx int, metadataFiles embed.FS, metadataRootDir string) (pokedex.PokemonMetadata, pokedex.PokemonEntryMapping, error) {
metadata := pokedex.ReadMetadataFromEmbedded(
metadataFiles,
pokedex.MetadataFpath(metadataRootDir, idx),
)
for _, entry := range metadata.Entries {
if entry.EntryIndex == entryIdx {
return metadata, entry, nil
}
}
return pokedex.PokemonMetadata{}, pokedex.PokemonEntryMapping{}, errors.New("could not find pokemon by index")
}

func ChooseByName(names map[string][]int, nameToken string, metadataFiles embed.FS, metadataRootDir string) (pokedex.PokemonMetadata, pokedex.PokemonEntryMapping) {
Expand Down
3 changes: 3 additions & 0 deletions src/pokesay/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ type Args struct {
NoCategoryInfo bool
ListCategories bool
ListNames bool
ListNameToken string
Category string
NameToken string
IDToken string
JapaneseName bool
ShowID bool
BoxCharacters *BoxCharacters
DrawInfoBorder bool
Help bool
Expand Down
Loading