package main import ( "bufio" "encoding/json" "fmt" "os" "strings" "github.com/beanzilla/gonode" ) // A Parser for handling Endless Sky save format type Parser struct { Debug bool // Do we print debug info (to logs) DebugJSON string // Do we json output a debug file (this is it's name, leave blank for disabled) } // Parses a given file returning a tree of Nodes func (p *Parser) Load(filename string) (Root *gonode.Node, err error) { Root = gonode.NewNode() Root.SetData(filename) file, err := os.Open(filename) if err != nil { return } defer file.Close() radar := bufio.NewScanner(file) content := []string{} for radar.Scan() { line := radar.Text() line = strings.ReplaceAll(line, "\n", "") content = append(content, line) } p.parse(&content, 0, Root) if p.DebugJSON != "" { pay, err := json.MarshalIndent(Root, "", " ") if err != nil { return Root, err } err = os.WriteFile(p.DebugJSON, pay, 0660) if err != nil { return Root, err } } return } // Assistant func // // Processes the current line and next line for making the node tree/structure func (p *Parser) parse(file *[]string, linenum int, node *gonode.Node) { if linenum+1 > len(*file) { if p.Debug { fmt.Println("EOF") } return } line := (*file)[linenum] var next_line string if linenum+2 <= len(*file) { next_line = (*file)[linenum+1] } else { next_line = "" } next_depth := FindDepth(next_line) if p.Debug { fmt.Printf("%4d | '%s'\nNXTL | '%s'\n", linenum, line, next_line) } kid := node.NewChild() depth := FindDepth(line) kid.SetData(CleanLine(line)) if next_depth == depth { if p.Debug { fmt.Printf("Same Depth (%d)\n", depth) } p.parse(file, linenum+1, node) } else if next_depth > depth { if p.Debug { fmt.Printf("Deeper (%d->%d)\n", depth, next_depth) } p.parse(file, linenum+1, kid) } else if next_depth < depth { diff := depth - next_depth at := node if p.Debug { fmt.Printf("Surface (%d<-%d) diff=%d\n", depth, next_depth, diff) } for up := 0; up < diff; up++ { if at.Parent() != nil { at = at.Parent() } } p.parse(file, linenum+1, at) } } // Assistant func // // # Supports writing the current node's line, and the node's children too // // This uses recursive calls to write out the children func (p *Parser) saveassist(file *os.File, node *gonode.Node) error { if p.Debug { fmt.Printf("%s%v\n", strings.Repeat("\t", node.Depth()), node.Data()) } _, err := file.WriteString(strings.Repeat("\t", node.Depth()) + fmt.Sprintf("%v\n", node.Data())) if err != nil { return err } if node.Len() != 0 { for kid := range node.Iter() { err := p.saveassist(file, kid) if err != nil { return err } } } return nil } // Saves the node and it's children to the given filename func (p *Parser) Save(filename string, Root *gonode.Node) error { file, err := os.OpenFile(filename, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0660) if err != nil { return err } defer file.Close() for kid := range Root.Iter() { err = p.saveassist(file, kid) if err != nil { return err } } return nil } // TODO: Move these Util funcs to utils.go (update tests) // Util func // // Finds how many tabs deep the line is func FindDepth(line string) int { if strings.HasPrefix(line, "\t") { depth := 0 for _, c := range line { if c != '\t' { break } depth += 1 } return depth } return 0 } // Util func // // # Cleans the starting tabs from the line // // Utilizes FindDepth to chop off that many characters in the beginning of the line func CleanLine(line string) string { if strings.HasPrefix(line, "\t") { depth := FindDepth(line) return line[depth:] } return line }