package main import ( "encoding/csv" "encoding/json" "errors" "flag" "fmt" "html" "log" "os" "path/filepath" "strings" "github.com/themoeway/jmdict-go" ) type ( StoryEntry struct { Author string `json:"author"` Content string `json:"content"` ModifiedDate string `json:"modifiedDate"` StarredCount int `json:"starredCount"` ReportedCount int `json:"reportedCount"` } KanjiEntry struct { Character string `json:"character"` Reading string `json:"reading"` FrameNumber int `json:"frameNumber"` StrokeCount int `json:"strokeCount"` Story string `json:"story"` Stories []StoryEntry `json:"stories"` } TableRow = []string Table = []TableRow ) func wrapSpan(content, class string) string { return fmt.Sprintf("%s", class, html.EscapeString(content)) } func injectStories(table Table, path string, heisigIndex int) error { fp, err := os.Open(path) if err != nil { return err } defer fp.Close() decoder := json.NewDecoder(fp) var characters []KanjiEntry if err := decoder.Decode(&characters); err != nil { return err } for i, row := range table { for _, character := range characters { if character.Character != row[heisigIndex] { continue } var stories strings.Builder for _, story := range character.Stories { content := strings.ReplaceAll(story.Content, "\n", " ") stories.WriteString(wrapSpan(content, "rtk-story")) } row = append(row, stories.String()) table[i] = row } } return nil } func injectKanjidic(table Table, path string, heisigIndex int) error { fp, err := os.Open(path) if err != nil { return err } defer fp.Close() kd, err := jmdict.LoadKanjidic(fp) if err != nil { return err } for i, row := range table { var found bool for _, character := range kd.Characters { if character.Literal != row[heisigIndex] { continue } var ( meanings strings.Builder kunyomi strings.Builder onyomi strings.Builder ) for _, reading := range character.ReadingMeaning.Readings { switch reading.Type { case "ja_on": onyomi.WriteString(wrapSpan(reading.Value, "rtk-onyomi")) case "ja_kun": kunyomi.WriteString(wrapSpan(reading.Value, "rtk-kunyomi")) } } for _, meaning := range character.ReadingMeaning.Meanings { if meaning.Language == nil { meanings.WriteString(wrapSpan(meaning.Meaning, "rtk-meaning")) } } row = append(row, meanings.String()) row = append(row, kunyomi.String()) row = append(row, onyomi.String()) table[i] = row found = true } if !found { return errors.New("character not found") } } return nil } func loadTable(path string, heisigIndex, columnLimit int) (Table, error) { fp, err := os.Open(path) if err != nil { return nil, err } defer fp.Close() reader := csv.NewReader(fp) reader.Comment = '#' reader.Comma = '\t' table, err := reader.ReadAll() if err != nil { return nil, err } for i := range table { table[i] = table[i][:columnLimit] if len(table[i]) < heisigIndex { return nil, errors.New("unexpected heisig index") } } return table, nil } func saveTable(path string, table Table) error { fp, err := os.Create(path) if err != nil { return err } defer fp.Close() writer := csv.NewWriter(fp) writer.Comma = '\t' if err := writer.WriteAll(table); err != nil { return err } return nil } func main() { var ( storiesPath = flag.String("stories", "", "path for stories JSON") kanjidicPath = flag.String("kanjidic", "", "path for KANJIDIC") heisigIndex = flag.Int("heisig", 0, "heisig index column index") columnLimit = flag.Int("columns", 3, "column trim value") ) flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [options] \n", filepath.Base(os.Args[0])) fmt.Fprintln(os.Stderr, "Options:") flag.PrintDefaults() } flag.Parse() args := flag.Args() if len(args) != 2 { flag.Usage() os.Exit(2) } table, err := loadTable(args[0], *heisigIndex, *columnLimit) if err != nil { log.Fatal(err) } if len(*kanjidicPath) > 0 { if err := injectKanjidic(table, *kanjidicPath, *heisigIndex); err != nil { log.Fatal(err) } } if len(*storiesPath) > 0 { if err := injectStories(table, *storiesPath, *heisigIndex); err != nil { log.Fatal(err) } } if err := saveTable(args[1], table); err != nil { log.Fatal(err) } }