/
effective_wiki
/
exporter
Обзор
Документация
Войти
/
effective_wiki
/
exporter
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
internal/tree/parser.go
221 строка
6 KB
Nikolay Koren
feat: add v3 API batch import support and datasource processing optimization with caching
05 дек 2025, 11:31
05 дек 2025, 11:31
e08eb5e
Код
Авторство
О чём код?
package tree import ( "bufio" "fmt" "os" "strings" ) // Tree represents a parsed tree structure type Tree struct { Root *Node Nodes map[string]*Node // map of ID to Node for quick lookup } // LoadFromFile loads and parses the tree structure from structure.txt func LoadFromFile(filepath string) (*Tree, error) { fmt.Printf("Loading tree structure from %s\n", filepath) file, err := os.Open(filepath) if err != nil { return nil, fmt.Errorf("failed to open structure file: %w", err) } defer file.Close() tree := &Tree{ Nodes: make(map[string]*Node), } scanner := bufio.NewScanner(file) var stack []*Node // stack[level] = last node on this level lineNumber := 0 for scanner.Scan() { lineNumber++ line := scanner.Text() // Пропускаем пустые строки if strings.TrimSpace(line) == "" { continue } // Первая строка — это корень Workspace if tree.Root == nil { tree.Root = &Node{ Type: "workspace_root", ID: "root", Title: strings.TrimSpace(line), Children: make([]*Node, 0), Parent: nil, } // Стек пока пустой, так как дети начинаются с уровня 0 continue } level, payload, err := parseIndentAndPayload(line) if err != nil { return nil, fmt.Errorf("parse error on line %d: %w", lineNumber, err) } nodeType, id, title, err := parseNodePayload(payload) if err != nil { return nil, fmt.Errorf("parse node error on line %d: %w", lineNumber, err) } // Определяем родителя по уровню var parent *Node if level == 0 { parent = tree.Root } else { if level-1 >= len(stack) || stack[level-1] == nil { return nil, fmt.Errorf("invalid indentation on line %d: parent level %d not found", lineNumber, level-1) } parent = stack[level-1] } // Проверяем, существует ли уже узел с таким ID existingNode, exists := tree.Nodes[id] var node *Node if exists { // Используем существующий узел (с его детьми) node = existingNode // Добавляем ссылку на существующий узел к новому родителю parent.Children = append(parent.Children, node) } else { // Создаем новый узел node = &Node{ Type: nodeType, ID: id, Title: title, Children: make([]*Node, 0), Parent: parent, } // Добавляем в дерево parent.Children = append(parent.Children, node) tree.Nodes[id] = node } // Обновляем стек: на текущем уровне этот узел становится последним if level < len(stack) { stack[level] = node stack = stack[:level+1] } else { // возможно, что level == len(stack) stack = append(stack, node) } } if err := scanner.Err(); err != nil { return nil, fmt.Errorf("failed to scan structure file: %w", err) } return tree, nil } func parseIndentAndPayload(line string) (int, string, error) { level := 0 i := 0 for i < len(line) { switch { case strings.HasPrefix(line[i:], "│ "): level++ i += len("│ ") case strings.HasPrefix(line[i:], " "): level++ i += len(" ") case strings.HasPrefix(line[i:], "├── "): i += len("├── ") return level, line[i:], nil case strings.HasPrefix(line[i:], "└── "): i += len("└── ") return level, line[i:], nil default: return level, strings.TrimSpace(line[i:]), nil } } return 0, "", fmt.Errorf("cannot parse indent for line %q", line) } func parseNodePayload(payload string) (nodeType, id, title string, err error) { payload = strings.TrimSpace(payload) if payload == "" { return "", "", "", fmt.Errorf("empty node payload") } spaceIdx := strings.IndexByte(payload, ' ') if spaceIdx == -1 { return "", "", "", fmt.Errorf("invalid payload, no space: %q", payload) } typeAndID := payload[:spaceIdx] titlePart := strings.TrimSpace(payload[spaceIdx+1:]) parts := strings.SplitN(typeAndID, ":", 2) if len(parts) != 2 { return "", "", "", fmt.Errorf("invalid type:id segment: %q", typeAndID) } nodeType = parts[0] id = parts[1] title = strings.Trim(titlePart, "\"") title = unescapeTitle(title) return nodeType, id, title, nil } func unescapeTitle(title string) string { title = strings.ReplaceAll(title, "\\n", "\n") title = strings.ReplaceAll(title, "\\\"", "\"") title = strings.ReplaceAll(title, "\\\\", "\\") return title } // FindNode finds a node by its type:id string func (t *Tree) FindNode(nodeRef string) (*Node, error) { // nodeRef format: "type:id" parts := strings.SplitN(nodeRef, ":", 2) if len(parts) != 2 { return nil, fmt.Errorf("invalid nodeRef format, expected type:id, got: %s", nodeRef) } wantType := parts[0] id := parts[1] node, ok := t.Nodes[id] if !ok { return nil, fmt.Errorf("node not found: %s", nodeRef) } if node.Type != wantType { return nil, fmt.Errorf("node type mismatch: expected %s, got %s", wantType, node.Type) } return node, nil } // GetSubtree returns a node and all its descendants func (t *Tree) GetSubtree(node *Node, includeChildren bool) []*Node { if !includeChildren { return []*Node{node} } nodes := []*Node{node} for _, child := range node.Children { nodes = append(nodes, t.GetSubtree(child, true)...) } return nodes } // FindNodeByID находит узел по ID в дереве func (t *Tree) FindNodeByID(nodeID string) *Node { if t.Nodes == nil { return nil } return t.Nodes[nodeID] }