From a7fb6c2eee435685c437cf575c39d84480e1f3bb Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Tue, 23 Jun 2026 17:07:31 +0200 Subject: [PATCH 1/6] Extract orchestration logic from `processor.go`. --- embedding/orchestration.go | 237 +++++++++++++++++++++++++++++++++++++ embedding/processor.go | 207 -------------------------------- 2 files changed, 237 insertions(+), 207 deletions(-) create mode 100644 embedding/orchestration.go diff --git a/embedding/orchestration.go b/embedding/orchestration.go new file mode 100644 index 00000000..19a7f179 --- /dev/null +++ b/embedding/orchestration.go @@ -0,0 +1,237 @@ +// Copyright 2026, TeamDev. All rights reserved. +// +// Redistribution and use in source and/or binary forms, with or without +// modification, must retain the above copyright notice and the following +// disclaimer. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package embedding + +import ( + "errors" + "fmt" + "log/slog" + "path/filepath" + "strings" + + "embed-code/embed-code-go/configuration" + "embed-code/embed-code-go/embedding/parsing" + "embed-code/embed-code-go/logging" + + "github.com/bmatcuk/doublestar/v4" +) + +// EmbedAllResult is result of the EmbedAll method. +// +// TargetFiles is the list of target documentation files. +// +// TotalEmbeddings is the total number of embeddings found in the target documentation files. +// +// UpdatedTargetFiles is the list of updated target documentation files. +type EmbedAllResult struct { + TargetFiles []string + TotalEmbeddings int + UpdatedTargetFiles []string +} + +// processorHandler applies one processing mode to a configured documentation processor. +type processorHandler func(processor Processor) error + +// EmbedAll processes embedding for multiple documentation files based on provided config. +// +// Iterates over patterns in the configuration, finds documentation files matching those patterns, +// creates an EmbeddingProcessor for each file, and embeds code fragments in them. +// +// config — a configuration for embedding. +func EmbedAll(config configuration.Configuration) (EmbedAllResult, error) { + totalEmbeddings := 0 + var updatedTargetFiles []string + requiredDocPaths, embeddingErrors := processRequiredDocs(config, func(processor Processor) error { + context, err := processor.Embed() + if err != nil { + return err + } + totalEmbeddings += context.EmbeddingsCount() + if context.IsContentChanged() { + updatedTargetFiles = append(updatedTargetFiles, processor.DocFilePath) + } + + return nil + }) + if len(embeddingErrors) > 0 { + return EmbedAllResult{}, errors.Join(embeddingErrors...) + } + if totalEmbeddings > 0 { + slog.Info( + fmt.Sprintf( + "Processed %d documentation file(s) with %d embedding(s) in `%s`%s.", + len(requiredDocPaths), totalEmbeddings, + logging.FileReference(config.DocumentationRoot), + configNameLabel(config), + ), + ) + } else { + slog.Warn( + fmt.Sprintf("No embedding instructions were found in documentation folder `%s`%s.", + logging.FileReference(config.DocumentationRoot), configNameLabel(config)), + ) + } + + return EmbedAllResult{ + TargetFiles: requiredDocPaths, + TotalEmbeddings: totalEmbeddings, + UpdatedTargetFiles: updatedTargetFiles, + }, nil +} + +// configNameLabel formats a configuration name for summary log messages. +func configNameLabel(config configuration.Configuration) string { + if config.Name == "" { + return "" + } + + return fmt.Sprintf(" for `%s` embedding setup", config.Name) +} + +// CheckUpToDate returns documentation files that are not up-to-date with code files. +// +// config — a configuration for embedding. +func CheckUpToDate(config configuration.Configuration) ([]string, error) { + changedFiles, checkErrors := findChangedFiles(config) + if len(checkErrors) > 0 { + return nil, errors.Join(checkErrors...) + } + + return changedFiles, nil +} + +// findChangedFiles returns documentation files that are not up-to-date with their code files. +// +// config — a configuration for embedding. +func findChangedFiles(config configuration.Configuration) ([]string, []error) { + var changedFiles []string + _, checkErrors := processRequiredDocs(config, func(processor Processor) error { + upToDate, err := processor.isUpToDate() + if err != nil { + return err + } + if !upToDate { + changedFiles = append(changedFiles, processor.DocFilePath) + } + + return nil + }) + + return changedFiles, checkErrors +} + +// processRequiredDocs applies a processing handler to every documentation file in config. +func processRequiredDocs( + config configuration.Configuration, + handle processorHandler, +) ([]string, []error) { + requiredDocPaths, err := requiredDocs(config) + if err != nil { + return nil, []error{err} + } + + var processingErrors []error + for _, doc := range requiredDocPaths { + processor := newProcessor(doc, config, parsing.Transitions, requiredDocPaths) + if err := handle(processor); err != nil { + processingErrors = append(processingErrors, err) + } + } + + return requiredDocPaths, processingErrors +} + +// requiredDocs returns documentation files matched by includes minus excludes. +func requiredDocs(config configuration.Configuration) ([]string, error) { + documentationRoot := config.DocumentationRoot + includedPatterns := config.DocIncludes + excludedPatterns := config.DocExcludes + + includedDocs, err := getFilesByPatterns(documentationRoot, includedPatterns) + if err != nil { + return nil, err + } + + excludedDocs, err := getFilesByPatterns(documentationRoot, excludedPatterns) + if err != nil { + return nil, err + } + if len(excludedDocs) == 0 { + slog.Info(fmt.Sprintf( + "Found %d documentation file(s) from `%s` matching include pattern(s) %s.", + len(includedDocs), logging.FileReference(documentationRoot), + patternsLabel(includedPatterns), + )) + + return includedDocs, nil + } + + result := removeElements(includedDocs, excludedDocs) + slog.Info(fmt.Sprintf( + "Found %d documentation file(s) from `%s` matching include pattern(s) %s "+ + "and exclude pattern(s) %s.", + len(result), logging.FileReference(documentationRoot), patternsLabel(includedPatterns), + patternsLabel(excludedPatterns), + )) + + return result, nil +} + +// patternsLabel formats glob patterns for human-readable log messages. +func patternsLabel(patterns []string) string { + if len(patterns) == 0 { + return "nothing" + } + + return "`" + strings.Join(patterns, "`, `") + "`" +} + +// getFilesByPatterns expands documentation glob patterns relative to the given root. +func getFilesByPatterns(root string, patterns []string) ([]string, error) { + var result []string + for _, pattern := range patterns { + globString := filepath.Join(root, filepath.FromSlash(pattern)) + matches, err := doublestar.FilepathGlob(globString) + if err != nil { + return nil, err + } + for _, match := range matches { + result = append(result, filepath.ToSlash(match)) + } + } + + return result, nil +} + +// removeElements returns values from first that are not present in second. +func removeElements(first, second []string) []string { + secondMap := make(map[string]struct{}) + for _, value := range second { + secondMap[value] = struct{}{} + } + + var result []string + for _, value := range first { + if _, exists := secondMap[value]; !exists { + result = append(result, value) + } + } + + return result +} diff --git a/embedding/processor.go b/embedding/processor.go index 6155558a..2728358f 100644 --- a/embedding/processor.go +++ b/embedding/processor.go @@ -23,7 +23,6 @@ import ( "fmt" "log/slog" "os" - "path/filepath" "slices" "strings" @@ -31,8 +30,6 @@ import ( "embed-code/embed-code-go/embedding/parsing" "embed-code/embed-code-go/files" "embed-code/embed-code-go/logging" - - "github.com/bmatcuk/doublestar/v4" ) // Processor entity processes a single documentation file and embeds code snippets @@ -48,22 +45,6 @@ type Processor struct { requiredDocPaths []string } -// EmbedAllResult is result of the EmbedAll method. -// -// TargetFiles is the list of target documentation files. -// -// TotalEmbeddings is the total number of embeddings found in the target documentation files. -// -// UpdatedTargetFiles is the list of updated target documentation files. -type EmbedAllResult struct { - TargetFiles []string - TotalEmbeddings int - UpdatedTargetFiles []string -} - -// processorHandler applies one processing mode to a configured documentation processor. -type processorHandler func(processor Processor) error - // NewProcessor creates and returns new Processor with given docFile and config. func NewProcessor(docFile string, config configuration.Configuration) (Processor, error) { requiredDocPaths, err := requiredDocs(config) @@ -188,74 +169,6 @@ func (p Processor) isUpToDate() (bool, error) { return upToDate, nil } -// EmbedAll processes embedding for multiple documentation files based on provided config. -// -// Iterates over patterns in the configuration, finds documentation files matching those patterns, -// creates an EmbeddingProcessor for each file, and embeds code fragments in them. -// -// config — a configuration for embedding. -func EmbedAll(config configuration.Configuration) (EmbedAllResult, error) { - totalEmbeddings := 0 - var updatedTargetFiles []string - requiredDocPaths, embeddingErrors := processRequiredDocs(config, func(processor Processor) error { - context, err := processor.Embed() - if err != nil { - return err - } - totalEmbeddings += context.EmbeddingsCount() - if context.IsContentChanged() { - updatedTargetFiles = append(updatedTargetFiles, processor.DocFilePath) - } - - return nil - }) - if len(embeddingErrors) > 0 { - return EmbedAllResult{}, errors.Join(embeddingErrors...) - } - if totalEmbeddings > 0 { - slog.Info( - fmt.Sprintf( - "Processed %d documentation file(s) with %d embedding(s) in `%s`%s.", - len(requiredDocPaths), totalEmbeddings, - logging.FileReference(config.DocumentationRoot), - configNameLabel(config), - ), - ) - } else { - slog.Warn( - fmt.Sprintf("No embedding instructions were found in documentation folder `%s`%s.", - logging.FileReference(config.DocumentationRoot), configNameLabel(config)), - ) - } - - return EmbedAllResult{ - TargetFiles: requiredDocPaths, - TotalEmbeddings: totalEmbeddings, - UpdatedTargetFiles: updatedTargetFiles, - }, nil -} - -// configNameLabel formats a configuration name for summary log messages. -func configNameLabel(config configuration.Configuration) string { - if config.Name == "" { - return "" - } - - return fmt.Sprintf(" for `%s` embedding setup", config.Name) -} - -// CheckUpToDate returns documentation files that are not up-to-date with code files. -// -// config — a configuration for embedding. -func CheckUpToDate(config configuration.Configuration) ([]string, error) { - changedFiles, checkErrors := findChangedFiles(config) - if len(checkErrors) > 0 { - return nil, errors.Join(checkErrors...) - } - - return changedFiles, nil -} - // Iterates through the doc file line by line considering them as a states of an embedding. // Such way, transits from the state to the next possible one until it reaches the end of a file. // By the transition process, fills the parsing.Context accordingly, so it is ready to retrieve @@ -359,123 +272,3 @@ func (p Processor) moveToNextState(state *parsing.State, context *parsing.Contex return false, state, nil } - -// findChangedFiles returns documentation files that are not up-to-date with their code files. -// -// config — a configuration for embedding. -func findChangedFiles(config configuration.Configuration) ([]string, []error) { - var changedFiles []string - _, checkErrors := processRequiredDocs(config, func(processor Processor) error { - upToDate, err := processor.isUpToDate() - if err != nil { - return err - } - if !upToDate { - changedFiles = append(changedFiles, processor.DocFilePath) - } - - return nil - }) - - return changedFiles, checkErrors -} - -// processRequiredDocs applies a processing handler to every documentation file in config. -func processRequiredDocs( - config configuration.Configuration, - handle processorHandler, -) ([]string, []error) { - requiredDocPaths, err := requiredDocs(config) - if err != nil { - return nil, []error{err} - } - - var processingErrors []error - for _, doc := range requiredDocPaths { - processor := newProcessor(doc, config, parsing.Transitions, requiredDocPaths) - if err := handle(processor); err != nil { - processingErrors = append(processingErrors, err) - } - } - - return requiredDocPaths, processingErrors -} - -// requiredDocs returns documentation files matched by includes minus excludes. -func requiredDocs(config configuration.Configuration) ([]string, error) { - documentationRoot := config.DocumentationRoot - includedPatterns := config.DocIncludes - excludedPatterns := config.DocExcludes - - includedDocs, err := getFilesByPatterns(documentationRoot, includedPatterns) - if err != nil { - return nil, err - } - - excludedDocs, err := getFilesByPatterns(documentationRoot, excludedPatterns) - if err != nil { - return nil, err - } - if len(excludedDocs) == 0 { - slog.Info(fmt.Sprintf( - "Found %d documentation file(s) from `%s` matching include pattern(s) %s.", - len(includedDocs), logging.FileReference(documentationRoot), - patternsLabel(includedPatterns), - )) - - return includedDocs, nil - } - - result := removeElements(includedDocs, excludedDocs) - slog.Info(fmt.Sprintf( - "Found %d documentation file(s) from `%s` matching include pattern(s) %s "+ - "and exclude pattern(s) %s.", - len(result), logging.FileReference(documentationRoot), patternsLabel(includedPatterns), - patternsLabel(excludedPatterns), - )) - - return result, nil -} - -// patternsLabel formats glob patterns for human-readable log messages. -func patternsLabel(patterns []string) string { - if len(patterns) == 0 { - return "nothing" - } - - return "`" + strings.Join(patterns, "`, `") + "`" -} - -// getFilesByPatterns expands documentation glob patterns relative to the given root. -func getFilesByPatterns(root string, patterns []string) ([]string, error) { - var result []string - for _, pattern := range patterns { - globString := filepath.Join(root, filepath.FromSlash(pattern)) - matches, err := doublestar.FilepathGlob(globString) - if err != nil { - return nil, err - } - for _, match := range matches { - result = append(result, filepath.ToSlash(match)) - } - } - - return result, nil -} - -// removeElements returns values from first that are not present in second. -func removeElements(first, second []string) []string { - secondMap := make(map[string]struct{}) - for _, value := range second { - secondMap[value] = struct{}{} - } - - var result []string - for _, value := range first { - if _, exists := secondMap[value]; !exists { - result = append(result, value) - } - } - - return result -} From 409c87b93ed25de14b5db0c2b1cc1a60a241f9f7 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Tue, 23 Jun 2026 17:36:24 +0200 Subject: [PATCH 2/6] Improve readability. --- embedding/orchestration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embedding/orchestration.go b/embedding/orchestration.go index 19a7f179..e38cbdcb 100644 --- a/embedding/orchestration.go +++ b/embedding/orchestration.go @@ -32,7 +32,7 @@ import ( "github.com/bmatcuk/doublestar/v4" ) -// EmbedAllResult is result of the EmbedAll method. +// EmbedAllResult contains the result of an EmbedAll operation. // // TargetFiles is the list of target documentation files. // @@ -51,7 +51,7 @@ type processorHandler func(processor Processor) error // EmbedAll processes embedding for multiple documentation files based on provided config. // // Iterates over patterns in the configuration, finds documentation files matching those patterns, -// creates an EmbeddingProcessor for each file, and embeds code fragments in them. +// creates a Processor for each file, and embeds code fragments in them. // // config — a configuration for embedding. func EmbedAll(config configuration.Configuration) (EmbedAllResult, error) { From bfa83d6b0f6f8b3e689a60dce3bda1b142bd7b17 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Tue, 23 Jun 2026 17:56:27 +0200 Subject: [PATCH 3/6] Remove unused processor API. --- embedding/embedding_test.go | 24 ----------- embedding/orchestration.go | 8 +--- embedding/parsing/context.go | 43 +------------------- embedding/processor.go | 77 +++++++++++------------------------- 4 files changed, 25 insertions(+), 127 deletions(-) diff --git a/embedding/embedding_test.go b/embedding/embedding_test.go index 1a45a91f..c8adbdb1 100644 --- a/embedding/embedding_test.go +++ b/embedding/embedding_test.go @@ -84,18 +84,6 @@ var _ = Describe("Embedding", func() { Expect(processor.IsUpToDate()).Should(BeTrue()) }) - It("should have error as it has invalid transition map", func() { - docPath := fmt.Sprintf("%s/split-lines.md", config.DocumentationRoot) - - falseTransitions := parsing.TransitionMap{ - parsing.Start: {parsing.Finish, parsing.EmbedInstruction, parsing.RegularLine}, - parsing.RegularLine: {parsing.CodeFenceEnd}, - } - - falseProcessor := newProcessorWithTransitions(docPath, config, falseTransitions) - Expect(falseProcessor.Embed()).Error().Should(HaveOccurred()) - }) - It("should successfully embed with multi lined tag", func() { docPath := fmt.Sprintf("%s/multi-lined-tag.md", config.DocumentationRoot) processor := newProcessor(docPath, config) @@ -376,18 +364,6 @@ func newProcessor( return processor } -func newProcessorWithTransitions( - docPath string, - config configuration.Configuration, - transitions parsing.TransitionMap, -) embedding.Processor { - processor, err := embedding.NewProcessorWithTransitions(docPath, config, transitions) - - Expect(err).ShouldNot(HaveOccurred()) - - return processor -} - func copyDirRecursive(sourceDirPath string, targetDirPath string) { info, err := os.Stat(sourceDirPath) if err != nil { diff --git a/embedding/orchestration.go b/embedding/orchestration.go index e38cbdcb..fd591358 100644 --- a/embedding/orchestration.go +++ b/embedding/orchestration.go @@ -34,13 +34,10 @@ import ( // EmbedAllResult contains the result of an EmbedAll operation. // -// TargetFiles is the list of target documentation files. -// // TotalEmbeddings is the total number of embeddings found in the target documentation files. // // UpdatedTargetFiles is the list of updated target documentation files. type EmbedAllResult struct { - TargetFiles []string TotalEmbeddings int UpdatedTargetFiles []string } @@ -64,7 +61,7 @@ func EmbedAll(config configuration.Configuration) (EmbedAllResult, error) { } totalEmbeddings += context.EmbeddingsCount() if context.IsContentChanged() { - updatedTargetFiles = append(updatedTargetFiles, processor.DocFilePath) + updatedTargetFiles = append(updatedTargetFiles, processor.docFilePath) } return nil @@ -89,7 +86,6 @@ func EmbedAll(config configuration.Configuration) (EmbedAllResult, error) { } return EmbedAllResult{ - TargetFiles: requiredDocPaths, TotalEmbeddings: totalEmbeddings, UpdatedTargetFiles: updatedTargetFiles, }, nil @@ -127,7 +123,7 @@ func findChangedFiles(config configuration.Configuration) ([]string, []error) { return err } if !upToDate { - changedFiles = append(changedFiles, processor.DocFilePath) + changedFiles = append(changedFiles, processor.docFilePath) } return nil diff --git a/embedding/parsing/context.go b/embedding/parsing/context.go index c09c5dee..95f6f6c6 100644 --- a/embedding/parsing/context.go +++ b/embedding/parsing/context.go @@ -66,24 +66,17 @@ func (c *Context) EmbeddingsCount() int { return len(c.embeddings) } -// EmbeddingContext contains the information about the position in the source and the -// resulting Markdown files. +// EmbeddingContext contains an instruction and its position in the source Markdown file. // // embeddingInstruction - an Instruction, containing all the needed embedding information. // // SourceStartIndex - an index of the StartState line in the original markdown file. // // SourceEndIndex - an index of the end line in the original markdown file. -// -// resultStartIndex - an index of the StartState line in the result markdown file. -// -// resultEndIndex - an index of the end line in the result markdown file. type EmbeddingContext struct { embeddingInstruction Instruction SourceStartIndex int SourceEndIndex int - resultStartIndex int - resultEndIndex int } // NewContext Creates and returns a new Context struct with initial values for markdownFile, source, @@ -143,20 +136,6 @@ func (c *Context) IsContentChanged() bool { return false } -// FindChangedEmbeddings returns a list of changed embeddings. -func (c *Context) FindChangedEmbeddings() []Instruction { - var changedEmbeddings []Instruction - for _, embedding := range c.embeddings { - sourceContent := c.readEmbeddingSource(embedding) - resultContent := c.readEmbeddingResult(embedding) - if !isStringSlicesEqual(sourceContent, resultContent) { - changedEmbeddings = append(changedEmbeddings, embedding.embeddingInstruction) - } - } - - return changedEmbeddings -} - // IsContainsEmbedding reports whether the doc file contains an embedding. func (c *Context) IsContainsEmbedding() bool { return c.fileContainsEmbedding @@ -196,7 +175,6 @@ func (c *Context) StartEmbedding(instruction Instruction) { func (c *Context) FinishEmbedding() { currentEmbedding := c.CurrentEmbedding() currentEmbedding.SourceEndIndex = c.lineIndex - currentEmbedding.resultEndIndex = len(c.Result) c.EmbeddingInstruction = nil } @@ -206,7 +184,6 @@ func (c *Context) SetCodeStart() { if c.fileContainsEmbedding { lastEmbedding := c.CurrentEmbedding() lastEmbedding.SourceStartIndex = c.lineIndex - lastEmbedding.resultStartIndex = len(c.Result) } } @@ -236,11 +213,6 @@ func (c *Context) readEmbeddingSource(context EmbeddingContext) []string { return c.source[context.SourceStartIndex:context.SourceEndIndex] } -// readEmbeddingResult returns generated Markdown lines for one embedding. -func (c *Context) readEmbeddingResult(context EmbeddingContext) []string { - return c.Result[context.resultStartIndex:context.resultEndIndex] -} - // readLines returns the content of a file placed at filepath as a list of strings. func readLines(filepath string) ([]string, error) { bytes, err := os.ReadFile(filepath) @@ -252,16 +224,3 @@ func readLines(filepath string) ([]string, error) { return lines, nil } - -func isStringSlicesEqual(first, second []string) bool { - if len(first) != len(second) { - return false - } - for i := range first { - if first[i] != second[i] { - return false - } - } - - return true -} diff --git a/embedding/processor.go b/embedding/processor.go index 2728358f..21271f83 100644 --- a/embedding/processor.go +++ b/embedding/processor.go @@ -32,16 +32,11 @@ import ( "embed-code/embed-code-go/logging" ) -// Processor entity processes a single documentation file and embeds code snippets -// into it based on the provided configuration. -// -// DocFilePath — the path to the documentation file. -// -// Config — a configuration for embedding. +// Processor processes a single documentation file using the provided embedding configuration. type Processor struct { - DocFilePath string - Config configuration.Configuration - TransitionsMap parsing.TransitionMap + docFilePath string + config configuration.Configuration + transitionsMap parsing.TransitionMap requiredDocPaths []string } @@ -55,18 +50,6 @@ func NewProcessor(docFile string, config configuration.Configuration) (Processor return newProcessor(docFile, config, parsing.Transitions, requiredDocPaths), nil } -// NewProcessorWithTransitions Creates and returns new Processor with given docFile, config -// and transitions. -func NewProcessorWithTransitions(docFile string, config configuration.Configuration, - transitions parsing.TransitionMap) (Processor, error) { - requiredDocPaths, err := requiredDocs(config) - if err != nil { - return Processor{}, err - } - - return newProcessor(docFile, config, transitions, requiredDocPaths), nil -} - // newProcessor creates a Processor with a precomputed documentation file list. func newProcessor( docFile string, @@ -75,9 +58,9 @@ func newProcessor( requiredDocPaths []string, ) Processor { return Processor{ - DocFilePath: docFile, - Config: config, - TransitionsMap: transitions, + docFilePath: docFile, + config: config, + transitionsMap: transitions, requiredDocPaths: requiredDocPaths, } } @@ -87,53 +70,37 @@ func newProcessor( // Returns an empty context without parsing the file when it is excluded by configuration. // If any problems faced, an error is returned. func (p Processor) Embed() (*parsing.Context, error) { - if !slices.Contains(p.requiredDocPaths, p.DocFilePath) { + if !slices.Contains(p.requiredDocPaths, p.docFilePath) { slog.Info(fmt.Sprintf("Skipping `%s`; it is excluded by the configuration.", - logging.FileReference(p.DocFilePath))) - context := parsing.NewEmptyContext(p.DocFilePath) + logging.FileReference(p.docFilePath))) + context := parsing.NewEmptyContext(p.docFilePath) return &context, nil } - slog.Info(fmt.Sprintf("Started processing doc file `%s`.", logging.FileReference(p.DocFilePath))) + slog.Info(fmt.Sprintf("Started processing doc file `%s`.", logging.FileReference(p.docFilePath))) context, err := p.fillEmbeddingContext() if err != nil { return nil, err } if context.IsContainsEmbedding() && context.IsContentChanged() { data := []byte(strings.Join(context.GetResult(), "\n")) - err = os.WriteFile(p.DocFilePath, data, os.FileMode(files.DocumentationFilePermission)) + err = os.WriteFile(p.docFilePath, data, os.FileMode(files.DocumentationFilePermission)) if err != nil { return &context, err } slog.Info(fmt.Sprintf("Updated `%s` after processing %d embedding(s).", - logging.FileReference(p.DocFilePath), context.EmbeddingsCount())) + logging.FileReference(p.docFilePath), context.EmbeddingsCount())) } else { slog.Info(fmt.Sprintf( "Documentation is up-to-date in `%s`.", - logging.FileReference(p.DocFilePath), + logging.FileReference(p.docFilePath), )) } return &context, nil } -// FindChangedEmbeddings Returns the list of EmbeddingInstruction that are changed in the -// markdown file. -// -// If any problems during the embedding construction faced, an error is returned. -func (p Processor) FindChangedEmbeddings() ([]parsing.Instruction, error) { - if !slices.Contains(p.requiredDocPaths, p.DocFilePath) { - return nil, nil - } - context, err := p.fillEmbeddingContext() - if err != nil { - return nil, err - } - - return context.FindChangedEmbeddings(), nil -} - // IsUpToDate reports whether the embedding of the target markdown is up-to-date with the code file. func (p Processor) IsUpToDate() bool { upToDate, err := p.isUpToDate() @@ -146,13 +113,13 @@ func (p Processor) IsUpToDate() bool { // isUpToDate reports whether the target markdown is up-to-date and returns processing errors. func (p Processor) isUpToDate() (bool, error) { - if !slices.Contains(p.requiredDocPaths, p.DocFilePath) { + if !slices.Contains(p.requiredDocPaths, p.docFilePath) { slog.Info(fmt.Sprintf("Skipping `%s`; it is excluded by the configuration.", - logging.FileReference(p.DocFilePath))) + logging.FileReference(p.docFilePath))) return true, nil } - slog.Info(fmt.Sprintf("Checking `%s`.", logging.FileReference(p.DocFilePath))) + slog.Info(fmt.Sprintf("Checking `%s`.", logging.FileReference(p.docFilePath))) context, err := p.fillEmbeddingContext() if err != nil { return false, err @@ -164,7 +131,7 @@ func (p Processor) isUpToDate() (bool, error) { status = "needs an update" } slog.Info(fmt.Sprintf("Checked `%s`: %d embedding(s), %s.", - logging.FileReference(p.DocFilePath), context.EmbeddingsCount(), status)) + logging.FileReference(p.docFilePath), context.EmbeddingsCount(), status)) return upToDate, nil } @@ -176,7 +143,7 @@ func (p Processor) isUpToDate() (bool, error) { // // Returns a parsing.Context and an error if any occurs. func (p Processor) fillEmbeddingContext() (parsing.Context, error) { - context, err := parsing.NewContext(p.DocFilePath) + context, err := parsing.NewContext(p.docFilePath) if err != nil { return context, err } @@ -208,7 +175,7 @@ func (p Processor) fillEmbeddingContext() (parsing.Context, error) { // processingError wraps a parsing error with the current documentation location. func (p Processor) processingError(context parsing.Context, err error) ProcessingError { return ProcessingError{ - DocFilePath: p.DocFilePath, + DocFilePath: p.docFilePath, Line: errorLine(context, err), Err: err, } @@ -259,9 +226,9 @@ func unacceptedTransitionError(context parsing.Context) error { // it successfully moved to the next state and returns the new state. func (p Processor) moveToNextState(state *parsing.State, context *parsing.Context) ( bool, *parsing.State, error) { - for _, nextState := range p.TransitionsMap[*state] { + for _, nextState := range p.transitionsMap[*state] { if nextState.Recognize(*context) { - err := nextState.Accept(context, p.Config) + err := nextState.Accept(context, p.config) if err != nil { return false, &nextState, err } From 5ff8eff1ada5aa5383e08dbc9b7e924ac071dd83 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 24 Jun 2026 12:22:53 +0200 Subject: [PATCH 4/6] Improve docs. --- embedding/processor.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/embedding/processor.go b/embedding/processor.go index 21271f83..5dd19bcf 100644 --- a/embedding/processor.go +++ b/embedding/processor.go @@ -34,9 +34,16 @@ import ( // Processor processes a single documentation file using the provided embedding configuration. type Processor struct { - docFilePath string - config configuration.Configuration - transitionsMap parsing.TransitionMap + // docFilePath is the path to the documentation file. + docFilePath string + + // config contains the embedding settings. + config configuration.Configuration + + // transitionsMap defines valid parser state transitions. + transitionsMap parsing.TransitionMap + + // requiredDocPaths contains documentation files included by the configuration. requiredDocPaths []string } From 83a0334007f3af6568f3ef7c64247dcf3446586b Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 24 Jun 2026 12:36:35 +0200 Subject: [PATCH 5/6] Improve doc. --- embedding/orchestration.go | 2 ++ embedding/parsing/context.go | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/embedding/orchestration.go b/embedding/orchestration.go index fd591358..8e335fd0 100644 --- a/embedding/orchestration.go +++ b/embedding/orchestration.go @@ -92,6 +92,8 @@ func EmbedAll(config configuration.Configuration) (EmbedAllResult, error) { } // configNameLabel formats a configuration name for summary log messages. +// +// A non-empty label starts with a space so callers can append it directly. func configNameLabel(config configuration.Configuration) string { if config.Name == "" { return "" diff --git a/embedding/parsing/context.go b/embedding/parsing/context.go index 95f6f6c6..c037dfea 100644 --- a/embedding/parsing/context.go +++ b/embedding/parsing/context.go @@ -68,12 +68,11 @@ func (c *Context) EmbeddingsCount() int { // EmbeddingContext contains an instruction and its position in the source Markdown file. // -// embeddingInstruction - an Instruction, containing all the needed embedding information. +// SourceStartIndex is the zero-based index of the first line after the opening code fence. // -// SourceStartIndex - an index of the StartState line in the original markdown file. -// -// SourceEndIndex - an index of the end line in the original markdown file. +// SourceEndIndex is the zero-based index of the closing code fence and the exclusive slice bound. type EmbeddingContext struct { + // embeddingInstruction contains the embedding parameters. embeddingInstruction Instruction SourceStartIndex int SourceEndIndex int From 839600a4dbb8816a8e5214f6e73191eeca6e79be Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 24 Jun 2026 13:05:50 +0200 Subject: [PATCH 6/6] Improve processor behavior. --- embedding/embedding_test.go | 3 ++- embedding/orchestration.go | 20 ++++++++++------ embedding/processor.go | 48 ++++++++++++++++++------------------- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/embedding/embedding_test.go b/embedding/embedding_test.go index c8adbdb1..8020ce14 100644 --- a/embedding/embedding_test.go +++ b/embedding/embedding_test.go @@ -323,9 +323,10 @@ var _ = Describe("Embedding", func() { config.DocumentationRoot) processor := newProcessor(docPath, config) - _, err := embedding.EmbedAll(config) + result, err := embedding.EmbedAll(config) Expect(err).ShouldNot(HaveOccurred()) + Expect(result.UpdatedTargetFiles).Should(ContainElement(docPath)) Expect(processor.IsUpToDate()).Should(BeTrue()) }) diff --git a/embedding/orchestration.go b/embedding/orchestration.go index 8e335fd0..a9e0108d 100644 --- a/embedding/orchestration.go +++ b/embedding/orchestration.go @@ -42,8 +42,8 @@ type EmbedAllResult struct { UpdatedTargetFiles []string } -// processorHandler applies one processing mode to a configured documentation processor. -type processorHandler func(processor Processor) error +// processorHandler applies one processing mode to a discovered documentation file. +type processorHandler func(docFilePath string, processor Processor) error // EmbedAll processes embedding for multiple documentation files based on provided config. // @@ -54,14 +54,17 @@ type processorHandler func(processor Processor) error func EmbedAll(config configuration.Configuration) (EmbedAllResult, error) { totalEmbeddings := 0 var updatedTargetFiles []string - requiredDocPaths, embeddingErrors := processRequiredDocs(config, func(processor Processor) error { + requiredDocPaths, embeddingErrors := processRequiredDocs(config, func( + _ string, + processor Processor, + ) error { context, err := processor.Embed() if err != nil { return err } totalEmbeddings += context.EmbeddingsCount() if context.IsContentChanged() { - updatedTargetFiles = append(updatedTargetFiles, processor.docFilePath) + updatedTargetFiles = append(updatedTargetFiles, context.MarkdownFilePath) } return nil @@ -119,13 +122,16 @@ func CheckUpToDate(config configuration.Configuration) ([]string, error) { // config — a configuration for embedding. func findChangedFiles(config configuration.Configuration) ([]string, []error) { var changedFiles []string - _, checkErrors := processRequiredDocs(config, func(processor Processor) error { + _, checkErrors := processRequiredDocs(config, func( + docFilePath string, + processor Processor, + ) error { upToDate, err := processor.isUpToDate() if err != nil { return err } if !upToDate { - changedFiles = append(changedFiles, processor.docFilePath) + changedFiles = append(changedFiles, docFilePath) } return nil @@ -147,7 +153,7 @@ func processRequiredDocs( var processingErrors []error for _, doc := range requiredDocPaths { processor := newProcessor(doc, config, parsing.Transitions, requiredDocPaths) - if err := handle(processor); err != nil { + if err := handle(doc, processor); err != nil { processingErrors = append(processingErrors, err) } } diff --git a/embedding/processor.go b/embedding/processor.go index 5dd19bcf..0f385c48 100644 --- a/embedding/processor.go +++ b/embedding/processor.go @@ -34,14 +34,14 @@ import ( // Processor processes a single documentation file using the provided embedding configuration. type Processor struct { - // docFilePath is the path to the documentation file. - docFilePath string + // DocFilePath is the path to the documentation file. + DocFilePath string - // config contains the embedding settings. - config configuration.Configuration + // Config contains the embedding settings. + Config configuration.Configuration - // transitionsMap defines valid parser state transitions. - transitionsMap parsing.TransitionMap + // TransitionsMap defines valid parser state transitions. + TransitionsMap parsing.TransitionMap // requiredDocPaths contains documentation files included by the configuration. requiredDocPaths []string @@ -65,9 +65,9 @@ func newProcessor( requiredDocPaths []string, ) Processor { return Processor{ - docFilePath: docFile, - config: config, - transitionsMap: transitions, + DocFilePath: docFile, + Config: config, + TransitionsMap: transitions, requiredDocPaths: requiredDocPaths, } } @@ -77,31 +77,31 @@ func newProcessor( // Returns an empty context without parsing the file when it is excluded by configuration. // If any problems faced, an error is returned. func (p Processor) Embed() (*parsing.Context, error) { - if !slices.Contains(p.requiredDocPaths, p.docFilePath) { + if !slices.Contains(p.requiredDocPaths, p.DocFilePath) { slog.Info(fmt.Sprintf("Skipping `%s`; it is excluded by the configuration.", - logging.FileReference(p.docFilePath))) - context := parsing.NewEmptyContext(p.docFilePath) + logging.FileReference(p.DocFilePath))) + context := parsing.NewEmptyContext(p.DocFilePath) return &context, nil } - slog.Info(fmt.Sprintf("Started processing doc file `%s`.", logging.FileReference(p.docFilePath))) + slog.Info(fmt.Sprintf("Started processing doc file `%s`.", logging.FileReference(p.DocFilePath))) context, err := p.fillEmbeddingContext() if err != nil { return nil, err } if context.IsContainsEmbedding() && context.IsContentChanged() { data := []byte(strings.Join(context.GetResult(), "\n")) - err = os.WriteFile(p.docFilePath, data, os.FileMode(files.DocumentationFilePermission)) + err = os.WriteFile(p.DocFilePath, data, os.FileMode(files.DocumentationFilePermission)) if err != nil { return &context, err } slog.Info(fmt.Sprintf("Updated `%s` after processing %d embedding(s).", - logging.FileReference(p.docFilePath), context.EmbeddingsCount())) + logging.FileReference(p.DocFilePath), context.EmbeddingsCount())) } else { slog.Info(fmt.Sprintf( "Documentation is up-to-date in `%s`.", - logging.FileReference(p.docFilePath), + logging.FileReference(p.DocFilePath), )) } @@ -120,13 +120,13 @@ func (p Processor) IsUpToDate() bool { // isUpToDate reports whether the target markdown is up-to-date and returns processing errors. func (p Processor) isUpToDate() (bool, error) { - if !slices.Contains(p.requiredDocPaths, p.docFilePath) { + if !slices.Contains(p.requiredDocPaths, p.DocFilePath) { slog.Info(fmt.Sprintf("Skipping `%s`; it is excluded by the configuration.", - logging.FileReference(p.docFilePath))) + logging.FileReference(p.DocFilePath))) return true, nil } - slog.Info(fmt.Sprintf("Checking `%s`.", logging.FileReference(p.docFilePath))) + slog.Info(fmt.Sprintf("Checking `%s`.", logging.FileReference(p.DocFilePath))) context, err := p.fillEmbeddingContext() if err != nil { return false, err @@ -138,7 +138,7 @@ func (p Processor) isUpToDate() (bool, error) { status = "needs an update" } slog.Info(fmt.Sprintf("Checked `%s`: %d embedding(s), %s.", - logging.FileReference(p.docFilePath), context.EmbeddingsCount(), status)) + logging.FileReference(p.DocFilePath), context.EmbeddingsCount(), status)) return upToDate, nil } @@ -150,7 +150,7 @@ func (p Processor) isUpToDate() (bool, error) { // // Returns a parsing.Context and an error if any occurs. func (p Processor) fillEmbeddingContext() (parsing.Context, error) { - context, err := parsing.NewContext(p.docFilePath) + context, err := parsing.NewContext(p.DocFilePath) if err != nil { return context, err } @@ -182,7 +182,7 @@ func (p Processor) fillEmbeddingContext() (parsing.Context, error) { // processingError wraps a parsing error with the current documentation location. func (p Processor) processingError(context parsing.Context, err error) ProcessingError { return ProcessingError{ - DocFilePath: p.docFilePath, + DocFilePath: p.DocFilePath, Line: errorLine(context, err), Err: err, } @@ -233,9 +233,9 @@ func unacceptedTransitionError(context parsing.Context) error { // it successfully moved to the next state and returns the new state. func (p Processor) moveToNextState(state *parsing.State, context *parsing.Context) ( bool, *parsing.State, error) { - for _, nextState := range p.transitionsMap[*state] { + for _, nextState := range p.TransitionsMap[*state] { if nextState.Recognize(*context) { - err := nextState.Accept(context, p.config) + err := nextState.Accept(context, p.Config) if err != nil { return false, &nextState, err }