package infra import ( "bytes" "fmt" "os" "strings" "gopkg.in/yaml.v3" ) // ParseIssueMd lee un archivo Markdown de issue, extrae y parsea el frontmatter YAML // en un struct Issue, y devuelve el body (todo lo que va despues del segundo "---"). // FilePath e MtimeNs se rellenan con los valores del archivo en disco. // Completed se deduce del path (contiene "/completed/"). func ParseIssueMd(path string) (Issue, []byte, error) { data, err := os.ReadFile(path) if err != nil { return Issue{}, nil, fmt.Errorf("parse_issue_md: read %s: %w", path, err) } info, err := os.Stat(path) if err != nil { return Issue{}, nil, fmt.Errorf("parse_issue_md: stat %s: %w", path, err) } fm, body, err := splitFrontmatter(data) if err != nil { return Issue{}, nil, fmt.Errorf("parse_issue_md: %s: %w", path, err) } var iss Issue if err := yaml.Unmarshal(fm, &iss); err != nil { return Issue{}, nil, fmt.Errorf("parse_issue_md: yaml %s: %w", path, err) } iss.FilePath = path iss.MtimeNs = info.ModTime().UnixNano() iss.Completed = strings.Contains(path, "/completed/") return iss, body, nil } // splitFrontmatter divide el contenido en bloque YAML y body. // Espera formato: "---\n\n---\n". // Devuelve el YAML (sin los delimitadores) y el body (incluye el \n posterior al segundo ---). func splitFrontmatter(data []byte) ([]byte, []byte, error) { sep := []byte("---") newline := []byte("\n") // El archivo debe empezar con "---\n" if !bytes.HasPrefix(data, append(sep, '\n')) { return nil, nil, fmt.Errorf("missing opening '---' delimiter") } // Buscar el segundo "---" (en su propia linea) rest := data[len(sep)+1:] // avanza pasado el primer "---\n" idx := -1 for i := 0; i <= len(rest)-len(sep); i++ { // Debe estar al inicio de linea: posicion 0 o precedido por '\n' atLineStart := i == 0 || rest[i-1] == '\n' if atLineStart && bytes.Equal(rest[i:i+len(sep)], sep) { // El separador debe ir seguido de '\n' o EOF end := i + len(sep) if end == len(rest) || rest[end] == '\n' { idx = i break } } } if idx == -1 { return nil, nil, fmt.Errorf("missing closing '---' delimiter") } fm := rest[:idx] // El body empieza despues del segundo "---\n" bodyStart := idx + len(sep) if bodyStart < len(rest) && rest[bodyStart] == '\n' { bodyStart++ } body := rest[bodyStart:] _ = newline return fm, body, nil }