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
54 changes: 34 additions & 20 deletions docci.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ func RunDocciFileWithOptions(filePath string, opts types.DocciOpts) DocciResult
log := logger.GetLogger()

// Read the file into a string
log.Debugf("Reading file: %s", filePath)
log.Debug("Reading file", "path", filePath)
markdown, err := os.ReadFile(filePath)
if err != nil {
log.Errorf("Failed to read file: %s", err.Error())
log.Error("Failed to read file", "error", err.Error())
return DocciResult{
Success: false,
ExitCode: 1,
Expand All @@ -50,15 +50,15 @@ func RunDocciFileWithOptions(filePath string, opts types.DocciOpts) DocciResult
log.Debug("Parsing code blocks from markdown")
blocks, err := parser.ParseCodeBlocks(string(markdown))
if err != nil {
log.Errorf("Failed to parse code blocks: %s", err.Error())
log.Error("Failed to parse code blocks", "error", err.Error())
return DocciResult{
Success: false,
ExitCode: 1,
Stderr: fmt.Sprintf("Error parsing code blocks: %s", err.Error()),
}
}

log.Debugf("Found %d code blocks", len(blocks))
log.Debug("Found code blocks", "count", len(blocks))

// Build executable script with validation markers
log.Debug("Building executable script")
Expand All @@ -76,7 +76,14 @@ func RunDocciFileWithOptions(filePath string, opts types.DocciOpts) DocciResult

// Execute the script
log.Debug("Executing script")
resp := executor.Exec(script)
resp, err := executor.Exec(script)
if err != nil {
return DocciResult{
Success: false,
ExitCode: 1,
Stderr: fmt.Sprintf("execute script: %v", err),
}
}

// Check assert-failure blocks
if len(assertFailureMap) > 0 {
Expand All @@ -95,7 +102,7 @@ func RunDocciFileWithOptions(filePath string, opts types.DocciOpts) DocciResult
// Script failed as expected, continue processing
} else if resp.Error != nil {
// No assert-failure blocks, so error is unexpected
log.Errorf("✗ Unexpected script execution failure: %s", resp.Error.Error())
log.Error("Unexpected script execution failure", "error", resp.Error.Error())
return DocciResult{
Success: false,
ExitCode: 1,
Expand All @@ -111,10 +118,10 @@ func RunDocciFileWithOptions(filePath string, opts types.DocciOpts) DocciResult
// Validate outputs if there are any validation requirements
var validationErrors []error
if len(validationMap) > 0 {
log.Debugf("Validating %d output expectations", len(validationMap))
log.Debug("Validating output expectations", "count", len(validationMap))
validationErrors = executor.ValidateOutputs(blockOutputs, validationMap)
if len(validationErrors) > 0 {
log.Errorf("Found %d validation errors", len(validationErrors))
log.Error("Found validation errors", "count", len(validationErrors))
errorMsg := "\n=== Validation Errors ===\n"
for _, err := range validationErrors {
errorMsg += fmt.Sprintf("❌ %s\n", err.Error())
Expand Down Expand Up @@ -184,17 +191,17 @@ func RunDocciFiles(filePaths []string) DocciResult {
func RunDocciFilesWithOptions(filePaths []string, opts types.DocciOpts) DocciResult {
log := logger.GetLogger()

log.Debugf("Merging %d markdown files", len(filePaths))
log.Debug("Merging markdown files", "count", len(filePaths))

var allBlocks []parser.CodeBlock
globalIndex := 1

// Parse all files and collect blocks with filename metadata
for _, filePath := range filePaths {
log.Debugf("Reading file: %s", filePath)
log.Debug("Reading file", "path", filePath)
markdown, err := os.ReadFile(filePath)
if err != nil {
log.Errorf("Failed to read file %s: %s", filePath, err.Error())
log.Error("Failed to read file", "path", filePath, "error", err.Error())
return DocciResult{
Success: false,
ExitCode: 1,
Expand All @@ -203,11 +210,11 @@ func RunDocciFilesWithOptions(filePaths []string, opts types.DocciOpts) DocciRes
}

// Parse code blocks with filename metadata
log.Debugf("Parsing code blocks from %s", filePath)
log.Debug("Parsing code blocks", "path", filePath)
fileName := filepath.Base(filePath)
blocks, err := parser.ParseCodeBlocksWithFileName(string(markdown), fileName)
if err != nil {
log.Errorf("Failed to parse code blocks from %s: %s", filePath, err.Error())
log.Error("Failed to parse code blocks", "path", filePath, "error", err.Error())
return DocciResult{
Success: false,
ExitCode: 1,
Expand All @@ -222,10 +229,10 @@ func RunDocciFilesWithOptions(filePaths []string, opts types.DocciOpts) DocciRes
}

allBlocks = append(allBlocks, blocks...)
log.Debugf("Found %d code blocks in %s", len(blocks), filePath)
log.Debug("Found code blocks in file", "count", len(blocks), "path", filePath)
}

log.Debugf("Total merged blocks: %d", len(allBlocks))
log.Debug("Total merged blocks", "count", len(allBlocks))

// Build executable script with validation markers
log.Debug("Building executable script from merged blocks")
Expand All @@ -243,7 +250,14 @@ func RunDocciFilesWithOptions(filePaths []string, opts types.DocciOpts) DocciRes

// Execute the script
log.Debug("Executing merged script")
resp := executor.Exec(script)
resp, err := executor.Exec(script)
if err != nil {
return DocciResult{
Success: false,
ExitCode: 1,
Stderr: fmt.Sprintf("execute script: %v", err),
}
}

// Check assert-failure blocks
if len(assertFailureMap) > 0 {
Expand All @@ -262,7 +276,7 @@ func RunDocciFilesWithOptions(filePaths []string, opts types.DocciOpts) DocciRes
// Script failed as expected, continue processing
} else if resp.Error != nil {
// No assert-failure blocks, so error is unexpected
log.Errorf("✗ Unexpected script execution failure: %s", resp.Error.Error())
log.Error("Unexpected script execution failure", "error", resp.Error.Error())
return DocciResult{
Success: false,
ExitCode: 1,
Expand All @@ -278,10 +292,10 @@ func RunDocciFilesWithOptions(filePaths []string, opts types.DocciOpts) DocciRes
// Validate outputs if there are any validation requirements
var validationErrors []error
if len(validationMap) > 0 {
log.Debugf("Validating %d output expectations", len(validationMap))
log.Debug("Validating output expectations", "count", len(validationMap))
validationErrors = executor.ValidateOutputs(blockOutputs, validationMap)
if len(validationErrors) > 0 {
log.Errorf("Found %d validation errors", len(validationErrors))
log.Error("Found validation errors", "count", len(validationErrors))
errorMsg := "\n=== Validation Errors ===\n"
for _, err := range validationErrors {
errorMsg += fmt.Sprintf("❌ %s\n", err.Error())
Expand All @@ -299,7 +313,7 @@ func RunDocciFilesWithOptions(filePaths []string, opts types.DocciOpts) DocciRes

log.Debug("Merged script execution completed successfully")
fileList := strings.Join(filePaths, ", ")
log.Infof("Successfully executed merged files: %s", fileList)
log.Info("Successfully executed merged files", "files", fileList)

return DocciResult{
Success: true,
Expand Down
28 changes: 14 additions & 14 deletions executor/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func NewExecResponse(exitCode uint, stdout, stderr string, err error) ExecRespon
// Exec runs a specific codeblock in a bash shell.
// returns exit (status code, error message)

func Exec(commands string) ExecResponse {
func Exec(commands string) (ExecResponse, error) {
log := logger.GetLogger()
log.Debug("Executing commands in bash shell")

Expand All @@ -40,16 +40,16 @@ func Exec(commands string) ExecResponse {

stdout, err := cmd.StdoutPipe()
if err != nil {
panic(err)
return ExecResponse{}, fmt.Errorf("create stdout pipe: %w", err)
}

stderr, err := cmd.StderrPipe()
if err != nil {
panic(err)
return ExecResponse{}, fmt.Errorf("create stderr pipe: %w", err)
}

if err := cmd.Start(); err != nil {
panic(err)
return ExecResponse{}, fmt.Errorf("start command: %w", err)
}

var stdoutBuf, stderrBuf strings.Builder // captures output for further validation
Expand Down Expand Up @@ -119,15 +119,15 @@ func Exec(commands string) ExecResponse {
if exitError, ok := err.(*exec.ExitError); ok {
exitCode := exitError.ExitCode()
exitErr := exitError.Error()
log.Debugf("Command exited with code: %d, error: %s", exitCode, exitErr)
return NewExecResponse(uint(exitCode), stdoutBuf.String(), stderrBuf.String(), fmt.Errorf(exitError.Error()))
log.Debug("Command exited with code", "exitCode", exitCode, "error", exitErr)
return NewExecResponse(uint(exitCode), stdoutBuf.String(), stderrBuf.String(), fmt.Errorf(exitError.Error())), nil
} else {
panic(err)
return ExecResponse{}, fmt.Errorf("wait command: %w", err)
}
}

log.Debug("Command executed successfully")
return NewExecResponse(0, stdoutBuf.String(), stderrBuf.String(), nil)
return NewExecResponse(0, stdoutBuf.String(), stderrBuf.String(), nil), nil
}

// ParseBlockOutputs extracts output for each code block based on markers
Expand All @@ -148,7 +148,7 @@ func ParseBlockOutputs(output string) map[int]string {
marker := strings.TrimPrefix(line, "### DOCCI_BLOCK_START_")
marker = strings.TrimSuffix(marker, " ###")
fmt.Sscanf(marker, "%d", &currentBlock)
log.Debugf("Found start marker for block %d", currentBlock)
log.Debug("Found start marker for block", "block", currentBlock)
inBlock = true
currentOutput.Reset()
continue
Expand All @@ -158,7 +158,7 @@ func ParseBlockOutputs(output string) map[int]string {
if strings.HasPrefix(line, "### DOCCI_BLOCK_END_") && strings.HasSuffix(line, " ###") {
if inBlock {
blockOutputs[currentBlock] = strings.TrimSpace(currentOutput.String())
log.Debugf("Found end marker for block %d, captured output length: %d", currentBlock, len(blockOutputs[currentBlock]))
log.Debug("Found end marker for block", "block", currentBlock, "capturedOutputLength", len(blockOutputs[currentBlock]))
}
inBlock = false
continue
Expand All @@ -178,7 +178,7 @@ func ParseBlockOutputs(output string) map[int]string {
}
}

log.Debugf("Parsed %d block outputs", len(blockOutputs))
log.Debug("Parsed block outputs", "count", len(blockOutputs))
return blockOutputs
}

Expand All @@ -191,17 +191,17 @@ func ValidateOutputs(blockOutputs map[int]string, validationMap map[int]string)
for blockIndex, expectedContains := range validationMap {
output, exists := blockOutputs[blockIndex]
if !exists {
log.Errorf("No output found for block %d", blockIndex)
log.Error("No output found for block", "block", blockIndex)
errors = append(errors, fmt.Errorf("no output found for block %d", blockIndex))
continue
}

if !strings.Contains(output, expectedContains) {
log.Errorf("Block %d validation failed: output does not contain '%s'", blockIndex, expectedContains)
log.Error("Block validation failed: output does not contain expected", "block", blockIndex, "expected", expectedContains)
errors = append(errors, fmt.Errorf("block %d: output does not contain expected string '%s'\nActual output:\n%s",
blockIndex, expectedContains, output))
} else {
log.Debugf("Block %d validation passed: found expected string '%s'", blockIndex, expectedContains)
log.Debug("Block validation passed: found expected string", "block", blockIndex, "expected", expectedContains)
}
}

Expand Down
6 changes: 2 additions & 4 deletions gh.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"strings"
"time"

"github.com/sirupsen/logrus"
"golang.org/x/mod/semver"
)

Expand Down Expand Up @@ -115,9 +114,8 @@ func GetRealLatestReleases(r []Release) (string, string) {
return latestPre, latestOfficial
}

// OutOfDateCheckLog logs & returns true if it is out of date.
func OutOfDateCheckLog(logger *logrus.Logger, binName, current, latest string) bool {

// OutOfDateCheckLog returns true if current version is out of date.
func OutOfDateCheckLog(binName, current, latest string) bool {
currentVer := current
if currentVer == "dev" {
currentVer = "v0.0.0"
Expand Down
2 changes: 0 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/reecepbcups/docci
go 1.23.7

require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/stretchr/testify v1.10.0
golang.org/x/mod v0.26.0
Expand All @@ -14,6 +13,5 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
8 changes: 0 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading