303 lines
8.1 KiB
Go
303 lines
8.1 KiB
Go
package gsp
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
var resolutionRank = map[string]int{
|
|
"": 0,
|
|
"L0": 0,
|
|
"L1": 1,
|
|
"L2": 2,
|
|
"L3": 3,
|
|
"L4": 4,
|
|
"L5": 5,
|
|
}
|
|
|
|
type Unit struct {
|
|
ID string `json:"id" yaml:"id"`
|
|
Title string `json:"title,omitempty" yaml:"title"`
|
|
Context string `json:"context,omitempty" yaml:"context"`
|
|
Resolution string `json:"resolution,omitempty" yaml:"resolution"`
|
|
With Relations `json:"with,omitempty" yaml:"with"`
|
|
Refines string `json:"refines,omitempty" yaml:"refines"`
|
|
Type string `json:"type,omitempty" yaml:"type"`
|
|
Links Links `json:"links,omitempty" yaml:"links"`
|
|
File string `json:"file,omitempty" yaml:"-"`
|
|
}
|
|
|
|
type Relation struct {
|
|
ID string `json:"id" yaml:"id"`
|
|
Context string `json:"context,omitempty" yaml:"context"`
|
|
}
|
|
|
|
type Relations []Relation
|
|
|
|
func (r *Relations) UnmarshalYAML(value *yaml.Node) error {
|
|
if value.Kind == 0 || value.Tag == "!!null" {
|
|
*r = nil
|
|
return nil
|
|
}
|
|
if value.Kind != yaml.SequenceNode {
|
|
return fmt.Errorf("with must be a list")
|
|
}
|
|
out := make([]Relation, 0, len(value.Content))
|
|
for _, item := range value.Content {
|
|
switch item.Kind {
|
|
case yaml.ScalarNode:
|
|
if item.Value == "" {
|
|
return fmt.Errorf("with item cannot be empty")
|
|
}
|
|
out = append(out, Relation{ID: item.Value})
|
|
case yaml.MappingNode:
|
|
var rel Relation
|
|
if err := item.Decode(&rel); err != nil {
|
|
return err
|
|
}
|
|
if rel.ID == "" {
|
|
return fmt.Errorf("with object item requires id")
|
|
}
|
|
out = append(out, rel)
|
|
default:
|
|
return fmt.Errorf("with item must be a string or object")
|
|
}
|
|
}
|
|
*r = out
|
|
return nil
|
|
}
|
|
|
|
type Link struct {
|
|
Path string `json:"path" yaml:"path"`
|
|
Role string `json:"role,omitempty" yaml:"role"`
|
|
Context string `json:"context,omitempty" yaml:"context"`
|
|
}
|
|
|
|
type Links []Link
|
|
|
|
func (l *Links) UnmarshalYAML(value *yaml.Node) error {
|
|
if value.Kind == 0 || value.Tag == "!!null" {
|
|
*l = nil
|
|
return nil
|
|
}
|
|
switch value.Kind {
|
|
case yaml.ScalarNode:
|
|
if value.Value == "" {
|
|
return fmt.Errorf("links item cannot be empty")
|
|
}
|
|
*l = Links{{Path: value.Value}}
|
|
return nil
|
|
case yaml.SequenceNode:
|
|
out := make([]Link, 0, len(value.Content))
|
|
for _, item := range value.Content {
|
|
switch item.Kind {
|
|
case yaml.ScalarNode:
|
|
if item.Value == "" {
|
|
return fmt.Errorf("links item cannot be empty")
|
|
}
|
|
out = append(out, Link{Path: item.Value})
|
|
case yaml.MappingNode:
|
|
var link Link
|
|
if err := item.Decode(&link); err != nil {
|
|
return err
|
|
}
|
|
if link.Path == "" {
|
|
return fmt.Errorf("links object item requires path")
|
|
}
|
|
out = append(out, link)
|
|
default:
|
|
return fmt.Errorf("links item must be a string or object")
|
|
}
|
|
}
|
|
*l = out
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("links must be a string or list")
|
|
}
|
|
}
|
|
|
|
type Issue struct {
|
|
Level string `json:"level"`
|
|
Code string `json:"code"`
|
|
ID string `json:"id,omitempty"`
|
|
File string `json:"file,omitempty"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type Report struct {
|
|
OK bool `json:"ok"`
|
|
Errors []Issue `json:"errors,omitempty"`
|
|
Warnings []Issue `json:"warnings,omitempty"`
|
|
Notices []Issue `json:"notices,omitempty"`
|
|
}
|
|
|
|
func (r *Report) addError(code, id, file, message string) {
|
|
r.Errors = append(r.Errors, Issue{Level: "error", Code: code, ID: id, File: file, Message: message})
|
|
r.OK = false
|
|
}
|
|
|
|
func (r *Report) addWarning(code, id, file, message string) {
|
|
r.Warnings = append(r.Warnings, Issue{Level: "warning", Code: code, ID: id, File: file, Message: message})
|
|
}
|
|
|
|
func (r *Report) addNotice(code, id, file, message string) {
|
|
r.Notices = append(r.Notices, Issue{Level: "notice", Code: code, ID: id, File: file, Message: message})
|
|
}
|
|
|
|
type Project struct {
|
|
Root string
|
|
Manifest *Manifest
|
|
Units []*Unit
|
|
ByID map[string]*Unit
|
|
Duplicates map[string][]*Unit
|
|
LoadIssues []Issue
|
|
}
|
|
|
|
type IndexEntry struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title,omitempty"`
|
|
File string `json:"file"`
|
|
Type string `json:"type,omitempty"`
|
|
Resolution string `json:"resolution,omitempty"`
|
|
With []string `json:"with,omitempty"`
|
|
Refines string `json:"refines,omitempty"`
|
|
Links []string `json:"links,omitempty"`
|
|
}
|
|
|
|
type FlattenResult struct {
|
|
Entry string `json:"entry"`
|
|
Depth int `json:"depth"`
|
|
Units []*Unit `json:"units"`
|
|
Warnings []Issue `json:"warnings,omitempty"`
|
|
}
|
|
|
|
type TraceResult struct {
|
|
Entry string `json:"entry"`
|
|
Depth int `json:"depth"`
|
|
Nodes []*Unit `json:"nodes"`
|
|
Edges []GraphEdge `json:"edges"`
|
|
Warnings []Issue `json:"warnings,omitempty"`
|
|
}
|
|
|
|
type PackResult struct {
|
|
Entry string `json:"entry"`
|
|
Intent string `json:"intent,omitempty"`
|
|
Stage string `json:"stage,omitempty"`
|
|
Depth int `json:"depth"`
|
|
Budget int `json:"budget,omitempty"`
|
|
Truncated bool `json:"truncated"`
|
|
Units []*Unit `json:"units"`
|
|
Links []ResolvedLink `json:"links,omitempty"`
|
|
Summary Summary `json:"summary"`
|
|
ApproxChars int `json:"approxChars"`
|
|
Warnings []Issue `json:"warnings,omitempty"`
|
|
}
|
|
|
|
type Summary struct {
|
|
UnitCount int `json:"unitCount"`
|
|
MinResolution string `json:"minResolution,omitempty"`
|
|
MaxResolution string `json:"maxResolution,omitempty"`
|
|
TypeCounts map[string]int `json:"typeCounts,omitempty"`
|
|
MissingCount int `json:"missingCount,omitempty"`
|
|
LinkCount int `json:"linkCount,omitempty"`
|
|
}
|
|
|
|
type LinkResult struct {
|
|
Entry string `json:"entry"`
|
|
Depth int `json:"depth"`
|
|
Links []ResolvedLink `json:"links,omitempty"`
|
|
Summary LinkSummary `json:"summary"`
|
|
Warnings []Issue `json:"warnings,omitempty"`
|
|
}
|
|
|
|
type LinkSummary struct {
|
|
Total int `json:"total"`
|
|
ByKind map[string]int `json:"byKind,omitempty"`
|
|
ByRole map[string]int `json:"byRole,omitempty"`
|
|
ByState map[string]int `json:"byState,omitempty"`
|
|
}
|
|
|
|
type ResolvedLink struct {
|
|
Owner string `json:"owner"`
|
|
Title string `json:"title,omitempty"`
|
|
Path string `json:"path"`
|
|
Role string `json:"role"`
|
|
Kind string `json:"kind"`
|
|
Status string `json:"status"`
|
|
Exists bool `json:"exists,omitempty"`
|
|
Context string `json:"context,omitempty"`
|
|
File string `json:"file,omitempty"`
|
|
}
|
|
|
|
type ImpactResult struct {
|
|
Entry string `json:"entry"`
|
|
Depth int `json:"depth"`
|
|
Summary ImpactSummary `json:"summary"`
|
|
Direct []ImpactEntry `json:"direct,omitempty"`
|
|
Indirect []ImpactEntry `json:"indirect,omitempty"`
|
|
Edges []GraphEdge `json:"edges,omitempty"`
|
|
Warnings []Issue `json:"warnings,omitempty"`
|
|
}
|
|
|
|
type ImpactSummary struct {
|
|
DirectCount int `json:"directCount"`
|
|
IndirectCount int `json:"indirectCount"`
|
|
TotalCount int `json:"totalCount"`
|
|
MaxDepth int `json:"maxDepth"`
|
|
}
|
|
|
|
type ImpactEntry struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
Resolution string `json:"resolution,omitempty"`
|
|
File string `json:"file,omitempty"`
|
|
Depth int `json:"depth"`
|
|
Via string `json:"via,omitempty"`
|
|
Kind string `json:"kind,omitempty"`
|
|
}
|
|
|
|
type Graph struct {
|
|
Nodes []GraphNode `json:"nodes"`
|
|
Edges []GraphEdge `json:"edges"`
|
|
}
|
|
|
|
type GraphNode struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title,omitempty"`
|
|
Type string `json:"type,omitempty"`
|
|
Resolution string `json:"resolution,omitempty"`
|
|
File string `json:"file,omitempty"`
|
|
Missing bool `json:"missing,omitempty"`
|
|
}
|
|
|
|
type GraphEdge struct {
|
|
From string `json:"from"`
|
|
To string `json:"to"`
|
|
Kind string `json:"kind"`
|
|
}
|
|
|
|
type Filter struct {
|
|
IncludeTypes map[string]bool
|
|
ExcludeTypes map[string]bool
|
|
}
|
|
|
|
func (f Filter) Allows(unit *Unit) bool {
|
|
if unit == nil {
|
|
return false
|
|
}
|
|
if len(f.IncludeTypes) > 0 && !f.IncludeTypes[unit.Type] {
|
|
return false
|
|
}
|
|
if len(f.ExcludeTypes) > 0 && f.ExcludeTypes[unit.Type] {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func resolutionValue(value string) (int, bool) {
|
|
rank, ok := resolutionRank[value]
|
|
return rank, ok
|
|
}
|