From 200f89b5604aa8df1719a27c8a68c035ba275050 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Tue, 12 May 2026 17:10:12 +0200 Subject: [PATCH 1/7] Fix `embed-code` tag multiline params. --- embedding/parsing/instruction_token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embedding/parsing/instruction_token.go b/embedding/parsing/instruction_token.go index 30efcea..cc9e6ad 100644 --- a/embedding/parsing/instruction_token.go +++ b/embedding/parsing/instruction_token.go @@ -57,7 +57,7 @@ func (e EmbedInstructionTokenState) Accept(context *Context, for !context.ReachedEOF() && context.EmbeddingInstruction == nil { instructionBody = append(instructionBody, context.CurrentLine()) - instruction, err := FromXML(strings.Join(instructionBody, ""), config) + instruction, err := FromXML(strings.Join(instructionBody, " "), config) if err == nil { context.SetEmbedding(&instruction) } From 45d76ca310695f918fed1194c08a1c79a0771ace Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Tue, 12 May 2026 17:11:04 +0200 Subject: [PATCH 2/7] Improve embedding tag usage. --- embedding/parsing/constants.go | 4 ++-- embedding/parsing/instruction_token.go | 2 +- embedding/parsing/xml_parse.go | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/embedding/parsing/constants.go b/embedding/parsing/constants.go index e1f9110..325ad86 100644 --- a/embedding/parsing/constants.go +++ b/embedding/parsing/constants.go @@ -18,8 +18,8 @@ package parsing -// EmbeddingTag is a StartState of a tag where it requires to embed the code. -const EmbeddingTag = " Date: Tue, 12 May 2026 18:16:36 +0200 Subject: [PATCH 3/7] Improve error logging. --- embedding/parsing/instruction_token.go | 50 ++++++++++++++++++++++++-- embedding/processor.go | 17 +++++++-- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/embedding/parsing/instruction_token.go b/embedding/parsing/instruction_token.go index 924e08f..b3234de 100644 --- a/embedding/parsing/instruction_token.go +++ b/embedding/parsing/instruction_token.go @@ -19,6 +19,8 @@ package parsing import ( + "encoding/xml" + "errors" "fmt" "strings" @@ -28,6 +30,20 @@ import ( // EmbedInstructionTokenState represents an embedding instruction token of a markdown. type EmbedInstructionTokenState struct{} +// InstructionParseError reports a failed embedding instruction parse and its source line. +type InstructionParseError struct { + Line int + Reason string +} + +// Error returns a user-facing description of an embedding instruction parse failure. +func (e InstructionParseError) Error() string { + return fmt.Sprintf( + "failed to parse an embedding instruction: %s", + e.Reason, + ) +} + // Recognize reports whether the current line in the parsing context starts with "") && + !strings.Contains(instruction, "") { + return fmt.Sprintf("the `<%s>` tag is not closed", + EmbeddingTag, + ) + } + if parseErr != nil { + var syntaxErr *xml.SyntaxError + if errors.As(parseErr, &syntaxErr) { + return syntaxErr.Msg + } + return parseErr.Error() + } + + return "invalid embedding instruction" +} diff --git a/embedding/processor.go b/embedding/processor.go index 839fda0..64572e8 100644 --- a/embedding/processor.go +++ b/embedding/processor.go @@ -217,13 +217,13 @@ func (p Processor) fillEmbeddingContext() (parsing.Context, error) { for currentState != finishState { accepted, newState, err := p.moveToNextState(¤tState, &context) if err != nil { - return context, fmt.Errorf(errorStr, absDocPath, context.CurrentEmbedding().SourceStartIndex-1, err) + return context, fmt.Errorf(errorStr, absDocPath, errorLine(context, err), err) } if !accepted { currentState = &parsing.RegularLineState{} context.ResolveUnacceptedEmbedding() - return context, fmt.Errorf(errorStr, absDocPath, context.CurrentEmbedding().SourceStartIndex-1, err) + return context, fmt.Errorf(errorStr, absDocPath, errorLine(context, err), err) } currentState = *newState } @@ -231,6 +231,19 @@ func (p Processor) fillEmbeddingContext() (parsing.Context, error) { return context, nil } +// errorLine returns the source line that should be used in the embedding error location. +func errorLine(context parsing.Context, err error) int { + var parseErr parsing.InstructionParseError + if errors.As(err, &parseErr) { + return parseErr.Line + } + if context.EmbeddingsCount() > 0 { + return context.CurrentEmbedding().SourceStartIndex - 1 + } + + return context.CurrentIndex() +} + // Moves to the next state accordingly to a transition map from the current state. Reports whether // it successfully moved to the next state and returns the new state. func (p Processor) moveToNextState(state *parsing.State, context *parsing.Context) ( From c575a0d78a016aaebae1d33e2587a93bb2110064 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Tue, 12 May 2026 18:16:47 +0200 Subject: [PATCH 4/7] Add tests. --- embedding/embedding_test.go | 36 +++++++++++++++++++ test/resources/docs/missing-closing-tag.md | 5 +++ .../docs/multi-lined-valid-tag-attributes.md | 8 +++++ test/resources/docs/unclosed-nested-tag.md | 5 +++ 4 files changed, 54 insertions(+) create mode 100644 test/resources/docs/missing-closing-tag.md create mode 100644 test/resources/docs/multi-lined-valid-tag-attributes.md create mode 100644 test/resources/docs/unclosed-nested-tag.md diff --git a/embedding/embedding_test.go b/embedding/embedding_test.go index 7262c4e..f8ee621 100644 --- a/embedding/embedding_test.go +++ b/embedding/embedding_test.go @@ -107,6 +107,42 @@ var _ = Describe("Embedding", func() { Expect(processor.IsUpToDate()).Should(BeTrue()) }) + It("should successfully embed with multi lined tag attributes", func() { + docPath := fmt.Sprintf("%s/multi-lined-valid-tag-attributes.md", config.DocumentationRoot) + processor := embedding.NewProcessor(docPath, config) + Expect(processor.Embed()).Error().ShouldNot(HaveOccurred()) + + Expect(processor.IsUpToDate()).Should(BeTrue()) + }) + + It("should report a missing closing tag", func() { + docPath := fmt.Sprintf("%s/missing-closing-tag.md", config.DocumentationRoot) + processor := embedding.NewProcessor(docPath, config) + + _, err := processor.Embed() + + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring( + "missing-closing-tag.md:3`: " + + "failed to parse an embedding instruction: " + + "the `` tag is not closed", + )) + }) + + It("should report the XML parser error for an unclosed nested tag", func() { + docPath := fmt.Sprintf("%s/unclosed-nested-tag.md", config.DocumentationRoot) + processor := embedding.NewProcessor(docPath, config) + + _, err := processor.Embed() + + Expect(err).Should(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring( + "unclosed-nested-tag.md:3`: " + + "failed to parse an embedding instruction: " + + "element closed by ", + )) + }) + // TODO:olena-zmiiova:https://github.com/SpineEventEngine/embed-code/issues/65 It("should successfully embed to a file in a nested dir", func() { Skip( diff --git a/test/resources/docs/missing-closing-tag.md b/test/resources/docs/missing-closing-tag.md new file mode 100644 index 0000000..50d9df6 --- /dev/null +++ b/test/resources/docs/missing-closing-tag.md @@ -0,0 +1,5 @@ +# Test missing closing tag + + +```java +``` diff --git a/test/resources/docs/multi-lined-valid-tag-attributes.md b/test/resources/docs/multi-lined-valid-tag-attributes.md new file mode 100644 index 0000000..772e954 --- /dev/null +++ b/test/resources/docs/multi-lined-valid-tag-attributes.md @@ -0,0 +1,8 @@ +# Test multiline attributes + + +```java +``` diff --git a/test/resources/docs/unclosed-nested-tag.md b/test/resources/docs/unclosed-nested-tag.md new file mode 100644 index 0000000..cbc1ff1 --- /dev/null +++ b/test/resources/docs/unclosed-nested-tag.md @@ -0,0 +1,5 @@ +# Test unclosed nested tag + + +```java +``` From f50453aaecfd25278634e59ac8ddc8a2e47dca12 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 13 May 2026 08:57:31 +0200 Subject: [PATCH 5/7] Improve docs. --- embedding/embedding_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embedding/embedding_test.go b/embedding/embedding_test.go index f8ee621..0463ef7 100644 --- a/embedding/embedding_test.go +++ b/embedding/embedding_test.go @@ -107,7 +107,7 @@ var _ = Describe("Embedding", func() { Expect(processor.IsUpToDate()).Should(BeTrue()) }) - It("should successfully embed with multi lined tag attributes", func() { + It("should embed with multi lined tag attributes", func() { docPath := fmt.Sprintf("%s/multi-lined-valid-tag-attributes.md", config.DocumentationRoot) processor := embedding.NewProcessor(docPath, config) Expect(processor.Embed()).Error().ShouldNot(HaveOccurred()) @@ -129,7 +129,7 @@ var _ = Describe("Embedding", func() { )) }) - It("should report the XML parser error for an unclosed nested tag", func() { + It("should report the XML parser error", func() { docPath := fmt.Sprintf("%s/unclosed-nested-tag.md", config.DocumentationRoot) processor := embedding.NewProcessor(docPath, config) From 75d6fb41b089d97a178e9b6d6f0e57b36f622b25 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk <106074440+Vladyslav-Kuksiuk@users.noreply.github.com> Date: Wed, 13 May 2026 12:28:00 +0200 Subject: [PATCH 6/7] Improve paths handling. --- .gitattributes | 1 + analyzing/analyzing.go | 10 ++++++---- embedding/processor.go | 6 ++++-- files/files.go | 2 +- fragmentation/fragment_file.go | 11 +++++++---- 5 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6313b56 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/analyzing/analyzing.go b/analyzing/analyzing.go index 80fb652..904fe10 100644 --- a/analyzing/analyzing.go +++ b/analyzing/analyzing.go @@ -3,7 +3,7 @@ package analyzing import ( "fmt" "os" - "strings" + "path/filepath" "embed-code/embed-code-go/configuration" "embed-code/embed-code-go/embedding" @@ -38,7 +38,7 @@ func AnalyzeAll(config configuration.Configuration) { // Generates a path to a given file in the analytics directory. func pathToFile(fileName string) string { - return fmt.Sprintf("%s/%s", analyticsDir, fileName) + return filepath.Join(analyticsDir, fileName) } // Finds all documentation files for given config. @@ -47,12 +47,14 @@ func findDocumentationFiles(config configuration.Configuration) []string { docPatterns := config.DocIncludes var documentationFiles []string for _, pattern := range docPatterns { - globString := strings.Join([]string{documentationRoot, pattern}, "/") + globString := filepath.Join(documentationRoot, filepath.FromSlash(pattern)) matches, err := doublestar.FilepathGlob(globString) if err != nil { panic(err) } - documentationFiles = append(documentationFiles, matches...) + for _, match := range matches { + documentationFiles = append(documentationFiles, filepath.ToSlash(match)) + } } return documentationFiles diff --git a/embedding/processor.go b/embedding/processor.go index 64572e8..b612947 100644 --- a/embedding/processor.go +++ b/embedding/processor.go @@ -302,12 +302,14 @@ func requiredDocs(config configuration.Configuration) []string { func getFilesByPatterns(root string, patterns []string) ([]string, error) { var result []string for _, pattern := range patterns { - globString := strings.Join([]string{root, pattern}, "/") + globString := filepath.Join(root, filepath.FromSlash(pattern)) matches, err := doublestar.FilepathGlob(globString) if err != nil { return nil, err } - result = append(result, matches...) + for _, match := range matches { + result = append(result, filepath.ToSlash(match)) + } } return result, nil diff --git a/files/files.go b/files/files.go index 78b9ec8..0464acd 100644 --- a/files/files.go +++ b/files/files.go @@ -90,7 +90,7 @@ func BuildDocRelativePath(absolutePath string, config configuration.Configuratio panic(err) } - return relativePath + return filepath.ToSlash(relativePath) } // EnsureDirExists creates dir at given path (relative or absolute) if it doesn't exist. diff --git a/fragmentation/fragment_file.go b/fragmentation/fragment_file.go index c3b465e..9794ca3 100644 --- a/fragmentation/fragment_file.go +++ b/fragmentation/fragment_file.go @@ -20,15 +20,16 @@ package fragmentation import ( "crypto/sha256" - _type "embed-code/embed-code-go/type" "encoding/hex" "fmt" "os" + "path" "path/filepath" "strings" config "embed-code/embed-code-go/configuration" "embed-code/embed-code-go/files" + _type "embed-code/embed-code-go/type" ) // FragmentFile is a file storing a single fragment from the file. @@ -73,7 +74,9 @@ func NewFragmentFileFromAbsolute( } if strings.TrimSpace(codeRoot.Name) != "" { - relativePath = filepath.Join(NamedPathPrefix+codeRoot.Name, relativePath) + relativePath = path.Join(NamedPathPrefix+codeRoot.Name, filepath.ToSlash(relativePath)) + } else { + relativePath = filepath.ToSlash(relativePath) } return FragmentFile{ @@ -143,10 +146,10 @@ func (f FragmentFile) absolutePath() string { } if f.FragmentName == DefaultFragmentName { - return filepath.Join(fragmentsAbsDir, f.CodePath) + return filepath.Join(fragmentsAbsDir, filepath.FromSlash(f.CodePath)) } - withoutExtension := strings.TrimSuffix(f.CodePath, fileExtension) + withoutExtension := filepath.FromSlash(strings.TrimSuffix(f.CodePath, fileExtension)) filename := fmt.Sprintf("%s-%s", withoutExtension, f.fragmentHash()) return filepath.Join(fragmentsAbsDir, filename+fileExtension) From 5b31eeaa733f4b3c01425203464c7980d3651c6e Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 13 May 2026 12:53:25 +0200 Subject: [PATCH 7/7] Update version. --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index d78aabf..f40913a 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,7 @@ import ( ) // Version of the embed-code application. -const Version = "1.1.0" +const Version = "1.1.1" // The entry point for embed-code. //