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
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
10 changes: 6 additions & 4 deletions analyzing/analyzing.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package analyzing
import (
"fmt"
"os"
"strings"
"path/filepath"

"embed-code/embed-code-go/configuration"
"embed-code/embed-code-go/embedding"
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
36 changes: 36 additions & 0 deletions embedding/embedding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,42 @@ var _ = Describe("Embedding", func() {
Expect(processor.IsUpToDate()).Should(BeTrue())
})

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())

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 `<embed-code>` tag is not closed",
))
})

It("should report the XML parser error", 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 <unexpected> closed by </embed-code>",
))
})

// TODO:olena-zmiiova:https://github.com/SpineEventEngine/embed-code/issues/65
It("should successfully embed to a file in a nested dir", func() {
Skip(
Expand Down
4 changes: 2 additions & 2 deletions embedding/parsing/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

package parsing

// EmbeddingTag is a StartState of a tag where it requires to embed the code.
const EmbeddingTag = "<embed-code"
// EmbeddingTag is a name of a tag which marks where to embed the code.
const EmbeddingTag = "embed-code"

// TransitionMap is a type for mapping one State to a list of possible next States.
type TransitionMap map[State][]State
Expand Down
54 changes: 49 additions & 5 deletions embedding/parsing/instruction_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
package parsing

import (
"encoding/xml"
"errors"
"fmt"
"strings"

Expand All @@ -28,14 +30,28 @@ 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 "<embed-code",
// and if there is no ongoing embedding and the end of the file is not reached, it returns true.
// Otherwise, it returns false.
//
// context — a context of the parsing process.
func (e EmbedInstructionTokenState) Recognize(context Context) bool {
line := context.CurrentLine()
isStatement := strings.HasPrefix(strings.TrimSpace(line), EmbeddingTag)
isStatement := strings.HasPrefix(strings.TrimSpace(line), "<"+EmbeddingTag)
if context.EmbeddingInstruction == nil && !context.ReachedEOF() && isStatement {
return true
}
Expand All @@ -54,20 +70,48 @@ func (e EmbedInstructionTokenState) Recognize(context Context) bool {
func (e EmbedInstructionTokenState) Accept(context *Context,
config configuration.Configuration) error {
var instructionBody []string
startLine := context.CurrentIndex()
var parseErr error
for !context.ReachedEOF() && context.EmbeddingInstruction == nil {
instructionBody = append(instructionBody, context.CurrentLine())
line := context.CurrentLine()
instructionBody = append(instructionBody, line)

instruction, err := FromXML(strings.Join(instructionBody, ""), config)
instruction, err := FromXML(strings.Join(instructionBody, " "), config)
if err == nil {
context.SetEmbedding(&instruction)
} else {
parseErr = err
}

context.Result = append(context.Result, context.CurrentLine())
context.Result = append(context.Result, line)
context.ToNextLine()
}
if context.EmbeddingInstruction == nil {
return fmt.Errorf("failed to parse an embedding instruction. Context: %v", context)
return InstructionParseError{
Line: startLine,
Reason: parseFailureReason(instructionBody, parseErr),
}
}

return nil
}

// parseFailureReason explains why an embedding instruction could not be parsed.
func parseFailureReason(instructionBody []string, parseErr error) string {
instruction := strings.TrimSpace(strings.Join(instructionBody, " "))
if !strings.Contains(instruction, "/>") &&
!strings.Contains(instruction, "</"+EmbeddingTag+">") {
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"
}
6 changes: 2 additions & 4 deletions embedding/parsing/xml_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import (
"fmt"
)

const xmlStringHeader string = "embed-code"

// Item needed for xml.Unmarshal parsing. The fields are filling up during the parsing.
//
// XMLName — a name of the tag in XML line.
Expand Down Expand Up @@ -74,9 +72,9 @@ func ParseXMLLine(xmlLine string) (map[string]string, error) {
return map[string]string{}, err
}

if root.XMLName.Local != xmlStringHeader {
if root.XMLName.Local != EmbeddingTag {
return map[string]string{},
fmt.Errorf("the provided line's header is not 'embed-code':\n%s", xmlLine)
fmt.Errorf("the provided line's header is not `%s`:\n%s", EmbeddingTag, xmlLine)
}

attributes := make(map[string]string)
Expand Down
23 changes: 19 additions & 4 deletions embedding/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,20 +217,33 @@ func (p Processor) fillEmbeddingContext() (parsing.Context, error) {
for currentState != finishState {
accepted, newState, err := p.moveToNextState(&currentState, &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
}

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) (
Expand Down Expand Up @@ -289,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
Expand Down
2 changes: 1 addition & 1 deletion files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 7 additions & 4 deletions fragmentation/fragment_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
5 changes: 5 additions & 0 deletions test/resources/docs/missing-closing-tag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Test missing closing tag

<embed-code file="org/example/Hello.java">
```java
```
8 changes: 8 additions & 0 deletions test/resources/docs/multi-lined-valid-tag-attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Test multiline attributes

<embed-code
file="org/example/Hello.java"
start="public class Hello"
end="System.out.println" />
```java
```
5 changes: 5 additions & 0 deletions test/resources/docs/unclosed-nested-tag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Test unclosed nested tag

<embed-code file="org/example/Hello.java"><unexpected></embed-code>
```java
```
Loading