From 820eeaedadb21c53955078536f3e06480a981684 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 13 May 2026 16:20:17 +0200 Subject: [PATCH 1/4] Implement in-memory fragments resolver. --- embedding/parsing/instruction.go | 18 +-- fragmentation/fragment.go | 14 -- fragmentation/fragment_file.go | 240 ------------------------------- fragmentation/fragmentation.go | 163 +-------------------- fragmentation/resolver.go | 214 +++++++++++++++++++++++++++ 5 files changed, 219 insertions(+), 430 deletions(-) delete mode 100644 fragmentation/fragment_file.go create mode 100644 fragmentation/resolver.go diff --git a/embedding/parsing/instruction.go b/embedding/parsing/instruction.go index 1033eb3..76cc87f 100644 --- a/embedding/parsing/instruction.go +++ b/embedding/parsing/instruction.go @@ -100,25 +100,15 @@ func NewInstruction( // // Returns an error if there was an error during reading the content. func (e Instruction) Content() ([]string, error) { - fragmentName := e.Fragment - if fragmentName == "" { - fragmentName = fragmentation.DefaultFragmentName - } - file := fragmentation.FragmentFile{ - CodePath: e.CodeFile, - FragmentName: fragmentName, - Configuration: e.Configuration, + fileContent, err := fragmentation.ResolveContent(e.CodeFile, e.Fragment, e.Configuration) + if err != nil { + return nil, err } if e.StartPattern != nil || e.EndPattern != nil { - fileContent, err := file.Content() - if err != nil { - return nil, err - } - return e.matchingLines(fileContent), nil } - return file.Content() + return fileContent, nil } // Returns string representation of Instruction. diff --git a/fragmentation/fragment.go b/fragmentation/fragment.go index aedfdee..fa45fbd 100644 --- a/fragmentation/fragment.go +++ b/fragmentation/fragment.go @@ -44,20 +44,6 @@ func CreateDefaultFragment() Fragment { } } -// WriteTo takes given lines, unites them into a text and writes it into given file. -// -// file — a FragmentFile to write the lines to. -// -// lines — a list of strings to write. -// -// separator — string to insert between multiple partitions of a single fragment. -// -// Creates the file if not exists and overwrites if exists. -func (f Fragment) WriteTo(file FragmentFile, lines []string, separator string) { - text := f.text(lines, separator) - file.Write(text) -} - func (f Fragment) isDefault() bool { return f.Name == DefaultFragmentName } diff --git a/fragmentation/fragment_file.go b/fragmentation/fragment_file.go deleted file mode 100644 index c3b465e..0000000 --- a/fragmentation/fragment_file.go +++ /dev/null @@ -1,240 +0,0 @@ -// 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 fragmentation - -import ( - "crypto/sha256" - _type "embed-code/embed-code-go/type" - "encoding/hex" - "fmt" - "os" - "path/filepath" - "strings" - - config "embed-code/embed-code-go/configuration" - "embed-code/embed-code-go/files" -) - -// FragmentFile is a file storing a single fragment from the file. -// -// CodePath — a relative path to a code file. The path is relative to the corresponding code root, -// and starts with the code root name if it's provided. -// -// FragmentName — a name of the fragment in the code file. -// -// Configuration — a configuration for embedding. -type FragmentFile struct { - CodePath string - FragmentName string - Configuration config.Configuration -} - -// NewFragmentFileFromAbsolute composes a FragmentFile for the given fragment in given codeFile. -// -// codeFile — an absolute path to a code file. -// -// codeRoot - a _type.NamedPath to the code root. -// -// fragmentName — a name of the fragment in the code file. -// -// configuration — configuration for embedding. -// -// Returns composed fragment. -func NewFragmentFileFromAbsolute( - codeFile string, - codeRoot _type.NamedPath, - fragmentName string, - config config.Configuration, -) FragmentFile { - absolutePath, err := filepath.Abs(codeRoot.Path) - if err != nil { - panic(err) - } - - relativePath, err := filepath.Rel(absolutePath, codeFile) - if err != nil { - panic(err) - } - - if strings.TrimSpace(codeRoot.Name) != "" { - relativePath = filepath.Join(NamedPathPrefix+codeRoot.Name, relativePath) - } - - return FragmentFile{ - CodePath: relativePath, - FragmentName: fragmentName, - Configuration: config, - } -} - -// Writes the given text to the file. -// -// Creates the file if not exists and overwrites if exists. -func (f FragmentFile) Write(text string) { - byteStr := []byte(text) - filePath := f.absolutePath() - err := os.WriteFile(filePath, byteStr, os.FileMode(files.WritePermission)) - if err != nil { - panic(err) - } -} - -// Content reads content of the file. -// -// Returns contents of the file as a list of strings, or returns an error if it doesn't exist. -func (f FragmentFile) Content() ([]string, error) { - path := f.absolutePath() - isPathFileExits, err := files.IsFileExist(path) - - if err != nil { - return nil, err - } - - if !isPathFileExits { - codeFileReference, err := f.codeFileReference() - if err != nil { - return nil, err - } - - if f.FragmentName != "" { - if f.FragmentName == "_default" { - return nil, fmt.Errorf("code file `%s` not found", codeFileReference) - } - return nil, fmt.Errorf( - "fragment `%s` from code file `%s` not found", f.FragmentName, codeFileReference, - ) - } - return nil, fmt.Errorf( - "code file %s fragment not found", - codeFileReference, - ) - } - - return files.ReadFile(path) -} - -// Returns string representation of FragmentFile. -func (f FragmentFile) String() string { - return f.absolutePath() -} - -// Obtains the absolute path to this fragment file. -func (f FragmentFile) absolutePath() string { - fileExtension := filepath.Ext(f.CodePath) - fragmentsAbsDir, err := filepath.Abs(f.Configuration.FragmentsDir) - if err != nil { - panic(err) - } - - if f.FragmentName == DefaultFragmentName { - return filepath.Join(fragmentsAbsDir, f.CodePath) - } - - withoutExtension := strings.TrimSuffix(f.CodePath, fileExtension) - filename := fmt.Sprintf("%s-%s", withoutExtension, f.fragmentHash()) - - return filepath.Join(fragmentsAbsDir, filename+fileExtension) -} - -// Builds a user-facing reference to the original code file for error messages. -// -// If the source file exists, returns its absolute `file://` URL. If the file path uses a named -// code root and the resolved file does not exist, returns both the prefixed path and the expanded -// absolute path. -func (f FragmentFile) codeFileReference() (string, error) { - originalCodePath, isPrefixed, err := f.originalCodePath() - if err != nil { - return "", err - } - if originalCodePath == "" { - return f.CodePath, nil - } - - exists, err := files.IsFileExist(originalCodePath) - if err != nil { - return "", err - } - if exists { - return "file://" + originalCodePath, nil - } - if isPrefixed { - return fmt.Sprintf("%s (%s)", f.CodePath, originalCodePath), nil - } - - return originalCodePath, nil -} - -// Resolves the original source file path from the fragment's code path. -// -// Returns the absolute path to the source file and reports whether the input path used a named -// code-root prefix such as `$runtime/...`. -// -// Returns an error if the path uses a named code root that is not present in the configuration. -func (f FragmentFile) originalCodePath() (string, bool, error) { - normalizedPath := filepath.ToSlash(filepath.Clean(f.CodePath)) - - if strings.HasPrefix(normalizedPath, NamedPathPrefix) { - withoutPrefix := strings.TrimPrefix(normalizedPath, NamedPathPrefix) - codeRootName, relativePath, _ := strings.Cut(withoutPrefix, "/") - - for _, codeRoot := range f.Configuration.CodeRoots { - if strings.TrimSpace(codeRoot.Name) != codeRootName { - continue - } - - return filepath.Join(resolvedRootPath(codeRoot.Path), filepath.FromSlash(relativePath)), - true, nil - } - - return "", true, fmt.Errorf("code root with name `%s` not found for path `%s`", - codeRootName, f.CodePath) - } - - if len(f.Configuration.CodeRoots) == 1 { - return filepath.Join( - resolvedRootPath(f.Configuration.CodeRoots[0].Path), - filepath.FromSlash(normalizedPath), - ), false, nil - } - - return "", false, nil -} - -// Resolves the given path to an absolute path when possible. -// -// If absolute-path resolution fails, returns the original path. -func resolvedRootPath(path string) string { - absolutePath, err := filepath.Abs(path) - if err != nil { - return path - } - - return absolutePath -} - -// Calculates and returns a hash string for FragmentFile. -// -// Since fragments which have the same name unite into one fragment with multiple partitions, -// the name of a fragment is unique. -func (f FragmentFile) fragmentHash() string { - hash := sha256.New() - hash.Write([]byte(f.FragmentName)) - - return hex.EncodeToString(hash.Sum(nil))[:8] -} diff --git a/fragmentation/fragmentation.go b/fragmentation/fragmentation.go index b08c666..1e3ebf3 100644 --- a/fragmentation/fragmentation.go +++ b/fragmentation/fragmentation.go @@ -40,21 +40,16 @@ import ( "embed-code/embed-code-go/files" _type "embed-code/embed-code-go/type" "fmt" - "log/slog" "os" "path/filepath" - "strings" config "embed-code/embed-code-go/configuration" - - "github.com/bmatcuk/doublestar/v4" ) // NamedPathPrefix the prefix before the named code source. const NamedPathPrefix = "$" -// Fragmentation splits the given file into fragments and writes them into corresponding -// output files. +// Fragmentation splits the given file into fragments. // // Configuration — a configuration for embedding. // @@ -68,17 +63,6 @@ type Fragmentation struct { fragmentBuilders map[string]*FragmentBuilder } -// WriteFragmentFilesResult is result of the WriteFragmentFiles method. -// -// TotalSourceFiles total number of source code files. -// -// TotalFragments is the total number of fragments extracted from the source code files. -// A whole source file also counts as a fragment. -type WriteFragmentFilesResult struct { - TotalSourceFiles int - TotalFragments int -} - // NewFragmentation builds Fragmentation from given codeFileRelative and config. // // codeFileRelative — a relative path to a code file to fragment. @@ -151,129 +135,6 @@ func (f Fragmentation) DoFragmentation() ([]string, map[string]Fragment, error) return contentToRender, fragments, nil } -// WriteFragments serializes fragments to the output directory. -// -// Keeps the original directory structure relative to the sources root dir. -// That is, `SRC/src/main` becomes `OUT/src/main`. -// -// Returns fragments or an error if the fragmentation couldn't be done. -func (f Fragmentation) WriteFragments() (map[string]Fragment, error) { - allLines, fragments, err := f.DoFragmentation() - if err != nil { - return nil, err - } - - err = files.EnsureDirExists(f.targetDirectory()) - if err != nil { - return nil, err - } - - for _, fragment := range fragments { - fragmentFile := NewFragmentFileFromAbsolute( - f.CodeFile, f.SourcesRoot, fragment.Name, f.Configuration, - ) - fragment.WriteTo(fragmentFile, allLines, f.Configuration.Separator) - } - - return fragments, nil -} - -// WriteFragmentFiles writes each fragment into a corresponding file. -// -// Searches for code files with patterns defined in configuration and makes fragments of them with -// creating fragmented files as a result. -// -// All fragments are placed inside Configuration.FragmentsDir with keeping the original directory -// structure relative to the sources root dir. -// That is, `SRC/src/main` becomes `OUT/src/main`. -// If code root is named, `SRC/src/main` becomes `OUT/$CODE_ROOT_NAME/src/main` -// -// config — is a configuration for embedding. -// -// Returns an error if any of the fragments couldn't be written. -func WriteFragmentFiles(config config.Configuration) WriteFragmentFilesResult { - includes := config.CodeIncludes - codeRoots := config.CodeRoots - totalSourceFiles := 0 - totalFragments := 0 - for _, codeRoot := range codeRoots { - codeRootFiles := 0 - codeRootFragments := 0 - for _, rule := range includes { - pattern := filepath.Join(codeRoot.Path, rule) - codeFiles, err := doublestar.FilepathGlob(pattern) - codeRootFiles += len(codeFiles) - if err != nil { - panic(err) - } - for _, codeFile := range codeFiles { - fragments, err := writeFragments(config, codeRoot, codeFile) - if err != nil { - panic(err) - } - codeRootFragments += len(fragments) - } - } - totalSourceFiles += codeRootFiles - totalFragments += codeRootFragments - if codeRootFiles > 0 { - slog.Info( - fmt.Sprintf("Found `%d` source code files with `%d` fragments under `%s`%s.", - codeRootFiles, codeRootFragments, codeRoot.Path, configNameLabel(config)), - ) - } else { - slog.Warn( - fmt.Sprintf("No code fragments were found under `%s`%s.", - codeRoot.Path, configNameLabel(config)), - ) - } - } - - return WriteFragmentFilesResult{ - TotalSourceFiles: totalSourceFiles, - TotalFragments: totalFragments, - } -} - -func configNameLabel(config config.Configuration) string { - if config.Name == "" { - return "" - } - return fmt.Sprintf(" for embedding `%s`", config.Name) -} - -// CleanFragmentFiles deletes Configuration.FragmentsDir if it exists. -func CleanFragmentFiles(config config.Configuration) { - exists, err := files.IsDirExist(config.FragmentsDir) - if err != nil { - panic(err) - } - if !exists { - panic(fmt.Errorf("%s directory is not exist", config.FragmentsDir)) - } - if err = os.RemoveAll(config.FragmentsDir); err != nil { - panic(err) - } -} - -// Checks if the code is able to split into fragments and writes them to a file. -func writeFragments( - config config.Configuration, - codeRoot _type.NamedPath, - codeFile string, -) (map[string]Fragment, error) { - if shouldDoFragmentation(codeFile) { - fragmentation := NewFragmentation(codeFile, codeRoot, config) - fragments, err := fragmentation.WriteFragments() - if err != nil { - return nil, err - } - return fragments, nil - } - - return map[string]Fragment{}, nil -} - // shouldDoFragmentation reports whether the file is valid to do fragmentation: // - it exists by the given path // - it is a file (not a dir) @@ -365,25 +226,3 @@ func (f Fragmentation) parseEndDocFragments(endDocFragments []string, cursor int return nil } - -// Obtains the target directory path based on the Configuration.FragmentsDir and the parent -// dir of Fragmentation.CodeFile. -func (f Fragmentation) targetDirectory() string { - fragmentsDir := f.Configuration.FragmentsDir - codeRoot, err := filepath.Abs(f.SourcesRoot.Path) - codeRootName := strings.TrimSpace(f.SourcesRoot.Name) - if err != nil { - panic(fmt.Sprintf("error calculating absolute path: %v", err)) - } - relativeFile, err := filepath.Rel(codeRoot, f.CodeFile) - if err != nil { - panic(fmt.Sprintf("error calculating relative path: %v", err)) - } - subTree := filepath.Dir(relativeFile) - - if codeRootName != "" { - return filepath.Join(fragmentsDir, NamedPathPrefix+codeRootName, subTree) - } - - return filepath.Join(fragmentsDir, subTree) -} diff --git a/fragmentation/resolver.go b/fragmentation/resolver.go new file mode 100644 index 0000000..8635ef4 --- /dev/null +++ b/fragmentation/resolver.go @@ -0,0 +1,214 @@ +// 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 fragmentation + +import ( + "fmt" + "path/filepath" + "strings" + "sync" + + config "embed-code/embed-code-go/configuration" + _type "embed-code/embed-code-go/type" +) + +type cachedFragmentation struct { + lines []string + fragments map[string]Fragment +} + +var resolverCache = struct { + sync.RWMutex + files map[string]cachedFragmentation +}{ + files: make(map[string]cachedFragmentation), +} + +// ResolveContent returns source lines for the requested code file fragment. +// +// Named fragments are extracted directly from the source file on demand and cached by source file. +func ResolveContent(codePath string, fragmentName string, config config.Configuration) ([]string, error) { + if fragmentName == "" { + fragmentName = DefaultFragmentName + } + + source, found, err := resolveSource(codePath, config) + if err != nil { + return nil, err + } + if !found { + return nil, unresolvedSourceError(codePath, fragmentName, config) + } + + content, err := cachedSourceFragments(source, config) + if err != nil { + return nil, err + } + + fragment, found := content.fragments[fragmentName] + if !found { + codeFileReference := "file://" + source.absolutePath + return nil, fmt.Errorf("fragment `%s` from code file `%s` not found", + fragmentName, codeFileReference) + } + + return fragmentLines(fragment, content.lines, config.Separator), nil +} + +// ClearResolverCache removes cached source fragmentations. +func ClearResolverCache() { + resolverCache.Lock() + defer resolverCache.Unlock() + + resolverCache.files = make(map[string]cachedFragmentation) +} + +type resolvedSource struct { + root _type.NamedPath + relativePath string + absolutePath string +} + +// resolveSource resolves the user-facing code path to an included textual source file. +func resolveSource(codePath string, config config.Configuration) (resolvedSource, bool, error) { + codeRootName, relativePath, named := splitNamedPath(codePath) + for _, root := range config.CodeRoots { + if named && strings.TrimSpace(root.Name) != codeRootName { + continue + } + + source, err := sourceFromRoot(root, relativePath) + if err != nil { + return resolvedSource{}, false, err + } + if !shouldDoFragmentation(source.absolutePath) { + continue + } + + return source, true, nil + } + + return resolvedSource{}, false, nil +} + +// splitNamedPath separates a named-code-root prefix from a code path. +func splitNamedPath(codePath string) (string, string, bool) { + normalizedPath := filepath.ToSlash(filepath.Clean(codePath)) + if !strings.HasPrefix(normalizedPath, NamedPathPrefix) { + return "", normalizedPath, false + } + + withoutPrefix := strings.TrimPrefix(normalizedPath, NamedPathPrefix) + rootName, relativePath, _ := strings.Cut(withoutPrefix, "/") + + return rootName, relativePath, true +} + +// sourceFromRoot builds a source path from a code root and a relative path. +func sourceFromRoot(root _type.NamedPath, relativePath string) (resolvedSource, error) { + rootAbs, err := filepath.Abs(root.Path) + if err != nil { + return resolvedSource{}, err + } + + return resolvedSource{ + root: root, + relativePath: filepath.FromSlash(relativePath), + absolutePath: filepath.Join(rootAbs, filepath.FromSlash(relativePath)), + }, nil +} + +// cachedSourceFragments returns cached source fragmentation for a resolved source file. +func cachedSourceFragments(source resolvedSource, config config.Configuration) (cachedFragmentation, error) { + cacheKey := source.absolutePath + resolverCache.RLock() + content, found := resolverCache.files[cacheKey] + resolverCache.RUnlock() + if found { + return content, nil + } + + fragmentation := NewFragmentation(source.absolutePath, source.root, config) + lines, fragments, err := fragmentation.DoFragmentation() + if err != nil { + return cachedFragmentation{}, err + } + content = cachedFragmentation{ + lines: lines, + fragments: fragments, + } + + resolverCache.Lock() + resolverCache.files[cacheKey] = content + resolverCache.Unlock() + + return content, nil +} + +// fragmentLines renders a fragment into lines. +func fragmentLines(fragment Fragment, lines []string, separator string) []string { + text := fragment.text(lines, separator) + if text == "" { + return []string{} + } + + return strings.Split(strings.TrimSuffix(text, "\n"), "\n") +} + +// unresolvedSourceError builds an error for a code path that cannot be resolved from sources. +func unresolvedSourceError(codePath string, fragmentName string, config config.Configuration) error { + codeFileReference, err := codeFileReference(codePath, config) + if err != nil { + return err + } + if fragmentName == DefaultFragmentName { + return fmt.Errorf("code file `%s` not found", codeFileReference) + } + + return fmt.Errorf("fragment `%s` from code file `%s` not found", + fragmentName, codeFileReference) +} + +// codeFileReference builds a user-facing source reference for unresolved code paths. +func codeFileReference(codePath string, config config.Configuration) (string, error) { + codeRootName, relativePath, named := splitNamedPath(codePath) + for _, root := range config.CodeRoots { + if named && strings.TrimSpace(root.Name) != codeRootName { + continue + } + + source, err := sourceFromRoot(root, relativePath) + if err != nil { + return "", err + } + if named { + return fmt.Sprintf("%s (%s)", codePath, source.absolutePath), nil + } + if len(config.CodeRoots) == 1 { + return source.absolutePath, nil + } + } + + if named { + return "", fmt.Errorf("code root with name `%s` not found for path `%s`", + codeRootName, codePath) + } + + return codePath, nil +} From 0a833d2bed1872e4297dd323a8d510c4bce84907 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 13 May 2026 16:21:59 +0200 Subject: [PATCH 2/4] Remove redundant params. --- README.md | 6 ------ cli/cli.go | 29 ----------------------------- cli/cli_test.go | 6 +++--- cli/cli_validation.go | 4 +--- main.go | 9 +-------- 5 files changed, 5 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index e7a8578..73c6183 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,6 @@ The available arguments are: * `-code-path`: (Optional) Path to the source code root directory. * `-docs-path`: (Optional) Path to the documentation root directory. * `-config-path`: (Optional) Path to a YAML configuration file containing `code-path` and `docs-path`. - * `-code-includes`: (Optional) Comma-separated glob patterns for source files to include (e.g., `"**/*.java,**/*.gradle"`). Defaults to `"**/*.*"`. - * `-code-excludes`: (Optional) Comma-separated glob patterns for source files to exclude. * `-doc-includes`: (Optional) Comma-separated glob patterns for documentation files to include. Defaults to `"**/*.md,**/*.html"`. * `-fragments-path`: (Optional) Directory for storing code fragments. Defaults to `./build/fragments`. * `-separator`: (Optional) String used to separate joined code fragments. Defaults to `...`. @@ -87,7 +85,6 @@ Optional settings can be defined in a YAML configuration file: ```yaml code-path: path/to/code/root docs-path: path/to/docs/root -code-includes: "**/*.java,**/*.gradle" doc-excludes: "**/*-old.*,**/deprecated/*.*" ``` @@ -98,7 +95,6 @@ embeddings: - name: java code-path: path/to/code/root/java docs-path: path/to/java/docs - code-includes: "**/*.java" - name: kotlin code-path: - name: samples @@ -138,8 +134,6 @@ The available fields for the configuration file are: but this may lead to fragments being overwritten if they have the same relative path and name. * `docs-path`: (Mandatory) Path to the documentation root. - * `code-includes`: (Optional) Glob patterns for source files to include. - It may be represented as a comma-separated string list or as a YAML sequence. * `doc-excludes`: (Optional) Glob patterns for documentation files to exclude. It may be represented as a comma-separated string list or as a YAML sequence. * `doc-includes`: (Optional) Glob patterns for documentation files to include. diff --git a/cli/cli.go b/cli/cli.go index a7dd473..9e93231 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -28,7 +28,6 @@ import ( "embed-code/embed-code-go/analyzing" "embed-code/embed-code-go/configuration" "embed-code/embed-code-go/embedding" - "embed-code/embed-code-go/fragmentation" "gopkg.in/yaml.v3" ) @@ -39,12 +38,6 @@ import ( // // BaseDocsPath — a path to a root directory with docs files. // -// CodeIncludes — a StringList with patterns for filtering the code files -// to be considered. -// Directories are never matched by these patterns. -// For example, "**/*.java,**/*.gradle". -// The default value is "**/*.*". -// // DocIncludes — a StringList with patterns for filtering files // in which we should look for embedding instructions. // The patterns are resolved relatively to the `documentation_root`. @@ -73,7 +66,6 @@ import ( type Config struct { BaseCodePaths _type.NamedPathList `yaml:"code-path"` BaseDocsPath string `yaml:"docs-path"` - CodeIncludes _type.StringList `yaml:"code-includes"` DocIncludes _type.StringList `yaml:"doc-includes"` DocExcludes _type.StringList `yaml:"doc-excludes"` FragmentsPath string `yaml:"fragments-path"` @@ -90,7 +82,6 @@ type EmbeddingConfig struct { Name string `yaml:"name"` CodePaths _type.NamedPathList `yaml:"code-path"` DocsPath string `yaml:"docs-path"` - CodeIncludes _type.StringList `yaml:"code-includes"` DocIncludes _type.StringList `yaml:"doc-includes"` DocExcludes _type.StringList `yaml:"doc-excludes"` FragmentsPath string `yaml:"fragments-path"` @@ -99,11 +90,8 @@ type EmbeddingConfig struct { // EmbedCodeSamplesResult is result of the EmbedCodeSamples method. // -// WriteFragmentFilesResult the result of code fragmentation. -// // EmbedAllResult the result of embedding code fragments in the documentation. type EmbedCodeSamplesResult struct { - fragmentation.WriteFragmentFilesResult embedding.EmbedAllResult } @@ -118,7 +106,6 @@ const ( // // config — a configuration for checking code samples. func CheckCodeSamples(config configuration.Configuration) { - fragmentation.WriteFragmentFiles(config) embedding.CheckUpToDate(config) } @@ -126,11 +113,9 @@ func CheckCodeSamples(config configuration.Configuration) { // // config — a configuration for embedding. func EmbedCodeSamples(config configuration.Configuration) EmbedCodeSamplesResult { - fragmentationResult := fragmentation.WriteFragmentFiles(config) embeddingResult := embedding.EmbedAll(config) embedding.CheckUpToDate(config) return EmbedCodeSamplesResult{ - fragmentationResult, embeddingResult, } } @@ -139,9 +124,7 @@ func EmbedCodeSamples(config configuration.Configuration) EmbedCodeSamplesResult // // config — a configuration for embedding. func AnalyzeCodeSamples(config configuration.Configuration) { - fragmentation.WriteFragmentFiles(config) analyzing.AnalyzeAll(config) - fragmentation.CleanFragmentFiles(config) } // ReadArgs reads user-specified args from the command line. @@ -150,8 +133,6 @@ func AnalyzeCodeSamples(config configuration.Configuration) { func ReadArgs() Config { codePath := flag.String("code-path", "", "a path to a root directory with code files") docsPath := flag.String("docs-path", "", "a path to a root directory with docs files") - codeIncludes := flag.String("code-includes", "", - "a comma-separated string of glob patterns for code files to include") docIncludes := flag.String("doc-includes", "", "a comma-separated string of glob patterns for docs files to include") docExcludes := flag.String("doc-excludes", "", @@ -173,7 +154,6 @@ func ReadArgs() Config { return Config{ BaseCodePaths: _type.NamedPathList{_type.NamedPath{Path: *codePath}}, BaseDocsPath: *docsPath, - CodeIncludes: parseListArgument(*codeIncludes), DocIncludes: parseListArgument(*docIncludes), DocExcludes: parseListArgument(*docExcludes), FragmentsPath: *fragmentsPath, @@ -195,9 +175,6 @@ func FillArgsFromConfigFile(args Config) (Config, error) { args.BaseDocsPath = configFields.BaseDocsPath args.BaseCodePaths = configFields.BaseCodePaths - if len(configFields.CodeIncludes) > 0 { - args.CodeIncludes = configFields.CodeIncludes - } if len(configFields.Embeddings) > 0 { args.Embeddings = configFields.Embeddings } @@ -248,9 +225,6 @@ func configFromEmbedding(embedding EmbeddingConfig) configuration.Configuration embedCodeConfig.CodeRoots = embedding.CodePaths embedCodeConfig.DocumentationRoot = embedding.DocsPath - if len(embedding.CodeIncludes) > 0 { - embedCodeConfig.CodeIncludes = embedding.CodeIncludes - } if len(embedding.DocIncludes) > 0 { embedCodeConfig.DocIncludes = embedding.DocIncludes } @@ -272,9 +246,6 @@ func configFromEmbedding(embedding EmbeddingConfig) configuration.Configuration func configWithOptionalParams(userArgs Config) configuration.Configuration { embedCodeConfig := configuration.NewConfiguration() - if len(userArgs.CodeIncludes) > 0 { - embedCodeConfig.CodeIncludes = userArgs.CodeIncludes - } if len(userArgs.DocIncludes) > 0 { embedCodeConfig.DocIncludes = userArgs.DocIncludes } diff --git a/cli/cli_test.go b/cli/cli_test.go index 726acc6..882397f 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -155,9 +155,9 @@ var _ = Describe("CLI validation", func() { It("should fail validation when embeddings and root optional params are set at the same time", func() { invalidConfig := cli.Config{ - Mode: cli.ModeCheck, - CodeIncludes: []string{"**/*.java"}, - Embeddings: []cli.EmbeddingConfig{baseEmbeddingConfig()}, + Mode: cli.ModeCheck, + DocIncludes: []string{"**/*.md"}, + Embeddings: []cli.EmbeddingConfig{baseEmbeddingConfig()}, } Expect(cli.ValidateConfig(invalidConfig)).Error().Should(HaveOccurred()) diff --git a/cli/cli_validation.go b/cli/cli_validation.go index 3880e0b..c996e91 100644 --- a/cli/cli_validation.go +++ b/cli/cli_validation.go @@ -256,14 +256,12 @@ func verifyDuplicateEmbeddingDocsPaths(embeddings []EmbeddingConfig) { // validateOptionalParamsSet reports whether at least one optional config is set. func validateOptionalParamsSet(config Config) bool { - isCodeIncludesSet := len(config.CodeIncludes) > 0 isDocIncludesSet := len(config.DocIncludes) > 0 isDocExcludesSet := len(config.DocExcludes) > 0 isSeparatorSet := isNotEmpty(config.Separator) isFragmentPathSet := isNotEmpty(config.FragmentsPath) - return isCodeIncludesSet || isDocIncludesSet || isFragmentPathSet || - isSeparatorSet || isDocExcludesSet + return isDocIncludesSet || isFragmentPathSet || isSeparatorSet || isDocExcludesSet } // validatePathSet reports whether path is set and checks if it exists. diff --git a/main.go b/main.go index d78aabf..f68115e 100644 --- a/main.go +++ b/main.go @@ -69,9 +69,6 @@ const Version = "1.1.0" // then the checking for up-to-date is performed. If it is set to 'embed', the embedding // is performed. // If it is set to 'analyze', the analyzing is performed; -// - code-includes — a comma-separated string of glob patterns for code files to include. -// For example: -// "**/*.java,**/*.gradle". Default value is "**/*.*"; // - doc-includes — a comma-separated string of glob patterns for docs files to include. // For example: // "docs/**/*.md,guides/*.html". Default value is "**/*.md,**/*.html"; @@ -146,16 +143,12 @@ func logError(message string, err error) { func embedByConfigs(configs []configuration.Configuration) { var totalEmbeddedFiles []string totalEmbeddings := 0 - totalFragments := 0 for _, config := range configs { result := cli.EmbedCodeSamples(config) totalEmbeddedFiles = append(totalEmbeddedFiles, result.UpdatedTargetFiles...) totalEmbeddings += result.TotalEmbeddings - totalFragments += result.TotalFragments } - if len(totalEmbeddedFiles) == 0 && - totalEmbeddings != 0 && - totalFragments != 0 { + if len(totalEmbeddedFiles) == 0 && totalEmbeddings != 0 { fmt.Println("All documentation files are already up to date. Nothing to update.") } if len(totalEmbeddedFiles) == 1 { From 55be0c08a857bcdd5f73f8c15d164db614420369 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 13 May 2026 16:22:32 +0200 Subject: [PATCH 3/4] Update tests. --- configuration/configuration.go | 11 - embedding/embedding_test.go | 22 +- embedding/parsing/instruction_test.go | 3 +- fragmentation/fragmentation_test.go | 245 +++++------------- .../code/java/plain-text-to-embed.txt | 7 + 5 files changed, 89 insertions(+), 199 deletions(-) create mode 100644 test/resources/code/java/plain-text-to-embed.txt diff --git a/configuration/configuration.go b/configuration/configuration.go index 133a533..0e79f50 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -28,7 +28,6 @@ const ( DefaultFragmentsDir = "./build/fragments" ) -var DefaultInclude = []string{"**/*.*"} var DefaultDocIncludes = []string{"**/*.md", "**/*.html"} // Configuration contains the settings for the plugin to work. @@ -53,15 +52,6 @@ type Configuration struct { // DocumentationRoot is a root directory of the documentation files. DocumentationRoot string - // CodeIncludes is a list of patterns for filtering the code files to be considered. - // - // Directories are never matched by these patterns. - // - // For example, ["**/*.java", "**/*.gradle"]. - // - // The default value is "**/*". - CodeIncludes []string - // DocIncludes is a list of patterns for filtering files in which we should look for embedding // instructions. // @@ -101,7 +91,6 @@ type Configuration struct { // NewConfiguration builds the default config. func NewConfiguration() Configuration { return Configuration{ - CodeIncludes: DefaultInclude, DocIncludes: DefaultDocIncludes, FragmentsDir: DefaultFragmentsDir, Separator: DefaultSeparator, diff --git a/embedding/embedding_test.go b/embedding/embedding_test.go index 0463ef7..3704fb6 100644 --- a/embedding/embedding_test.go +++ b/embedding/embedding_test.go @@ -107,6 +107,23 @@ var _ = Describe("Embedding", func() { Expect(processor.IsUpToDate()).Should(BeTrue()) }) + It("should embed directly from source without writing fragment files", func() { + config.CodeRoots = _type.NamedPathList{_type.NamedPath{Path: "../test/resources/code/java"}} + config.FragmentsDir = "../test/lazy-fragments" + if err := os.RemoveAll(config.FragmentsDir); err != nil { + Fail(err.Error()) + } + docPath := fmt.Sprintf("%s/doc.md", config.DocumentationRoot) + processor := embedding.NewProcessor(docPath, config) + + Expect(processor.Embed()).Error().ShouldNot(HaveOccurred()) + exists, err := files.IsDirExist(config.FragmentsDir) + + Expect(err).ShouldNot(HaveOccurred()) + Expect(exists).Should(BeFalse()) + 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) @@ -160,7 +177,7 @@ var _ = Describe("Embedding", func() { Expect(processor.IsUpToDate()).Should(BeTrue()) }) - It("should not embed to a file matched the `code-excludes` pattern", func() { + It("should not embed to a file matched the `doc-excludes` pattern", func() { config.DocExcludes = []string{"**/excluded-doc.*"} docPath := fmt.Sprintf("%s/excluded-doc.md", config.DocumentationRoot) @@ -174,8 +191,7 @@ var _ = Describe("Embedding", func() { func buildConfigWithPreparedFragments() configuration.Configuration { var config = configuration.NewConfiguration() config.DocumentationRoot = temporaryTestDir - config.CodeRoots = _type.NamedPathList{_type.NamedPath{Path: "../test/resources/code"}} - config.FragmentsDir = "../test/resources/prepared-fragments" + config.CodeRoots = _type.NamedPathList{_type.NamedPath{Path: "../test/resources/code/java"}} return config } diff --git a/embedding/parsing/instruction_test.go b/embedding/parsing/instruction_test.go index b70bad9..046207e 100644 --- a/embedding/parsing/instruction_test.go +++ b/embedding/parsing/instruction_test.go @@ -301,8 +301,7 @@ func getXMLExtractionContent(fileName string, params TestInstructionParams, func buildConfigWithPreparedFragments() configuration.Configuration { var config = configuration.NewConfiguration() config.DocumentationRoot = "../../test/resources/docs" - config.CodeRoots = _type.NamedPathList{_type.NamedPath{Path: "../../test/resources/code"}} - config.FragmentsDir = "../../test/resources/prepared-fragments" + config.CodeRoots = _type.NamedPathList{_type.NamedPath{Path: "../../test/resources/code/java"}} return config } diff --git a/fragmentation/fragmentation_test.go b/fragmentation/fragmentation_test.go index e529773..d9e6440 100644 --- a/fragmentation/fragmentation_test.go +++ b/fragmentation/fragmentation_test.go @@ -20,13 +20,9 @@ package fragmentation_test import ( "embed-code/embed-code-go/configuration" - "embed-code/embed-code-go/files" "embed-code/embed-code-go/fragmentation" _type "embed-code/embed-code-go/type" "fmt" - "os" - "path/filepath" - "regexp" "testing" . "github.com/onsi/ginkgo/v2" @@ -53,112 +49,57 @@ var _ = Describe("Fragmentation", func() { var config configuration.Configuration BeforeEach(func() { + fragmentation.ClearResolverCache() config = configuration.NewConfiguration() config.DocumentationRoot = "../test/resources/docs" config.CodeRoots = _type.NamedPathList{_type.NamedPath{Path: "../test/resources/code/java"}} }) - AfterEach(func() { - cleanupDir(config.FragmentsDir) - }) - - It("should do file fragmentation successfully", func() { - frag := buildTestFragmentation(correctFragmentsFileName, config) - Expect(frag.WriteFragments()).Error().ShouldNot(HaveOccurred()) - - fragmentChild, _ := os.ReadDir(config.FragmentsDir) - Expect(fragmentChild).Should(HaveLen(1)) - Expect(fragmentChild[0].Name()).Should(Equal("org")) - - fragmentFiles := readFragmentsDir(config) - Expect(fragmentFiles).Should(HaveLen(4)) + It("should fragment a file in memory", func() { + lines, fragments := doTestFragmentation(correctFragmentsFileName, config) - var isDefaultFragmentExist bool - for _, file := range fragmentFiles { - if file.Name() == correctFragmentsFileName { - isDefaultFragmentExist = true - } else { - Expect(file.Name()).Should(MatchRegexp(`Hello-\w+\.java`)) - } - } - - Expect(isDefaultFragmentExist).Should(BeTrue()) + Expect(lines).ShouldNot(ContainElement(ContainSubstring("#docfragment"))) + Expect(lines).ShouldNot(ContainElement(ContainSubstring("#enddocfragment"))) + Expect(fragments).Should(HaveKey(fragmentation.DefaultFragmentName)) + Expect(fragments).Should(HaveKey("Without License")) + Expect(fragments).Should(HaveKey("Hello class")) + Expect(fragments).Should(HaveKey("main()")) }) - It("should do multi-source fragmentation successfully", func() { - config := configuration.NewConfiguration() - config.DocumentationRoot = "../test/resources/docs" - javaCodePathName := "java-code" - kotlinCodePathName := "kotlin-code" - config.CodeRoots = _type.NamedPathList{ - _type.NamedPath{ - Name: javaCodePathName, - Path: "../test/resources/code/java/org/example/multitest", - }, - _type.NamedPath{ - Name: kotlinCodePathName, - Path: "../test/resources/code/kotlin/org/example/multitest", - }, - } - result := fragmentation.WriteFragmentFiles(config) - Expect(result.TotalSourceFiles).Should(Equal(2)) - javaFragments, _ := os.ReadDir( - filepath.Join(config.FragmentsDir, fragmentation.NamedPathPrefix+javaCodePathName), - ) - kotlinFragments, _ := os.ReadDir( - filepath.Join(config.FragmentsDir, fragmentation.NamedPathPrefix+kotlinCodePathName), - ) - Expect(javaFragments).Should(HaveLen(2)) - Expect(kotlinFragments).Should(HaveLen(2)) - }) - - It("should do fragmentation of a fragment without end", func() { - frag := buildTestFragmentation(unclosedFragmentFileName, config) - Expect(frag.WriteFragments()).Error().ShouldNot(HaveOccurred()) - - fragmentFiles := readFragmentsDir(config) - Expect(fragmentFiles).Should(HaveLen(2)) + It("should resolve named fragments directly from source", func() { + content := resolveTestFragment(correctFragmentsFileName, "main()", config) - fragmentFileName := findFragmentFile(fragmentFiles, unclosedFragmentFileName) - fragmentsDir := fragmentsDirPath(config.FragmentsDir) - content, err := os.ReadFile(filepath.Join(fragmentsDir, fragmentFileName)) - if err != nil { - Fail(err.Error()) - } - - re := regexp.MustCompile(`[.\n\s]+}\n}\n`) - matchedStrings := re.FindStringSubmatch(string(content)) - - Expect(matchedStrings).Should(Not(BeEmpty())) + Expect(content).Should(Equal([]string{ + "public static void main(String[] args) {", + indent + "System.out.println(\"Hello world\");", + "}", + })) }) - It("should not do fragmentation of an empty file", func() { - frag := buildTestFragmentation(emptyFileName, config) - Expect(frag.WriteFragments()).Error().ShouldNot(HaveOccurred()) - - fragmentFiles := readFragmentsDir(config) - Expect(fragmentFiles).Should(HaveLen(1)) - fragmentsFilePath := fragmentsDirPath(config.FragmentsDir) + "/" + fragmentFiles[0].Name() - - content, err := os.ReadFile(fragmentsFilePath) - if err != nil { - Fail(err.Error()) - } + It("should resolve fragments without an end marker through the end of the file", func() { + content := resolveTestFragment(unclosedFragmentFileName, "Fragment that never ends", config) - Expect(content).Should(BeEmpty()) + Expect(content).Should(Equal([]string{ + indent + indent + "System.out.println(\"Hello world\");", + indent + "}", + "}", + })) }) - It("should not do fragmentation of a binary file", func() { - config.CodeIncludes = []string{"**.jar"} + It("should fragment an empty file", func() { + lines, fragments := doTestFragmentation(emptyFileName, config) - Expect(fragmentation.WriteFragmentFiles(config).TotalFragments).Should(Equal(0)) - Expect(files.IsDirExist(config.FragmentsDir)).Should(BeFalse()) + Expect(lines).Should(BeEmpty()) + Expect(fragments).Should(HaveLen(1)) + Expect(fragments).Should(HaveKey(fragmentation.DefaultFragmentName)) }) - It("should not do fragmentation of an unopened fragment", func() { + It("should fail on an unopened fragment", func() { frag := buildTestFragmentation(unopenedFragmentFileName, config) - Expect(frag.WriteFragments()).Error().Should(HaveOccurred()) + _, _, err := frag.DoFragmentation() + + Expect(err).Should(HaveOccurred()) }) Context("fragments parsing", func() { @@ -203,20 +144,7 @@ var _ = Describe("Fragmentation", func() { }) It("should correctly parse file into many partitions", func() { - frag := buildTestFragmentation(complexFragmentsFileName, config) - _, err := frag.WriteFragments() - Expect(err).ToNot(HaveOccurred()) - - fragmentFiles := readFragmentsDir(config) - Expect(fragmentFiles).Should(HaveLen(2)) - - fragmentFileName := findFragmentFile(fragmentFiles, complexFragmentsFileName) - fragmentDir := fragmentsDirPath(config.FragmentsDir) - - content, err := files.ReadFile(fmt.Sprintf("%s/%s", fragmentDir, fragmentFileName)) - if err != nil { - Fail(err.Error()) - } + content := resolveTestFragment(complexFragmentsFileName, "Main", config) expected := []string{ "public class Main {", @@ -233,17 +161,10 @@ var _ = Describe("Fragmentation", func() { }) It("should correctly parse file with several different fragments", func() { - frag := buildTestFragmentation(twoFragmentsFileName, config) - _, err := frag.WriteFragments() - Expect(err).ToNot(HaveOccurred()) - - fragmentFiles := readFragmentsDir(config) - Expect(fragmentFiles).Should(HaveLen(3)) + mainContent := resolveTestFragment(twoFragmentsFileName, "Main", config) + helloContent := resolveTestFragment(twoFragmentsFileName, "Hello", config) - fragmentDir := fragmentsDirPath(config.FragmentsDir) - actual := readFragmentsContent(fragmentDir, fragmentFiles, twoFragmentsFileName) - - expected := [][]string{ + Expect([][]string{mainContent, helloContent}).Should(ConsistOf([][]string{ { "public class TwoFragments {", indent + config.Separator, @@ -262,23 +183,14 @@ var _ = Describe("Fragmentation", func() { indent + "System.out.println(coolText);", "}", }, - } - - Expect(actual).Should(ConsistOf(expected)) + })) }) It("should correctly parse file with several overlapping fragments", func() { - frag := buildTestFragmentation(overlappingFragmentsFileName, config) - _, err := frag.WriteFragments() - Expect(err).ToNot(HaveOccurred()) + mainContent := resolveTestFragment(overlappingFragmentsFileName, "Main", config) + helloContent := resolveTestFragment(overlappingFragmentsFileName, "Hello", config) - fragmentFiles := readFragmentsDir(config) - Expect(fragmentFiles).Should(HaveLen(3)) - - fragmentDir := fragmentsDirPath(config.FragmentsDir) - actual := readFragmentsContent(fragmentDir, fragmentFiles, overlappingFragmentsFileName) - - expected := [][]string{ + Expect([][]string{mainContent, helloContent}).Should(ConsistOf([][]string{ { "public class OverlappingFragments {", indent + config.Separator, @@ -301,9 +213,7 @@ var _ = Describe("Fragmentation", func() { config.Separator, "}", }, - } - - Expect(actual).Should(ConsistOf(expected)) + })) }) }) @@ -315,60 +225,29 @@ func buildTestFragmentation(testFileName string, return fragmentation.NewFragmentation(testFilePath, codeRoot, config) } -func readFragmentsDir(config configuration.Configuration) []os.DirEntry { - fragmentFiles, err := os.ReadDir(fragmentsDirPath(config.FragmentsDir)) - if err != nil { - Fail(err.Error()) - } - - return fragmentFiles -} - -// readFragmentsContent reads the contents of fragment files from a given directory. -// -// fragmentDir — path to the directory containing fragment files. -// fragmentFiles — list of directory entries representing the fragment files. -// skipFile — file name to skip (the default fragment file). -// -// Returns a slice of string slices, where each inner slice contains the lines -// of a fragment file. -// -// This function fails the test immediately if any file cannot be read. -func readFragmentsContent( - fragmentDir string, fragmentFiles []os.DirEntry, skipFile string, -) [][]string { - var result [][]string - for _, file := range fragmentFiles { - if file.Name() == skipFile { - continue - } - - content, err := files.ReadFile(fmt.Sprintf("%s/%s", fragmentDir, file.Name())) - Expect(err).ShouldNot(HaveOccurred()) - - result = append(result, content) - } - - return result -} - -func fragmentsDirPath(path string) string { - return fmt.Sprintf("%s/org/example", path) -} +func doTestFragmentation( + testFileName string, + config configuration.Configuration, +) ([]string, map[string]fragmentation.Fragment) { + frag := buildTestFragmentation(testFileName, config) -func findFragmentFile(files []os.DirEntry, fileName string) string { - for _, file := range files { - if file.Name() != fileName { - return file.Name() - } - } + lines, fragments, err := frag.DoFragmentation() - return "" + Expect(err).ShouldNot(HaveOccurred()) + return lines, fragments } -func cleanupDir(dirPath string) { - err := os.RemoveAll(dirPath) - if err != nil { - Fail(err.Error()) - } +func resolveTestFragment( + testFileName string, + fragmentName string, + config configuration.Configuration, +) []string { + content, err := fragmentation.ResolveContent( + fmt.Sprintf("org/example/%s", testFileName), + fragmentName, + config, + ) + + Expect(err).ShouldNot(HaveOccurred()) + return content } diff --git a/test/resources/code/java/plain-text-to-embed.txt b/test/resources/code/java/plain-text-to-embed.txt new file mode 100644 index 0000000..dc2bec0 --- /dev/null +++ b/test/resources/code/java/plain-text-to-embed.txt @@ -0,0 +1,7 @@ +This line contains foo in the middle +This line ends with foo +foo — this line starts with it + +This line contains bar in the middle +bar — this line starts with it +This line ends with bar From c024b521401ec407de31aea7546c1d93ba3c61a4 Mon Sep 17 00:00:00 2001 From: Vladyslav Kuksiuk Date: Wed, 13 May 2026 16:33:21 +0200 Subject: [PATCH 4/4] Update version. --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index cf651c1..210065d 100644 --- a/main.go +++ b/main.go @@ -28,7 +28,7 @@ import ( ) // Version of the embed-code application. -const Version = "1.1.1" +const Version = "1.2.0" // The entry point for embed-code. //