Skip to content

Commit e57be18

Browse files
authored
Merge pull request #58 from AryaHassanli/disco-add-xref-only-in-root-doc
Refine Xrefstyle Rules in Disco
2 parents 2127752 + 049efd4 commit e57be18

6 files changed

Lines changed: 245 additions & 24 deletions

File tree

disco/baller.go

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log/slog"
7+
"strings"
78

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

135136
func (b *Baller) discoBallTopLevelSection(dc *discoContext, top *asciidoc.Section, docType matter.DocType) error {
136-
if b.options.AddXrefstyle {
137-
var xrefstyleEntry *asciidoc.AttributeEntry
138-
var topIndex int = -1
139-
140-
for i, el := range dc.doc.Elements {
141-
if el == top {
142-
topIndex = i
143-
break
144-
}
145-
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
146-
xrefstyleEntry = ae
137+
if b.options.XrefStyleOnlyInRoot {
138+
// Logic:
139+
// 1. For non-root, no xrefstyle at all anywhere in doc.
140+
// 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.
141+
142+
if dc.doc == dc.library.Root {
143+
found := false
144+
for _, el := range dc.doc.Elements {
145+
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
146+
found = true
147+
break
148+
}
147149
}
148-
}
150+
if !found {
151+
ae := asciidoc.NewAttributeEntry("xrefstyle")
152+
ae.Elements = asciidoc.Elements{asciidoc.NewString("basic")}
153+
154+
copyrightIndex := -1
155+
topIndex := -1
156+
for i, el := range dc.doc.Elements {
157+
if s, ok := el.(*asciidoc.Section); ok {
158+
if topIndex == -1 {
159+
topIndex = i
160+
}
161+
name := dc.library.SectionName(s)
162+
if strings.Contains(strings.ToLower(name), "copyright notice") {
163+
copyrightIndex = i
164+
break
165+
}
166+
}
167+
}
149168

150-
if xrefstyleEntry == nil {
151-
ae := asciidoc.NewAttributeEntry("xrefstyle")
152-
ae.Elements = asciidoc.Elements{asciidoc.NewString("basic")}
153-
if topIndex != -1 {
154-
dc.doc.Elements = append(dc.doc.Elements, nil, nil)
155-
copy(dc.doc.Elements[topIndex+2:], dc.doc.Elements[topIndex:])
156-
dc.doc.Elements[topIndex] = ae
157-
dc.doc.Elements[topIndex+1] = &asciidoc.NewLine{}
158-
} else {
159-
dc.doc.Elements = append(asciidoc.Elements{ae, &asciidoc.NewLine{}}, dc.doc.Elements...)
169+
if copyrightIndex != -1 {
170+
index := copyrightIndex + 1
171+
dc.doc.Elements = append(dc.doc.Elements, nil, nil) // grow by 2
172+
copy(dc.doc.Elements[index+2:], dc.doc.Elements[index:])
173+
dc.doc.Elements[index] = &asciidoc.NewLine{}
174+
dc.doc.Elements[index+1] = ae
175+
} else if topIndex != -1 {
176+
index := topIndex
177+
dc.doc.Elements = append(dc.doc.Elements, nil, nil) // grow by 2
178+
copy(dc.doc.Elements[index+2:], dc.doc.Elements[index:])
179+
dc.doc.Elements[index] = ae
180+
dc.doc.Elements[index+1] = &asciidoc.NewLine{}
181+
} else {
182+
dc.doc.Elements = append(asciidoc.Elements{ae, &asciidoc.NewLine{}}, dc.doc.Elements...)
183+
}
160184
}
185+
} else {
186+
newElements := make(asciidoc.Elements, 0, len(dc.doc.Elements))
187+
for i := 0; i < len(dc.doc.Elements); i++ {
188+
el := dc.doc.Elements[i]
189+
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
190+
// Skip this element
191+
// Also skip following NewLine if present
192+
if i+1 < len(dc.doc.Elements) {
193+
if _, ok := dc.doc.Elements[i+1].(*asciidoc.NewLine); ok {
194+
i++
195+
}
196+
}
197+
continue
198+
}
199+
newElements = append(newElements, el)
200+
}
201+
dc.doc.Elements = newElements
161202
}
162203
}
163204

@@ -191,3 +232,5 @@ func (b *Baller) discoBallTopLevelSection(dc *discoContext, top *asciidoc.Sectio
191232
b.postCleanUpStrings(dc.doc, top)
192233
return nil
193234
}
235+
236+

disco/baller_test.go

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package disco
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"os"
7+
"strings"
8+
"testing"
9+
10+
"github.com/project-chip/alchemy/asciidoc"
11+
"github.com/project-chip/alchemy/asciidoc/parse"
12+
"github.com/project-chip/alchemy/config"
13+
"github.com/project-chip/alchemy/matter"
14+
"github.com/project-chip/alchemy/matter/spec"
15+
)
16+
17+
func TestXrefStyleOnlyInRootWithFile(t *testing.T) {
18+
b := NewBaller(nil, DiscoOptions{XrefStyleOnlyInRoot: true})
19+
20+
// Test Case 1: Root doc without xrefstyle (should add it)
21+
{
22+
path := asciidoc.Path{Relative: "testdata/root_without_xref.adoc"}
23+
in, err := os.ReadFile("testdata/root_without_xref.adoc")
24+
if err != nil {
25+
t.Fatalf("error reading file: %v", err)
26+
}
27+
28+
doc, err := parse.Reader(path, bytes.NewReader(in))
29+
if err != nil {
30+
t.Fatalf("error parsing file: %v", err)
31+
}
32+
33+
lib := spec.NewLibrary(doc, config.Library{}, nil, nil)
34+
lib.Reader = asciidoc.RawReader
35+
dc := &discoContext{
36+
Context: context.Background(),
37+
doc: doc,
38+
library: lib,
39+
potentialDataTypes: make(map[string][]*DataTypeEntry),
40+
}
41+
42+
top := parse.FindFirst[*asciidoc.Section](doc, asciidoc.RawReader, doc)
43+
err = b.discoBallTopLevelSection(dc, top, matter.DocTypeCluster)
44+
if err != nil {
45+
t.Fatalf("unexpected error: %v", err)
46+
}
47+
48+
// Verify added after Copyright Notice
49+
found := false
50+
for i, el := range doc.Elements {
51+
if s, ok := el.(*asciidoc.Section); ok {
52+
name := lib.SectionName(s)
53+
if strings.Contains(strings.ToLower(name), "copyright notice") {
54+
// Check next element
55+
if i+1 < len(doc.Elements) {
56+
if _, ok := doc.Elements[i+1].(*asciidoc.NewLine); ok {
57+
if i+2 < len(doc.Elements) {
58+
if ae, ok := doc.Elements[i+2].(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
59+
found = true
60+
break
61+
}
62+
}
63+
}
64+
}
65+
}
66+
}
67+
}
68+
if !found {
69+
t.Error("expected xrefstyle to be added after Copyright Notice section loaded from file")
70+
}
71+
}
72+
73+
// Test Case 2: Non-Root doc with xrefstyle (should remove it)
74+
{
75+
path := asciidoc.Path{Relative: "testdata/non_root_with_xref.adoc"}
76+
in, err := os.ReadFile("testdata/non_root_with_xref.adoc")
77+
if err != nil {
78+
t.Fatalf("error reading file: %v", err)
79+
}
80+
81+
doc, err := parse.Reader(path, bytes.NewReader(in))
82+
if err != nil {
83+
t.Fatalf("error parsing file: %v", err)
84+
}
85+
86+
lib := spec.NewLibrary(nil, config.Library{}, nil, nil) // Root is nil, so doc is not root
87+
lib.Reader = asciidoc.RawReader
88+
dc := &discoContext{
89+
Context: context.Background(),
90+
doc: doc,
91+
library: lib,
92+
potentialDataTypes: make(map[string][]*DataTypeEntry),
93+
}
94+
95+
top := parse.FindFirst[*asciidoc.Section](doc, asciidoc.RawReader, doc)
96+
err = b.discoBallTopLevelSection(dc, top, matter.DocTypeCluster)
97+
if err != nil {
98+
t.Fatalf("unexpected error: %v", err)
99+
}
100+
101+
// Verify no xrefstyle anywhere
102+
found := false
103+
for _, el := range doc.Elements {
104+
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
105+
found = true
106+
break
107+
}
108+
}
109+
if found {
110+
t.Error("expected all xrefstyle to be removed from non-root document loaded from file")
111+
}
112+
}
113+
114+
// Test Case 3: Root doc with xrefstyle (should do nothing)
115+
{
116+
path := asciidoc.Path{Relative: "testdata/root_with_xref.adoc"}
117+
in, err := os.ReadFile("testdata/root_with_xref.adoc")
118+
if err != nil {
119+
t.Fatalf("error reading file: %v", err)
120+
}
121+
122+
doc, err := parse.Reader(path, bytes.NewReader(in))
123+
if err != nil {
124+
t.Fatalf("error parsing file: %v", err)
125+
}
126+
127+
lib := spec.NewLibrary(doc, config.Library{}, nil, nil)
128+
lib.Reader = asciidoc.RawReader
129+
dc := &discoContext{
130+
Context: context.Background(),
131+
doc: doc,
132+
library: lib,
133+
potentialDataTypes: make(map[string][]*DataTypeEntry),
134+
}
135+
136+
// Count existing xrefstyle
137+
countBefore := 0
138+
for _, el := range doc.Elements {
139+
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
140+
countBefore++
141+
}
142+
}
143+
144+
top := parse.FindFirst[*asciidoc.Section](doc, asciidoc.RawReader, doc)
145+
err = b.discoBallTopLevelSection(dc, top, matter.DocTypeCluster)
146+
if err != nil {
147+
t.Fatalf("unexpected error: %v", err)
148+
}
149+
150+
// Count after
151+
countAfter := 0
152+
for _, el := range doc.Elements {
153+
if ae, ok := el.(*asciidoc.AttributeEntry); ok && ae.Name == "xrefstyle" {
154+
countAfter++
155+
}
156+
}
157+
158+
if countAfter != countBefore {
159+
t.Errorf("expected number of xrefstyle to remain %d, got %d", countBefore, countAfter)
160+
}
161+
}
162+
}

disco/option.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type DiscoOptions struct {
2222
NormalizeAnchors bool `default:"false" aliases:"normalizeAnchors" help:"rewrite anchors and references without labels" group:"Discoballing:"`
2323
RemoveMandatoryFallbacks bool `default:"true" aliases:"removeMandatoryFallbacks" help:"remove fallback values for mandatory fields" group:"Discoballing:"`
2424
RenameSections bool `default:"false" help:"rename sections to disco-ball standard names" group:"Discoballing:"`
25-
AddXrefstyle bool `default:"true" aliases:"addXrefstyle" help:"add xrefstyle attribute before the first section" group:"Discoballing:"`
25+
XrefStyleOnlyInRoot bool `default:"true" aliases:"xrefStyleOnlyInRoot" help:"enforce xrefstyle: basic only in root" group:"Discoballing:"`
2626
}
2727

2828
var DefaultOptions = DiscoOptions{
@@ -45,5 +45,5 @@ var DefaultOptions = DiscoOptions{
4545
NormalizeAnchors: false,
4646
RemoveMandatoryFallbacks: true,
4747
RenameSections: false,
48-
AddXrefstyle: true,
48+
XrefStyleOnlyInRoot: true,
4949
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:xrefstyle: full
2+
3+
= First Section
4+
This is the first section.

disco/testdata/root_with_xref.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:xrefstyle: full
2+
3+
= Copyright Notice
4+
This is the copyright notice.
5+
6+
= First Section
7+
This is the first section.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
= Copyright Notice
2+
This is the copyright notice.
3+
4+
= First Section
5+
This is the first section.

0 commit comments

Comments
 (0)