mirror of
https://github.com/Dvorinka/Devour.git
synced 2026-06-03 20:13:03 +00:00
273 lines
6.1 KiB
Go
273 lines
6.1 KiB
Go
package reactdocs
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/PuerkitoBio/goquery"
|
|
"github.com/yourorg/devour/pkg/parserutil"
|
|
)
|
|
|
|
type Parser struct {
|
|
baseURL string
|
|
}
|
|
|
|
func NewParser() *Parser {
|
|
return &Parser{
|
|
baseURL: "https://react.dev",
|
|
}
|
|
}
|
|
|
|
func (p *Parser) ParseReferencePage(html string, docURL string) (*Reference, error) {
|
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ref := &Reference{
|
|
DocURL: docURL,
|
|
FetchedAt: time.Now(),
|
|
}
|
|
|
|
ref.Hooks = p.extractHooks(doc, docURL)
|
|
ref.Components = p.extractComponents(doc, docURL)
|
|
ref.APIs = p.extractAPIs(doc, docURL)
|
|
ref.Directives = p.extractDirectives(doc, docURL)
|
|
|
|
return ref, nil
|
|
}
|
|
|
|
func (p *Parser) ParseSearchResults(html string) ([]*SearchResult, error) {
|
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var results []*SearchResult
|
|
|
|
doc.Find(".search-result, a[href*='/reference/'], .nav-link").Each(func(i int, s *goquery.Selection) {
|
|
result := &SearchResult{}
|
|
|
|
result.Name = strings.TrimSpace(s.Text())
|
|
|
|
if href, exists := s.Attr("href"); exists {
|
|
result.DocURL = resolveURL(p.baseURL, href)
|
|
|
|
if strings.Contains(href, "/hooks/") {
|
|
result.Kind = "hook"
|
|
} else if strings.Contains(href, "/components/") {
|
|
result.Kind = "component"
|
|
} else if strings.Contains(href, "/apis/") {
|
|
result.Kind = "api"
|
|
} else {
|
|
result.Kind = "doc"
|
|
}
|
|
}
|
|
|
|
if result.Name != "" {
|
|
results = append(results, result)
|
|
}
|
|
})
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (p *Parser) extractHooks(doc *goquery.Document, docURL string) []*Hook {
|
|
var hooks []*Hook
|
|
|
|
doc.Find("h2, h3, .api-item, [id^='use']").Each(func(_ int, s *goquery.Selection) {
|
|
id, _ := s.Attr("id")
|
|
text := s.Text()
|
|
|
|
if !strings.HasPrefix(id, "use") && !strings.Contains(text, "use") {
|
|
return
|
|
}
|
|
|
|
hook := &Hook{}
|
|
|
|
nameEl := s.Find("code, .name").First()
|
|
if nameEl.Length() == 0 {
|
|
nameEl = s
|
|
}
|
|
hook.Name = strings.TrimSpace(nameEl.Text())
|
|
hook.Name = strings.TrimSuffix(hook.Name, "(")
|
|
|
|
if strings.HasPrefix(hook.Name, "use") {
|
|
hook.DocURL = docURL + "#" + hook.Name
|
|
|
|
next := s.Next()
|
|
for next.Length() > 0 && !next.Is("h2, h3") {
|
|
if next.Is("p") && hook.Doc == "" {
|
|
hook.Doc = strings.TrimSpace(next.Text())
|
|
} else if next.Is("pre, code") {
|
|
sig := strings.TrimSpace(next.Text())
|
|
if strings.HasPrefix(sig, hook.Name) {
|
|
hook.Signature = sig
|
|
}
|
|
}
|
|
next = next.Next()
|
|
}
|
|
|
|
hooks = append(hooks, hook)
|
|
}
|
|
})
|
|
|
|
return hooks
|
|
}
|
|
|
|
func (p *Parser) extractComponents(doc *goquery.Document, docURL string) []*Component {
|
|
var components []*Component
|
|
|
|
doc.Find("h2, h3, .api-item").Each(func(_ int, s *goquery.Selection) {
|
|
id, _ := s.Attr("id")
|
|
text := s.Text()
|
|
|
|
componentNames := []string{"Fragment", "Profiler", "StrictMode", "Suspense", "Transition", "Portal", "Component"}
|
|
isComponent := false
|
|
for _, name := range componentNames {
|
|
if id == name || strings.Contains(text, name) {
|
|
isComponent = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !isComponent {
|
|
return
|
|
}
|
|
|
|
comp := &Component{}
|
|
|
|
nameEl := s.Find("code, .name").First()
|
|
if nameEl.Length() == 0 {
|
|
nameEl = s
|
|
}
|
|
comp.Name = strings.TrimSpace(nameEl.Text())
|
|
|
|
comp.DocURL = docURL + "#" + comp.Name
|
|
|
|
next := s.Next()
|
|
for next.Length() > 0 && !next.Is("h2, h3") {
|
|
if next.Is("p") && comp.Doc == "" {
|
|
comp.Doc = strings.TrimSpace(next.Text())
|
|
}
|
|
next = next.Next()
|
|
}
|
|
|
|
if comp.Name != "" {
|
|
components = append(components, comp)
|
|
}
|
|
})
|
|
|
|
return components
|
|
}
|
|
|
|
func (p *Parser) extractAPIs(doc *goquery.Document, docURL string) []*API {
|
|
var apis []*API
|
|
|
|
doc.Find("h2, h3, .api-item").Each(func(_ int, s *goquery.Selection) {
|
|
id, _ := s.Attr("id")
|
|
text := s.Text()
|
|
|
|
apiNames := []string{"createContext", "createElement", "createFactory", "createRef", "forwardRef", "isValidElement", "lazy", "memo", "startTransition", "cloneElement", "Children"}
|
|
isAPI := false
|
|
for _, name := range apiNames {
|
|
if id == name || strings.Contains(text, name) {
|
|
isAPI = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !isAPI {
|
|
return
|
|
}
|
|
|
|
api := &API{}
|
|
|
|
nameEl := s.Find("code, .name").First()
|
|
if nameEl.Length() == 0 {
|
|
nameEl = s
|
|
}
|
|
api.Name = strings.TrimSpace(nameEl.Text())
|
|
api.Name = strings.TrimSuffix(api.Name, "(")
|
|
|
|
api.DocURL = docURL + "#" + api.Name
|
|
|
|
next := s.Next()
|
|
for next.Length() > 0 && !next.Is("h2, h3") {
|
|
if next.Is("p") && api.Doc == "" {
|
|
api.Doc = strings.TrimSpace(next.Text())
|
|
} else if next.Is("pre, code") {
|
|
sig := strings.TrimSpace(next.Text())
|
|
if strings.HasPrefix(sig, api.Name) || strings.Contains(sig, api.Name) {
|
|
api.Signature = sig
|
|
}
|
|
}
|
|
next = next.Next()
|
|
}
|
|
|
|
if api.Name != "" {
|
|
apis = append(apis, api)
|
|
}
|
|
})
|
|
|
|
return apis
|
|
}
|
|
|
|
func (p *Parser) extractDirectives(doc *goquery.Document, docURL string) []*Directive {
|
|
var directives []*Directive
|
|
|
|
directiveNames := []string{"use client", "use server"}
|
|
|
|
doc.Find("h2, h3, code, pre").Each(func(_ int, s *goquery.Selection) {
|
|
text := strings.TrimSpace(s.Text())
|
|
|
|
for _, name := range directiveNames {
|
|
if text == name || strings.Contains(text, "'"+name+"'") {
|
|
dir := &Directive{
|
|
Name: name,
|
|
Usage: text,
|
|
}
|
|
dir.DocURL = docURL + "#" + strings.ReplaceAll(name, " ", "-")
|
|
|
|
next := s.Next()
|
|
for next.Length() > 0 && !next.Is("h2, h3") {
|
|
if next.Is("p") && dir.Doc == "" {
|
|
dir.Doc = strings.TrimSpace(next.Text())
|
|
}
|
|
next = next.Next()
|
|
}
|
|
|
|
directives = append(directives, dir)
|
|
break
|
|
}
|
|
}
|
|
})
|
|
|
|
return directives
|
|
}
|
|
|
|
func (p *Parser) ParseHookPage(html string, docURL string) (*Hook, error) {
|
|
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hook := &Hook{
|
|
DocURL: docURL,
|
|
}
|
|
|
|
header := doc.Find("h1, .title").First()
|
|
hook.Name = strings.TrimSpace(header.Text())
|
|
|
|
hook.Doc = strings.TrimSpace(doc.Find(".content p, main p, article p").First().Text())
|
|
|
|
sigEl := doc.Find("pre code, .signature, code").First()
|
|
hook.Signature = strings.TrimSpace(sigEl.Text())
|
|
|
|
return hook, nil
|
|
}
|
|
|
|
func resolveURL(base string, href string) string {
|
|
return parserutil.ResolveURL(base, href)
|
|
}
|