Add GSP links field and link inspection
This commit is contained in:
143
toolkit/internal/gsp/links.go
Normal file
143
toolkit/internal/gsp/links.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package gsp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var allowedLinkRoles = map[string]bool{
|
||||
"reference": true,
|
||||
"source": true,
|
||||
"binding": true,
|
||||
"output": true,
|
||||
"evidence": true,
|
||||
}
|
||||
|
||||
func (p *Project) Links(id string, depth int) LinkResult {
|
||||
flattened := p.Flatten(id, depth, Filter{})
|
||||
links := p.resolveLinks(flattened.Units)
|
||||
summary := summarizeLinks(links)
|
||||
return LinkResult{
|
||||
Entry: id,
|
||||
Depth: depth,
|
||||
Links: links,
|
||||
Summary: summary,
|
||||
Warnings: flattened.Warnings,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Project) resolveLinks(units []*Unit) []ResolvedLink {
|
||||
var result []ResolvedLink
|
||||
for _, unit := range units {
|
||||
for _, link := range unit.Links {
|
||||
result = append(result, p.resolveLink(unit, link))
|
||||
}
|
||||
}
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
if result[i].Owner == result[j].Owner {
|
||||
return result[i].Path < result[j].Path
|
||||
}
|
||||
return result[i].Owner < result[j].Owner
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *Project) resolveLink(unit *Unit, link Link) ResolvedLink {
|
||||
role := strings.TrimSpace(link.Role)
|
||||
if role == "" {
|
||||
role = "reference"
|
||||
}
|
||||
resolved := ResolvedLink{
|
||||
Owner: unit.ID,
|
||||
Title: unit.Title,
|
||||
Path: strings.TrimSpace(link.Path),
|
||||
Role: role,
|
||||
Context: strings.TrimSpace(link.Context),
|
||||
File: unit.File,
|
||||
}
|
||||
resolved.Kind, resolved.Status, resolved.Exists = p.classifyLink(resolved.Path)
|
||||
return resolved
|
||||
}
|
||||
|
||||
func (p *Project) classifyLink(path string) (kind, status string, exists bool) {
|
||||
if path == "" {
|
||||
return "unknown", "invalid", false
|
||||
}
|
||||
if isURL(path) {
|
||||
parsed, err := url.ParseRequestURI(path)
|
||||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||||
return "url", "invalid", false
|
||||
}
|
||||
return "url", "unchecked", false
|
||||
}
|
||||
checkPath := path
|
||||
if !filepath.IsAbs(checkPath) {
|
||||
checkPath = filepath.Join(p.Root, filepath.FromSlash(checkPath))
|
||||
}
|
||||
info, err := os.Stat(checkPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "missing", "missing", false
|
||||
}
|
||||
return "unknown", "invalid", false
|
||||
}
|
||||
if info.IsDir() {
|
||||
return "folder", "ok", true
|
||||
}
|
||||
return "file", "ok", true
|
||||
}
|
||||
|
||||
func isURL(value string) bool {
|
||||
lower := strings.ToLower(value)
|
||||
return strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://")
|
||||
}
|
||||
|
||||
func summarizeLinks(links []ResolvedLink) LinkSummary {
|
||||
summary := LinkSummary{
|
||||
Total: len(links),
|
||||
ByKind: map[string]int{},
|
||||
ByRole: map[string]int{},
|
||||
ByState: map[string]int{},
|
||||
}
|
||||
for _, link := range links {
|
||||
summary.ByKind[link.Kind]++
|
||||
summary.ByRole[link.Role]++
|
||||
summary.ByState[link.Status]++
|
||||
}
|
||||
if len(summary.ByKind) == 0 {
|
||||
summary.ByKind = nil
|
||||
}
|
||||
if len(summary.ByRole) == 0 {
|
||||
summary.ByRole = nil
|
||||
}
|
||||
if len(summary.ByState) == 0 {
|
||||
summary.ByState = nil
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
func (r LinkResult) Markdown() string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("# GSP Links\n\n")
|
||||
builder.WriteString(fmt.Sprintf("- Entry: `%s`\n", r.Entry))
|
||||
builder.WriteString(fmt.Sprintf("- Links: `%d`\n", r.Summary.Total))
|
||||
if len(r.Links) == 0 {
|
||||
builder.WriteString("\nNo links.\n")
|
||||
return builder.String()
|
||||
}
|
||||
builder.WriteString("\n## Links\n\n")
|
||||
for _, link := range r.Links {
|
||||
builder.WriteString(fmt.Sprintf("- `%s` %s\n", link.Owner, link.Path))
|
||||
builder.WriteString(fmt.Sprintf(" - role: `%s`\n", link.Role))
|
||||
builder.WriteString(fmt.Sprintf(" - kind: `%s`\n", link.Kind))
|
||||
builder.WriteString(fmt.Sprintf(" - status: `%s`\n", link.Status))
|
||||
if link.Context != "" {
|
||||
builder.WriteString(fmt.Sprintf(" - context: %s\n", link.Context))
|
||||
}
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
Reference in New Issue
Block a user