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
35 changes: 10 additions & 25 deletions fragmentation/encoding.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,37 +19,22 @@
package fragmentation

import (
"os"
"unicode/utf8"
)

const lastASCIIchar = 127
// unsupportedEncodingError indicates that source content is not valid UTF-8.
type unsupportedEncodingError struct{}

// IsEncodedAsText reports whether the file stored at filePath is encoded as a text.
//
// If file encoded in ASCII or UTF-8, it is meant to be a text file.
func IsEncodedAsText(filePath string) (bool, error) {
// Read the entire file into memory.
content, err := os.ReadFile(filePath)
if err != nil {
return false, err
}

isUTF8Encoded := utf8.Valid(content)
isASCIIEncoded := areASCIIEncoded(content)

return isUTF8Encoded || isASCIIEncoded, nil
// Error describes the required source encoding.
func (*unsupportedEncodingError) Error() string {
return "unsupported source encoding: expected UTF-8"
}

// Reports whether given bytes are ASCII-encoded.
//
// If all the characters fall within the ASCII range (0 to 127), it’s likely an ASCII-encoded file.
func areASCIIEncoded(bytes []byte) bool {
for _, char := range bytes {
if char > byte(lastASCIIchar) {
return false
}
// validateTextEncoding reports whether source content uses UTF-8 text encoding.
func validateTextEncoding(content []byte) error {
if !utf8.Valid(content) {
return &unsupportedEncodingError{}
}

return true
return nil
}
Comment thread
Vladyslav-Kuksiuk marked this conversation as resolved.
83 changes: 21 additions & 62 deletions fragmentation/fragmentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,60 +37,36 @@ package fragmentation

import (
"bufio"
"embed-code/embed-code-go/files"
_type "embed-code/embed-code-go/type"
"bytes"
"fmt"
"os"
"path/filepath"

config "embed-code/embed-code-go/configuration"
)

// NamedPathPrefix the prefix before the named code source.
const NamedPathPrefix = "$"

// Fragmentation splits the given file into fragments.
//
// Configuration — a configuration for embedding.
//
// SourcesRoot — a named source code path.
//
// CodeFile — a full path of a file to fragment.
type Fragmentation struct {
Configuration config.Configuration
SourcesRoot _type.NamedPath
CodeFile string
// codeFile is the absolute path of the source file being fragmented.
codeFile string
// fragmentBuilders collects fragment partitions by name while the source file is scanned.
fragmentBuilders map[string]*FragmentBuilder

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's document this prop.

}

// NewFragmentation builds Fragmentation from given codeFileRelative and config.
//
// codeFileRelative — a relative path to a code file to fragment.
// NewFragmentation builds Fragmentation for the given code file.
//
// config — a configuration for embedding.
func NewFragmentation(
codeFileRelative string,
codeRoot _type.NamedPath,
config config.Configuration,
) (Fragmentation, error) {
fragmentation := Fragmentation{}

fragmentation.SourcesRoot = codeRoot
_, err := filepath.Abs(codeRoot.Path)
// codeFile — a relative or absolute path to a code file to fragment.
func NewFragmentation(codeFile string) (Fragmentation, error) {
absoluteCodeFile, err := filepath.Abs(codeFile)
if err != nil {
return Fragmentation{}, err
}

absoluteCodeFile, err := filepath.Abs(codeFileRelative)
if err != nil {
return Fragmentation{}, err
}
fragmentation.CodeFile = absoluteCodeFile

fragmentation.Configuration = config
fragmentation.fragmentBuilders = make(map[string]*FragmentBuilder)

return fragmentation, nil
return Fragmentation{
codeFile: absoluteCodeFile,
fragmentBuilders: make(map[string]*FragmentBuilder),
}, nil
}

// DoFragmentation splits the file into fragments.
Expand All @@ -100,16 +76,15 @@ func NewFragmentation(
func (f Fragmentation) DoFragmentation() ([]string, map[string]Fragment, error) {
var contentToRender []string

file, err := os.Open(f.CodeFile)
content, err := os.ReadFile(f.codeFile)
if err != nil {
return nil, nil, err
}
if err := validateTextEncoding(content); err != nil {
return nil, nil, err
}

defer func(file *os.File) {
err = file.Close()
}(file)

scanner := bufio.NewScanner(file)
scanner := bufio.NewScanner(bytes.NewReader(content))
lineNumber := 0
for scanner.Scan() {
lineNumber++
Expand All @@ -118,11 +93,11 @@ func (f Fragmentation) DoFragmentation() ([]string, map[string]Fragment, error)
if err != nil {
return nil, nil, fmt.Errorf(
"failed to do fragmentation on file `file://%s:%d`: %w",
f.CodeFile, lineNumber, err,
f.codeFile, lineNumber, err,
)
}
}
if err = scanner.Err(); err != nil {
if err := scanner.Err(); err != nil {
return nil, nil, err
}

Expand All @@ -135,22 +110,6 @@ func (f Fragmentation) DoFragmentation() ([]string, map[string]Fragment, error)
return contentToRender, fragments, nil
}

// shouldDoFragmentation reports whether the file is valid to do fragmentation:
// - it exists by the given path
// - it is a file (not a dir)
// - it is textual-encoded.
func shouldDoFragmentation(filePath string) (bool, error) {
exists, err := files.IsFileExist(filePath)
if err != nil {
return false, err
}
if exists {
return IsEncodedAsText(filePath)
}

return false, nil
}

// Parses a single line of input and performs the following actions:
// - identifies fragment start and end markers within given line;
// - updates fragmentBuilders based on the markers;
Expand Down Expand Up @@ -196,7 +155,7 @@ func (f Fragmentation) parseStartDocFragments(docFragments []string, cursor int)
fragment, exists := f.fragmentBuilders[fragmentName]
if !exists {
builder := FragmentBuilder{
CodeFilePath: f.CodeFile,
CodeFilePath: f.codeFile,
Name: fragmentName,
}
f.fragmentBuilders[fragmentName] = &builder
Expand All @@ -220,7 +179,7 @@ func (f Fragmentation) parseEndDocFragments(endDocFragments []string, cursor int
}
} else {
return fmt.Errorf("cannot end the fragment `%s` of the file `file://%s` as it wasn't started",
fragmentName, f.CodeFile)
fragmentName, f.codeFile)
}
}

Expand Down
29 changes: 28 additions & 1 deletion fragmentation/fragmentation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"embed-code/embed-code-go/fragmentation"
_type "embed-code/embed-code-go/type"
"fmt"
"os"
"path/filepath"
"testing"

. "github.com/onsi/ginkgo/v2"
Expand Down Expand Up @@ -94,6 +96,31 @@ var _ = Describe("Fragmentation", func() {
Expect(fragments).Should(HaveKey(fragmentation.DefaultFragmentName))
})

It("should skip a non-UTF-8 source and use the next code root", func() {
invalidRoot := GinkgoT().TempDir()
validRoot := GinkgoT().TempDir()
fileName := "Example.java"
Expect(os.WriteFile(filepath.Join(invalidRoot, fileName), []byte{0xff}, 0600)).To(Succeed())
Expect(os.WriteFile(
filepath.Join(validRoot, fileName),
[]byte("class Example {}"),
0600,
)).To(Succeed())
config.CodeRoots = _type.NamedPathList{
_type.NamedPath{Path: invalidRoot},
_type.NamedPath{Path: validRoot},
}

content, err := fragmentation.ResolveContent(
fileName,
fragmentation.DefaultFragmentName,
config,
)

Expect(err).ShouldNot(HaveOccurred())
Expect(content).Should(Equal([]string{"class Example {}"}))
})

It("should fail on an unopened fragment", func() {
frag := buildTestFragmentation(unopenedFragmentFileName, config)

Expand Down Expand Up @@ -221,7 +248,7 @@ func buildTestFragmentation(testFileName string,
config configuration.Configuration) fragmentation.Fragmentation {
codeRoot := config.CodeRoots[0]
testFilePath := fmt.Sprintf("%s/org/example/%s", codeRoot.Path, testFileName)
frag, err := fragmentation.NewFragmentation(testFilePath, codeRoot, config)
frag, err := fragmentation.NewFragmentation(testFilePath)

Expect(err).ShouldNot(HaveOccurred())

Expand Down
Loading
Loading