Skip to content
Open
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
8 changes: 8 additions & 0 deletions internal/adapters/secondary/repository/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"io"
"os"
"path/filepath"
"testing"
"time"

Expand All @@ -27,13 +28,15 @@ func NewMockFileSystem() *MockFileSystem {
}

func (m *MockFileSystem) ReadFile(name string) ([]byte, error) {
name = filepath.ToSlash(name)
if data, ok := m.files[name]; ok {
return data, nil
}
return nil, errors.New("file not found")
}

func (m *MockFileSystem) WriteFile(name string, data []byte, _ os.FileMode) error {
name = filepath.ToSlash(name)
m.files[name] = data
return nil
}
Expand All @@ -43,6 +46,7 @@ func (m *MockFileSystem) MkdirAll(_ string, _ os.FileMode) error {
}

func (m *MockFileSystem) Stat(name string) (os.FileInfo, error) {
name = filepath.ToSlash(name)
if _, ok := m.files[name]; ok {
//nolint:nilnil // Mock for testing
return nil, nil
Expand All @@ -51,6 +55,7 @@ func (m *MockFileSystem) Stat(name string) (os.FileInfo, error) {
}

func (m *MockFileSystem) Remove(name string) error {
name = filepath.ToSlash(name)
delete(m.files, name)
return nil
}
Expand All @@ -60,6 +65,8 @@ func (m *MockFileSystem) RemoveAll(_ string) error {
}

func (m *MockFileSystem) Rename(oldpath, newpath string) error {
oldpath = filepath.ToSlash(oldpath)
newpath = filepath.ToSlash(newpath)
if data, ok := m.files[oldpath]; ok {
m.files[newpath] = data
delete(m.files, oldpath)
Expand All @@ -82,6 +89,7 @@ func (m *MockFileSystem) Create(_ string) (io.WriteCloser, error) {
}

func (m *MockFileSystem) Exists(name string) bool {
name = filepath.ToSlash(name)
_, ok := m.files[name]
return ok
}
Expand Down
65 changes: 54 additions & 11 deletions utils/dcp/dcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import (
"github.com/hashload/boss/internal/core/domain"
"github.com/hashload/boss/pkg/consts"
"github.com/hashload/boss/pkg/msg"
"github.com/hashload/boss/utils"
"github.com/hashload/boss/utils/librarypath"
"golang.org/x/text/encoding/charmap"
"golang.org/x/text/transform"
)

var (
reRequires = regexp.MustCompile(`(?m)^(requires)([\n\r \w,{}\\.]+)(;)`)
reRequires = regexp.MustCompile(`(?m)^(requires)([^;]+)(;)`)
reWhitespace = regexp.MustCompile(`[\r\n ]+`)
)

Expand Down Expand Up @@ -106,7 +105,7 @@ func getDcpString(dcps []string) string {
return dcpRequiresLine[:len(dcpRequiresLine)-2]
}

// injectDcps injects DCP dependencies into the file content.
// injectDcps injects DCP dependencies into the file content while preserving original formatting, comments, and conditionals.
func injectDcps(filecontent string, dcps []string) (string, bool) {
resultRegex := reRequires.FindAllStringSubmatch(filecontent, -1)
if len(resultRegex) == 0 {
Expand All @@ -115,20 +114,64 @@ func injectDcps(filecontent string, dcps []string) (string, bool) {

resultRegexIndexes := reRequires.FindAllStringSubmatchIndex(filecontent, -1)

currentRequiresString := reWhitespace.ReplaceAllString(resultRegex[0][2], "")
// Group 2 (indices 4 and 5) is the body of the requires section
body := filecontent[resultRegexIndexes[0][4]:resultRegexIndexes[0][5]]

currentRequires := strings.Split(currentRequiresString, ",")
// Find all existing boss-injected dependencies in the body.
// They are marked with {BOSS}. We match them as \b([\w\.\-]+)\{BOSS\}
reBossDep := regexp.MustCompile(`(?i)\b([\w\.\-]+)\{BOSS\}`)
bossDepsMatches := reBossDep.FindAllStringSubmatch(body, -1)

var result = filecontent[:resultRegexIndexes[0][3]]
existingBossDeps := make(map[string]bool)
for _, match := range bossDepsMatches {
existingBossDeps[strings.ToLower(match[1])] = true
}

// Prepare a map of the target dcps (lowercase for case-insensitive comparison)
targetDcpsMap := make(map[string]bool)
for _, dcp := range dcps {
targetDcpsMap[strings.ToLower(filepath.Base(dcp))] = true
}

// 1. Remove old boss deps that are no longer in the target dcps list
modifiedBody := body
for oldDcp := range existingBossDeps {
if !targetDcpsMap[oldDcp] {
// Remove this dependency, its {BOSS} comment, and any trailing comma/whitespace
escapedDcp := regexp.QuoteMeta(oldDcp)
reRemove := regexp.MustCompile(`(?i)\s*\b` + escapedDcp + `\{BOSS\}\s*,?`)
modifiedBody = reRemove.ReplaceAllString(modifiedBody, "")
}
}

// 2. Add new dcps that are not already present in the requires section
for _, dcp := range dcps {
dcpName := filepath.Base(dcp)
dcpNameLower := strings.ToLower(dcpName)

// Check if it's already in the requires section (case-insensitive)
// We match it as a whole word: \b<dcpName>\b
escapedDcp := regexp.QuoteMeta(dcpNameLower)
reCheck := regexp.MustCompile(`(?i)\b` + escapedDcp + `\b`)
if reCheck.MatchString(modifiedBody) {
continue // Already exists (either user-defined or already injected), skip
}

for _, value := range currentRequires {
if strings.Contains(value, CommentBoss) || utils.Contains(dcps, value) {
continue
// Append the new dependency
trimmed := strings.TrimSpace(modifiedBody)
if trimmed != "" && !strings.HasSuffix(trimmed, ",") {
modifiedBody = trimmed + ",\n " + dcpName + CommentBoss
} else {
if trimmed == "" {
modifiedBody = "\n " + dcpName + CommentBoss
} else {
modifiedBody = modifiedBody + "\n " + dcpName + CommentBoss
}
}
result += "\n " + value + ","
}

result = result + getDcpString(dcps) + ";" + filecontent[resultRegexIndexes[0][7]:]
// Reconstruct the file content by replacing the body
result := filecontent[:resultRegexIndexes[0][4]] + modifiedBody + filecontent[resultRegexIndexes[0][5]:]
return result, true
}

Expand Down
43 changes: 43 additions & 0 deletions utils/dcp/dcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,46 @@ func TestGetRequiresList_NoDependencies(t *testing.T) {
t.Errorf("getRequiresList() should return empty list for no deps, got %v", result)
}
}

// TestInjectDcps_WithConditionals tests that injection preserves conditional directives and comments.
func TestInjectDcps_WithConditionals(t *testing.T) {
content := `package MyPackage;
requires
rtl,
vcl,
{$IFDEF MSWINDOWS}
designide,
{$ENDIF}
AnotherPkg;
contains
Unit1 in 'Unit1.pas';
end.`

dcps := []string{"newpkg"}

result, changed := injectDcps(content, dcps)

if !changed {
t.Error("injectDcps() should return true when requires section is modified")
}

// The new pkg should be added
if !strings.Contains(result, "newpkg{BOSS}") {
t.Error("injectDcps() should add new dcp to result with Boss marker")
}

// All original lines, including conditionals and comments, must be fully preserved
if !strings.Contains(result, "{$IFDEF MSWINDOWS}") {
t.Error("injectDcps() should preserve the opening conditional directive")
}
if !strings.Contains(result, "designide,") {
t.Error("injectDcps() should preserve the designide dependency")
}
if !strings.Contains(result, "{$ENDIF}") {
t.Error("injectDcps() should preserve the closing conditional directive")
}
if !strings.Contains(result, "AnotherPkg") {
t.Error("injectDcps() should preserve AnotherPkg dependency")
}
}

Loading