Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 65 additions & 22 deletions disco/baller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log/slog"
"strings"

"github.com/project-chip/alchemy/asciidoc"
"github.com/project-chip/alchemy/asciidoc/parse"
Expand Down Expand Up @@ -133,31 +134,71 @@ func (b *Baller) disco(cxt context.Context, doc *asciidoc.Document) error {
}

func (b *Baller) discoBallTopLevelSection(dc *discoContext, top *asciidoc.Section, docType matter.DocType) error {
if b.options.AddXrefstyle {
var xrefstyleEntry *asciidoc.AttributeEntry
var topIndex int = -1

for i, el := range dc.doc.Elements {
if el == top {
topIndex = i
break
}
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
xrefstyleEntry = ae
if b.options.XrefStyleOnlyInRoot {
// Logic:
// 1. For non-root, no xrefstyle at all anywhere in doc.
// 2. For root, if it is present somewhere in file, it's ok. If not present, add it after Copyright Notice or before first section.

if dc.doc == dc.library.Root {
found := false
for _, el := range dc.doc.Elements {
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
found = true
break
}
}
}
if !found {
ae := asciidoc.NewAttributeEntry("xrefstyle")
ae.Elements = asciidoc.Elements{asciidoc.NewString("basic")}

copyrightIndex := -1
topIndex := -1
for i, el := range dc.doc.Elements {
if s, ok := el.(*asciidoc.Section); ok {
if topIndex == -1 {
topIndex = i
}
name := dc.library.SectionName(s)
if strings.Contains(strings.ToLower(name), "copyright notice") {
copyrightIndex = i
break
}
}
}

if xrefstyleEntry == nil {
ae := asciidoc.NewAttributeEntry("xrefstyle")
ae.Elements = asciidoc.Elements{asciidoc.NewString("basic")}
if topIndex != -1 {
dc.doc.Elements = append(dc.doc.Elements, nil, nil)
copy(dc.doc.Elements[topIndex+2:], dc.doc.Elements[topIndex:])
dc.doc.Elements[topIndex] = ae
dc.doc.Elements[topIndex+1] = &asciidoc.NewLine{}
} else {
dc.doc.Elements = append(asciidoc.Elements{ae, &asciidoc.NewLine{}}, dc.doc.Elements...)
if copyrightIndex != -1 {
index := copyrightIndex + 1
dc.doc.Elements = append(dc.doc.Elements, nil, nil) // grow by 2
copy(dc.doc.Elements[index+2:], dc.doc.Elements[index:])
dc.doc.Elements[index] = &asciidoc.NewLine{}
dc.doc.Elements[index+1] = ae
} else if topIndex != -1 {
index := topIndex
dc.doc.Elements = append(dc.doc.Elements, nil, nil) // grow by 2
copy(dc.doc.Elements[index+2:], dc.doc.Elements[index:])
dc.doc.Elements[index] = ae
dc.doc.Elements[index+1] = &asciidoc.NewLine{}
} else {
dc.doc.Elements = append(asciidoc.Elements{ae, &asciidoc.NewLine{}}, dc.doc.Elements...)
}
Comment thread
AryaHassanli marked this conversation as resolved.
}
} else {
newElements := make(asciidoc.Elements, 0, len(dc.doc.Elements))
for i := 0; i < len(dc.doc.Elements); i++ {
el := dc.doc.Elements[i]
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
// Skip this element
// Also skip following NewLine if present
if i+1 < len(dc.doc.Elements) {
if _, ok := dc.doc.Elements[i+1].(*asciidoc.NewLine); ok {
i++
}
}
continue
}
newElements = append(newElements, el)
}
Comment thread
AryaHassanli marked this conversation as resolved.
dc.doc.Elements = newElements
}
}

Expand Down Expand Up @@ -191,3 +232,5 @@ func (b *Baller) discoBallTopLevelSection(dc *discoContext, top *asciidoc.Sectio
b.postCleanUpStrings(dc.doc, top)
return nil
}


162 changes: 162 additions & 0 deletions disco/baller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package disco

import (
"bytes"
"context"
"os"
"strings"
"testing"

"github.com/project-chip/alchemy/asciidoc"
"github.com/project-chip/alchemy/asciidoc/parse"
"github.com/project-chip/alchemy/config"
"github.com/project-chip/alchemy/matter"
"github.com/project-chip/alchemy/matter/spec"
)

func TestXrefStyleOnlyInRootWithFile(t *testing.T) {
b := NewBaller(nil, DiscoOptions{XrefStyleOnlyInRoot: true})

// Test Case 1: Root doc without xrefstyle (should add it)
{
path := asciidoc.Path{Relative: "testdata/root_without_xref.adoc"}
in, err := os.ReadFile("testdata/root_without_xref.adoc")
if err != nil {
t.Fatalf("error reading file: %v", err)
}

doc, err := parse.Reader(path, bytes.NewReader(in))
if err != nil {
t.Fatalf("error parsing file: %v", err)
}

lib := spec.NewLibrary(doc, config.Library{}, nil, nil)
lib.Reader = asciidoc.RawReader
dc := &discoContext{
Context: context.Background(),
doc: doc,
library: lib,
potentialDataTypes: make(map[string][]*DataTypeEntry),
}

top := parse.FindFirst[*asciidoc.Section](doc, asciidoc.RawReader, doc)
err = b.discoBallTopLevelSection(dc, top, matter.DocTypeCluster)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

// Verify added after Copyright Notice
found := false
for i, el := range doc.Elements {
if s, ok := el.(*asciidoc.Section); ok {
name := lib.SectionName(s)
if strings.Contains(strings.ToLower(name), "copyright notice") {
// Check next element
if i+1 < len(doc.Elements) {
if _, ok := doc.Elements[i+1].(*asciidoc.NewLine); ok {
if i+2 < len(doc.Elements) {
if ae, ok := doc.Elements[i+2].(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
found = true
break
}
}
}
}
}
}
}
if !found {
t.Error("expected xrefstyle to be added after Copyright Notice section loaded from file")
}
}

// Test Case 2: Non-Root doc with xrefstyle (should remove it)
{
path := asciidoc.Path{Relative: "testdata/non_root_with_xref.adoc"}
in, err := os.ReadFile("testdata/non_root_with_xref.adoc")
if err != nil {
t.Fatalf("error reading file: %v", err)
}

doc, err := parse.Reader(path, bytes.NewReader(in))
if err != nil {
t.Fatalf("error parsing file: %v", err)
}

lib := spec.NewLibrary(nil, config.Library{}, nil, nil) // Root is nil, so doc is not root
lib.Reader = asciidoc.RawReader
dc := &discoContext{
Context: context.Background(),
doc: doc,
library: lib,
potentialDataTypes: make(map[string][]*DataTypeEntry),
}

top := parse.FindFirst[*asciidoc.Section](doc, asciidoc.RawReader, doc)
err = b.discoBallTopLevelSection(dc, top, matter.DocTypeCluster)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

// Verify no xrefstyle anywhere
found := false
for _, el := range doc.Elements {
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
found = true
break
}
}
if found {
t.Error("expected all xrefstyle to be removed from non-root document loaded from file")
}
}

// Test Case 3: Root doc with xrefstyle (should do nothing)
{
path := asciidoc.Path{Relative: "testdata/root_with_xref.adoc"}
in, err := os.ReadFile("testdata/root_with_xref.adoc")
if err != nil {
t.Fatalf("error reading file: %v", err)
}

doc, err := parse.Reader(path, bytes.NewReader(in))
if err != nil {
t.Fatalf("error parsing file: %v", err)
}

lib := spec.NewLibrary(doc, config.Library{}, nil, nil)
lib.Reader = asciidoc.RawReader
dc := &discoContext{
Context: context.Background(),
doc: doc,
library: lib,
potentialDataTypes: make(map[string][]*DataTypeEntry),
}

// Count existing xrefstyle
countBefore := 0
for _, el := range doc.Elements {
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
countBefore++
}
}

top := parse.FindFirst[*asciidoc.Section](doc, asciidoc.RawReader, doc)
err = b.discoBallTopLevelSection(dc, top, matter.DocTypeCluster)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

// Count after
countAfter := 0
for _, el := range doc.Elements {
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
countAfter++
}
}

if countAfter != countBefore {
t.Errorf("expected number of xrefstyle to remain %d, got %d", countBefore, countAfter)
}
}
}
4 changes: 2 additions & 2 deletions disco/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type DiscoOptions struct {
NormalizeAnchors bool `default:"false" aliases:"normalizeAnchors" help:"rewrite anchors and references without labels" group:"Discoballing:"`
RemoveMandatoryFallbacks bool `default:"true" aliases:"removeMandatoryFallbacks" help:"remove fallback values for mandatory fields" group:"Discoballing:"`
RenameSections bool `default:"false" help:"rename sections to disco-ball standard names" group:"Discoballing:"`
AddXrefstyle bool `default:"true" aliases:"addXrefstyle" help:"add xrefstyle attribute before the first section" group:"Discoballing:"`
XrefStyleOnlyInRoot bool `default:"true" aliases:"xrefStyleOnlyInRoot" help:"enforce xrefstyle: basic only in root" group:"Discoballing:"`
}

var DefaultOptions = DiscoOptions{
Expand All @@ -45,5 +45,5 @@ var DefaultOptions = DiscoOptions{
NormalizeAnchors: false,
RemoveMandatoryFallbacks: true,
RenameSections: false,
AddXrefstyle: true,
XrefStyleOnlyInRoot: true,
}
4 changes: 4 additions & 0 deletions disco/testdata/non_root_with_xref.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:xrefstyle: full

= First Section
This is the first section.
7 changes: 7 additions & 0 deletions disco/testdata/root_with_xref.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
:xrefstyle: full

= Copyright Notice
This is the copyright notice.

= First Section
This is the first section.
5 changes: 5 additions & 0 deletions disco/testdata/root_without_xref.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
= Copyright Notice
This is the copyright notice.

= First Section
This is the first section.
Loading