237 lines
6.1 KiB
Go
237 lines
6.1 KiB
Go
package gsp
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
func (p *Project) Validate(stage string) Report {
|
|
report := Report{OK: true}
|
|
for _, issue := range p.LoadIssues {
|
|
switch issue.Level {
|
|
case "error":
|
|
report.Errors = append(report.Errors, issue)
|
|
report.OK = false
|
|
case "warning":
|
|
report.Warnings = append(report.Warnings, issue)
|
|
default:
|
|
report.Notices = append(report.Notices, issue)
|
|
}
|
|
}
|
|
if p.Manifest != nil {
|
|
if p.Manifest.GSPVersion == "" {
|
|
report.addWarning("missing_gsp_version", "", p.Manifest.File, "manifest has no gspVersion")
|
|
} else if !SupportsGSPVersion(p.Manifest.GSPVersion) {
|
|
report.addError("unsupported_gsp_version", "", p.Manifest.File, fmt.Sprintf("GSP version %q is not supported by this toolkit", p.Manifest.GSPVersion))
|
|
}
|
|
}
|
|
for _, unit := range p.Units {
|
|
if unit.ID == "" {
|
|
report.addError("missing_id", "", unit.File, "GSP requires id")
|
|
}
|
|
if unit.Resolution != "" {
|
|
if _, ok := resolutionValue(unit.Resolution); !ok {
|
|
report.addError("invalid_resolution", unit.ID, unit.File, fmt.Sprintf("resolution %q is not allowed", unit.Resolution))
|
|
}
|
|
}
|
|
for _, rel := range unit.With {
|
|
if _, ok := p.ByID[rel.ID]; !ok {
|
|
report.addError("missing_with", unit.ID, unit.File, fmt.Sprintf("with references missing GSP %q", rel.ID))
|
|
}
|
|
}
|
|
if unit.Refines != "" {
|
|
if _, ok := p.ByID[unit.Refines]; !ok {
|
|
report.addError("missing_refines", unit.ID, unit.File, fmt.Sprintf("refines references missing GSP %q", unit.Refines))
|
|
}
|
|
}
|
|
if unit.Context == "" {
|
|
report.addNotice("placeholder", unit.ID, unit.File, "GSP has no context and is treated as placeholder")
|
|
}
|
|
}
|
|
for id, units := range p.Duplicates {
|
|
files := make([]string, 0, len(units))
|
|
seen := map[string]bool{}
|
|
for _, unit := range units {
|
|
if !seen[unit.File] {
|
|
files = append(files, unit.File)
|
|
seen[unit.File] = true
|
|
}
|
|
}
|
|
sort.Strings(files)
|
|
report.addError("duplicate_id", id, "", fmt.Sprintf("id %q is defined more than once: %v", id, files))
|
|
}
|
|
if stage != "" {
|
|
stageReport := p.StageCheck(stage)
|
|
report.Errors = append(report.Errors, stageReport.Errors...)
|
|
report.Warnings = append(report.Warnings, stageReport.Warnings...)
|
|
if len(stageReport.Errors) > 0 {
|
|
report.OK = false
|
|
}
|
|
}
|
|
return report
|
|
}
|
|
|
|
func (p *Project) Index() []IndexEntry {
|
|
entries := make([]IndexEntry, 0, len(p.Units))
|
|
for _, unit := range p.Units {
|
|
with := make([]string, 0, len(unit.With))
|
|
for _, rel := range unit.With {
|
|
with = append(with, rel.ID)
|
|
}
|
|
sort.Strings(with)
|
|
entries = append(entries, IndexEntry{
|
|
ID: unit.ID,
|
|
Title: unit.Title,
|
|
File: unit.File,
|
|
Type: unit.Type,
|
|
Resolution: unit.Resolution,
|
|
With: with,
|
|
Refines: unit.Refines,
|
|
})
|
|
}
|
|
sort.Slice(entries, func(i, j int) bool {
|
|
return entries[i].ID < entries[j].ID
|
|
})
|
|
return entries
|
|
}
|
|
|
|
var defaultStageRules = map[string]string{
|
|
"design": "L0",
|
|
"integrate": "L2",
|
|
"implement": "L3",
|
|
"bind": "L4",
|
|
"release": "L5",
|
|
}
|
|
|
|
func (p *Project) StageCheck(stage string) Report {
|
|
report := Report{OK: true}
|
|
required, ok := p.Manifest.minResolution(stage)
|
|
if !ok {
|
|
report.addError("unknown_stage", "", "", fmt.Sprintf("unknown stage %q", stage))
|
|
return report
|
|
}
|
|
minRank, _ := resolutionValue(required)
|
|
for _, unit := range p.Units {
|
|
rank, ok := resolutionValue(unit.Resolution)
|
|
if !ok {
|
|
report.addError("invalid_resolution", unit.ID, unit.File, fmt.Sprintf("resolution %q is not allowed", unit.Resolution))
|
|
continue
|
|
}
|
|
if rank < minRank {
|
|
report.addError("low_resolution", unit.ID, unit.File, fmt.Sprintf("resolution %s is below %s for stage %s", displayResolution(unit.Resolution), required, stage))
|
|
}
|
|
}
|
|
if len(report.Errors) == 0 {
|
|
report.OK = true
|
|
}
|
|
return report
|
|
}
|
|
|
|
func displayResolution(value string) string {
|
|
if value == "" {
|
|
return "L0"
|
|
}
|
|
return value
|
|
}
|
|
|
|
func (p *Project) Trace(id string, depth int, filter Filter) TraceResult {
|
|
flatten := p.Flatten(id, depth, filter)
|
|
graph := p.graphForUnits(flatten.Units)
|
|
return TraceResult{
|
|
Entry: id,
|
|
Depth: depth,
|
|
Nodes: flatten.Units,
|
|
Edges: graph.Edges,
|
|
Warnings: flatten.Warnings,
|
|
}
|
|
}
|
|
|
|
func (p *Project) Flatten(id string, depth int, filter Filter) FlattenResult {
|
|
walker := &walker{
|
|
project: p,
|
|
depth: depth,
|
|
filter: filter,
|
|
seen: map[string]bool{},
|
|
stack: map[string]bool{},
|
|
}
|
|
walker.visit(id, 0)
|
|
return FlattenResult{
|
|
Entry: id,
|
|
Depth: depth,
|
|
Units: walker.units,
|
|
Warnings: walker.warnings,
|
|
}
|
|
}
|
|
|
|
func (p *Project) Pack(id string, depth, budget int, filter Filter) PackResult {
|
|
flattened := p.Flatten(id, depth, filter)
|
|
units := make([]*Unit, 0, len(flattened.Units))
|
|
approx := 0
|
|
truncated := false
|
|
for _, unit := range flattened.Units {
|
|
candidate := append(units, unit)
|
|
data, _ := json.Marshal(candidate)
|
|
if budget > 0 && len(data) > budget && len(units) > 0 {
|
|
truncated = true
|
|
break
|
|
}
|
|
units = candidate
|
|
approx = len(data)
|
|
}
|
|
return PackResult{
|
|
Entry: id,
|
|
Depth: depth,
|
|
Budget: budget,
|
|
Truncated: truncated,
|
|
Units: units,
|
|
ApproxChars: approx,
|
|
Warnings: flattened.Warnings,
|
|
}
|
|
}
|
|
|
|
type walker struct {
|
|
project *Project
|
|
depth int
|
|
filter Filter
|
|
seen map[string]bool
|
|
stack map[string]bool
|
|
units []*Unit
|
|
warnings []Issue
|
|
}
|
|
|
|
func (w *walker) visit(id string, currentDepth int) {
|
|
if w.depth >= 0 && currentDepth > w.depth {
|
|
return
|
|
}
|
|
if w.stack[id] {
|
|
w.warnings = append(w.warnings, Issue{Level: "warning", Code: "cycle", ID: id, Message: fmt.Sprintf("cycle detected at %q", id)})
|
|
return
|
|
}
|
|
unit, ok := w.project.ByID[id]
|
|
if !ok {
|
|
w.warnings = append(w.warnings, Issue{Level: "warning", Code: "missing", ID: id, Message: fmt.Sprintf("missing GSP %q", id)})
|
|
return
|
|
}
|
|
if w.seen[id] {
|
|
return
|
|
}
|
|
w.seen[id] = true
|
|
if currentDepth == 0 || w.filter.Allows(unit) {
|
|
w.units = append(w.units, unit)
|
|
}
|
|
w.stack[id] = true
|
|
if unit.Refines != "" {
|
|
w.visit(unit.Refines, currentDepth+1)
|
|
}
|
|
withIDs := make([]string, 0, len(unit.With))
|
|
for _, rel := range unit.With {
|
|
withIDs = append(withIDs, rel.ID)
|
|
}
|
|
sort.Strings(withIDs)
|
|
for _, relID := range withIDs {
|
|
w.visit(relID, currentDepth+1)
|
|
}
|
|
delete(w.stack, id)
|
|
}
|