Harden D&D scene description extraction
This commit is contained in:
@@ -78,6 +78,10 @@ func normalizeList(input dnd.SceneDescriptionList, doc *source.SourceDocument) (
|
||||
return dnd.SceneDescriptionList{}, fmt.Errorf("scenes must not be empty")
|
||||
}
|
||||
|
||||
unitPositions := make(map[int]int, len(doc.Units))
|
||||
for index, unit := range doc.Units {
|
||||
unitPositions[unit.ID] = index
|
||||
}
|
||||
output := dnd.SceneDescriptionList{Scenes: make([]dnd.SceneDescription, len(input.Scenes))}
|
||||
for index, scene := range input.Scenes {
|
||||
scene.Title = strings.TrimSpace(scene.Title)
|
||||
@@ -92,8 +96,8 @@ func normalizeList(input dnd.SceneDescriptionList, doc *source.SourceDocument) (
|
||||
}
|
||||
|
||||
sort.SliceStable(output.Scenes, func(left, right int) bool {
|
||||
leftStart, _ := source.UnitIndex(doc, output.Scenes[left].SourceRef.StartUnitID)
|
||||
rightStart, _ := source.UnitIndex(doc, output.Scenes[right].SourceRef.StartUnitID)
|
||||
leftStart := unitPositions[output.Scenes[left].SourceRef.StartUnitID]
|
||||
rightStart := unitPositions[output.Scenes[right].SourceRef.StartUnitID]
|
||||
if leftStart != rightStart {
|
||||
return leftStart < rightStart
|
||||
}
|
||||
@@ -103,6 +107,7 @@ func normalizeList(input dnd.SceneDescriptionList, doc *source.SourceDocument) (
|
||||
unique := make([]dnd.SceneDescription, 0, len(output.Scenes))
|
||||
byID := make(map[string]dnd.SceneDescription, len(output.Scenes))
|
||||
byRange := make(map[source.SourceRef]dnd.SceneDescription, len(output.Scenes))
|
||||
seen := make(map[dnd.SceneDescription]struct{}, len(output.Scenes))
|
||||
for _, scene := range output.Scenes {
|
||||
if previous, ok := byID[scene.ID]; ok && !identical(previous, scene) {
|
||||
return dnd.SceneDescriptionList{}, fmt.Errorf("scene ID %s has conflicting records", diagnostics.Quote(scene.ID))
|
||||
@@ -110,11 +115,12 @@ func normalizeList(input dnd.SceneDescriptionList, doc *source.SourceDocument) (
|
||||
if previous, ok := byRange[scene.SourceRef]; ok && !sameModelContent(previous, scene) {
|
||||
return dnd.SceneDescriptionList{}, fmt.Errorf("source range %s has conflicting records", sourceRefLabel(scene.SourceRef))
|
||||
}
|
||||
if containsIdentical(unique, scene) {
|
||||
if _, ok := seen[scene]; ok {
|
||||
continue
|
||||
}
|
||||
byID[scene.ID] = scene
|
||||
byRange[scene.SourceRef] = scene
|
||||
seen[scene] = struct{}{}
|
||||
unique = append(unique, scene)
|
||||
}
|
||||
output.Scenes = unique
|
||||
@@ -129,15 +135,6 @@ func sameModelContent(left, right dnd.SceneDescription) bool {
|
||||
return left.Kind == right.Kind && left.Title == right.Title && left.Summary == right.Summary
|
||||
}
|
||||
|
||||
func containsIdentical(scenes []dnd.SceneDescription, target dnd.SceneDescription) bool {
|
||||
for _, scene := range scenes {
|
||||
if identical(scene, target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func sourceRefLabel(ref source.SourceRef) string {
|
||||
return fmt.Sprintf("%s:%d-%d", diagnostics.Quote(ref.SourceID), ref.StartUnitID, ref.EndUnitID)
|
||||
}
|
||||
|
||||
@@ -65,6 +65,24 @@ func TestNormalizeRejectsInvalidCandidatesAndConflicts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeUsesDocumentOrderRatherThanNumericUnitID(t *testing.T) {
|
||||
doc := &source.SourceDocument{ID: "session", Units: []source.SourceUnit{
|
||||
{ID: 20, Text: "The party arrives."},
|
||||
{ID: 10, Text: "The party departs."},
|
||||
}}
|
||||
input := dnd.SceneDescriptionList{Scenes: []dnd.SceneDescription{
|
||||
scene("later", 10, 10, dnd.SceneKindNarrative, "Departure", "The party departs."),
|
||||
scene("first", 20, 20, dnd.SceneKindNarrative, "Arrival", "The party arrives."),
|
||||
}}
|
||||
result, err := New(Options{}).Normalize(context.Background(), normalizeRequest(input, doc))
|
||||
if err != nil {
|
||||
t.Fatalf("Normalize() error = %v", err)
|
||||
}
|
||||
if got, want := []string{result.Value.Scenes[0].ID, result.Value.Scenes[1].ID}, []string{"first", "later"}; !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("scene order = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizerContractAndCancellation(t *testing.T) {
|
||||
if _, err := DecodeOptions(nil); err != nil {
|
||||
t.Fatalf("DecodeOptions(nil) error = %v", err)
|
||||
|
||||
Reference in New Issue
Block a user