diff --git a/.agents/skills/go-engineer/SKILL.md b/.agents/skills/go-engineer/SKILL.md index 6735cfe0..720d3e8a 100644 --- a/.agents/skills/go-engineer/SKILL.md +++ b/.agents/skills/go-engineer/SKILL.md @@ -83,9 +83,11 @@ first function that exposes the symptom. `fragmentation` -> write or compare. - **Keep functions small and explicit.** Prefer direct code over broad helpers unless an abstraction removes real duplication or clarifies a shared contract. -- **Document every function and method.** Add concise doc comments to new or - changed functions, including unexported ones. Exported comments start with - the declaration name. +- **Document functions, methods, and struct fields.** Add concise doc comments + to new or changed functions, including unexported ones. Document every field + of a named struct directly above its declaration, including unexported fields; + keep the struct comment focused on the type as a whole. Exported comments + start with the declaration name. - **Return actionable errors.** Add file, pattern, instruction, or operation context where that context becomes known. - **Aggregate independent failures with `errors.Join`.** Continue processing diff --git a/.agents/skills/go-tester/SKILL.md b/.agents/skills/go-tester/SKILL.md index e7d3ce48..d92c9ac2 100644 --- a/.agents/skills/go-tester/SKILL.md +++ b/.agents/skills/go-tester/SKILL.md @@ -9,7 +9,7 @@ description: > helper code. --- -# Go tester +# Go Testing This skill is the single source of truth for how tests are written in `embed-code-go`. It does not decide what behavior to change; it decides how to diff --git a/.agents/skills/review-docs/SKILL.md b/.agents/skills/review-docs/SKILL.md index d020f45d..6b06aa42 100644 --- a/.agents/skills/review-docs/SKILL.md +++ b/.agents/skills/review-docs/SKILL.md @@ -9,7 +9,7 @@ description: > builds unless explicitly asked. --- -# Review documentation (repo-specific) +# Documentation Review You are the documentation reviewer for `embed-code-go`. Focus strictly on documentation quality: Go doc comments, inline comments, Markdown, examples, @@ -38,6 +38,10 @@ and repository guidance. Do not duplicate `writer` for authoring strategy, Go doc style for exported types, constants, variables, functions, and methods. - **Unexported comments explain intent.** Starting with the function name is preferred when it reads naturally. +- **Every named struct field is documented in place.** Require a comment + directly above each exported or unexported field, starting with the exact + field name. Keep the struct comment focused on the type and reject field + descriptions collected there or duplicated at struct literals. - **Comments describe behavior, not signatures.** Avoid prose that only restates parameters, return values, or obvious assignments. - **Mention important effects.** Document filesystem writes, state transitions, diff --git a/.agents/skills/writer/SKILL.md b/.agents/skills/writer/SKILL.md index d8ef5523..a7c62cd7 100644 --- a/.agents/skills/writer/SKILL.md +++ b/.agents/skills/writer/SKILL.md @@ -8,7 +8,7 @@ description: > current Go code, tests, fixtures, and project flows. --- -# Write documentation +# Documentation Writing ## Decide the Target and Audience @@ -74,12 +74,50 @@ Prefer updating an existing document over creating a new one. - Exported comments start with the exact declaration name. - Unexported comments state intent and start with the function name when it reads naturally. +- Document named types, interfaces, exported constants, exported variables, + functions, methods, and struct fields. For unexported declarations, preserve + existing documentation and add comments when the declaration is new, changed, + non-obvious, or part of a local contract. +- Document every field of a named struct directly above the field declaration, + including unexported fields. Start with the exact field name and use normal + Go prose such as `FieldName is...` or `fieldName contains...`. +- Keep struct comments focused on the type as a whole. Do not collect field + descriptions in the struct comment or duplicate them at struct literals. - Document non-obvious state transitions, filesystem writes, returned errors, panics, and parser constraints. +- Preserve existing parameter, return, and author/explanatory comments. When + restyling, convert the content to project style instead of deleting it, even + for private functions. - Do not restate the signature or narrate obvious assignments. - Inline comments in production Go should explain why a constraint exists, not what the next line does. +## Go Function Comment Style + +Use this structure for functions and methods when parameters or returns need +documentation: + +```go +// EmbedAll embeds code fragments into all documentation files selected by config. +// +// Parameters: +// config - provides embedding configuration. +// +// Returns: +// EmbedAllResult - embedding result. +// error - when selected documents fail to process. +``` + +- Keep the opening sentence short and behavioral. +- Use `Parameters:` when documenting parameters. Each line is + ` - `. +- Use `Returns:` only when there are multiple return values. Each line is + ` - `. +- For a single return value, use one sentence such as + `Returns parsed configuration.` +- Keep existing examples, constraints, and behavior notes, but normalize bullets + and parameter descriptions to this style. + ## Make Docs Actionable - Prefer executable steps, expected outcomes, and concrete examples over broad descriptions. diff --git a/README.md b/README.md index 831c3c84..7f070e0a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ failures, and runnable examples. ## Run -Download the asset for your platform from [GitHub Releases][releases]. +Download the asset for your platform from [GitHub Releases][releases]. On Linux, for example: diff --git a/cli/cli.go b/cli/cli.go index 7147ddc2..38cb4d78 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -33,79 +33,97 @@ import ( "gopkg.in/yaml.v3" ) -// Config — user-specified embed-code configurations. -// -// BaseCodePaths — a NamedPathList to directories with code files. -// -// BaseDocsPath — a path to a root directory with docs files. -// -// 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`. -// Directories are never matched by these patterns. -// For example, "docs/**/*.md,guides/*.html". -// The default value is "**/*.md,**/*.html". -// -// DocExcludes - a StringList with patterns for filtering documentation files -// which should be excluded from the embedding process. -// -// Separator — a string that's inserted between multiple partitions of a single fragment. -// The default value is "...". -// -// Embeddings — independent configurations for embedding multiple documentation targets. -// -// Info - specifies whether info-level logs should be shown. -// -// Stacktrace - specifies whether error stack traces should be shown. -// -// ConfigPath — a path to a yaml configuration file which contains roots or embeddings. -// -// Mode — defines the mode of embed-code execution. +// Config contains user-specified embed-code settings. type Config struct { + // BaseCodePaths contains directories with source code files. BaseCodePaths _type.NamedPathList `yaml:"code-path"` - BaseDocsPath string `yaml:"docs-path"` - DocIncludes _type.StringList `yaml:"doc-includes"` - DocExcludes _type.StringList `yaml:"doc-excludes"` - Separator string `yaml:"separator"` - Embeddings []EmbeddingConfig `yaml:"embeddings"` - Info bool `yaml:"info"` - Stacktrace bool `yaml:"stacktrace"` - ConfigPath string - Mode string + + // BaseDocsPath is the root directory containing documentation files. + BaseDocsPath string `yaml:"docs-path"` + + // DocIncludes contains patterns selecting documentation files to process. + // Patterns are resolved relative to the documentation root. + // For example, "docs/**/*.md,guides/*.html". The default is "**/*.md,**/*.html". + DocIncludes _type.StringList `yaml:"doc-includes"` + + // DocExcludes contains patterns selecting documentation files to skip. + DocExcludes _type.StringList `yaml:"doc-excludes"` + + // Separator is inserted between multiple partitions of one fragment. + // The default is "...". + Separator string `yaml:"separator"` + + // Embeddings contains independent embedding target configurations. + Embeddings []EmbeddingConfig `yaml:"embeddings"` + + // Info reports whether info-level logs should be shown. + Info bool `yaml:"info"` + + // Stacktrace reports whether panic stack traces should be shown. + Stacktrace bool `yaml:"stacktrace"` + + // ConfigPath is the path to the YAML configuration file. + ConfigPath string + + // Mode selects check or embed execution. + Mode string } // EmbeddingConfig contains a complete configuration for one embedding target. type EmbeddingConfig struct { - Name string `yaml:"name"` - CodePaths _type.NamedPathList `yaml:"code-path"` - DocsPath string `yaml:"docs-path"` - DocIncludes _type.StringList `yaml:"doc-includes"` - DocExcludes _type.StringList `yaml:"doc-excludes"` - Separator string `yaml:"separator"` + // Name identifies the embedding target. + Name string `yaml:"name"` + + // CodePaths contains directories with source code files. + CodePaths _type.NamedPathList `yaml:"code-path"` + + // DocsPath is the root directory containing documentation files. + DocsPath string `yaml:"docs-path"` + + // DocIncludes contains patterns selecting documentation files to process. + DocIncludes _type.StringList `yaml:"doc-includes"` + + // DocExcludes contains patterns selecting documentation files to skip. + DocExcludes _type.StringList `yaml:"doc-excludes"` + + // Separator is inserted between multiple partitions of one fragment. + Separator string `yaml:"separator"` } -// EmbedCodeSamplesResult is result of the EmbedCodeSamples method. -// -// EmbedAllResult the result of embedding code fragments in the documentation. +// EmbedCodeSamplesResult contains the result of an EmbedCodeSamples operation. type EmbedCodeSamplesResult struct { + // EmbedAllResult contains the underlying embedding result. embedding.EmbedAllResult } const ( + // ModeCheck checks whether documentation snippets are up-to-date. ModeCheck = "check" + + // ModeEmbed rewrites documentation snippets from source code. ModeEmbed = "embed" ) // CheckCodeSamples returns documentation files that are not up-to-date with code files. // -// config — a configuration for checking code samples. +// Parameters: +// config - provides embedding configuration. +// +// Returns: +// []string - stale documentation file paths. +// error - when selected documents fail to process. func CheckCodeSamples(config configuration.Configuration) ([]string, error) { return embedding.CheckUpToDate(config) } // EmbedCodeSamples embeds code fragments in documentation files. // -// config — a configuration for embedding. +// Parameters: +// config - provides embedding configuration. +// +// Returns: +// EmbedCodeSamplesResult - embedding result. +// error - when selected documents fail to process or write. func EmbedCodeSamples(config configuration.Configuration) (EmbedCodeSamplesResult, error) { embeddingResult, err := embedding.EmbedAll(config) if err != nil { @@ -117,9 +135,9 @@ func EmbedCodeSamples(config configuration.Configuration) (EmbedCodeSamplesResul }, nil } -// ReadArgs reads user-specified args from the command line. +// ReadArgs reads user-specified command-line args. // -// Returns Config struct filled with the corresponding args. +// Returns command-line 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") @@ -152,11 +170,14 @@ func ReadArgs() Config { } } -// FillArgsFromConfigFile fills config with the values read from config file. +// FillArgsFromConfigFile fills args with values read from the configured YAML file. // -// args — Config struct with user-provided args. +// Parameters: +// args - provides the config file path and command-line defaults. // -// Returns filled Config. +// Returns: +// Config - merged configuration. +// error - when the YAML file cannot be read or decoded. func FillArgsFromConfigFile(args Config) (Config, error) { configFields, err := readConfigFields(args.ConfigPath) if err != nil { @@ -187,9 +208,12 @@ func FillArgsFromConfigFile(args Config) (Config, error) { return args, nil } -// BuildEmbedCodeConfiguration generates and returns a configuration based on provided userArgs. +// BuildEmbedCodeConfiguration builds normalized embedding configurations from user args. // -// userArgs — a Config with user-provided args. +// Parameters: +// userArgs - provides command-line and YAML configuration values. +// +// Returns normalized embedding configurations. func BuildEmbedCodeConfiguration(userArgs Config) []configuration.Configuration { embedCodeConfigs := make([]configuration.Configuration, 0) @@ -278,7 +302,12 @@ func sourceFoldersLabel(paths _type.NamedPathList) string { return "Source code folders: " + strings.Join(labels, ", ") } -// parseListArgument returns a list of strings from given comma-separated string listArgument. +// parseListArgument splits a comma-separated command-line argument. +// +// Parameters: +// listArgument - provides a comma-separated string. +// +// Returns parsed non-empty values. func parseListArgument(listArgument string) []string { splitArgs := strings.Split(listArgument, ",") parsedArgs := make([]string, 0) @@ -291,11 +320,14 @@ func parseListArgument(listArgument string) []string { return parsedArgs } -// readConfigFields reads the provided config file and returns parsed fields. +// readConfigFields reads and parses a YAML configuration file. // -// configFilePath — a path to a yaml configuration file. +// Parameters: +// configFilePath - provides the path to a YAML configuration file. // -// Returns a filled ConfigFields struct. +// Returns: +// Config - parsed configuration fields. +// error - when the file cannot be read or decoded. func readConfigFields(configFilePath string) (Config, error) { content, err := os.ReadFile(configFilePath) if err != nil { diff --git a/cli/cli_test.go b/cli/cli_test.go index 4eb4ef40..57ecc37d 100644 --- a/cli/cli_test.go +++ b/cli/cli_test.go @@ -1,22 +1,20 @@ -/* - * 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. - */ +// 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 cli_test @@ -31,6 +29,7 @@ import ( . "github.com/onsi/gomega" ) +// TestCli runs the CLI test suite. func TestCli(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Data Suite") @@ -247,6 +246,7 @@ var _ = Describe("CLI validation", func() { }) +// baseCliConfig returns the default valid CLI config used by validation specs. func baseCliConfig() cli.Config { currentDir, err := os.Getwd() if err != nil { @@ -261,6 +261,7 @@ func baseCliConfig() cli.Config { } } +// baseEmbeddingConfig returns the default valid multi-target embedding config. func baseEmbeddingConfig() cli.EmbeddingConfig { baseConfig := baseCliConfig() @@ -271,6 +272,7 @@ func baseEmbeddingConfig() cli.EmbeddingConfig { } } +// configFilePath returns the path to a valid YAML config fixture. func configFilePath() string { currentDir, err := os.Getwd() if err != nil { diff --git a/cli/cli_validation.go b/cli/cli_validation.go index a9fb8288..4cc91d0a 100644 --- a/cli/cli_validation.go +++ b/cli/cli_validation.go @@ -1,22 +1,20 @@ -/* - * 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. - */ +// 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 cli @@ -35,14 +33,21 @@ import ( const IllegalFolderNameChars = `/\ *?:"<>|` // IsUsingConfigFile reports whether user configs are set with file. +// +// Parameters: +// config - provides user CLI settings. +// +// Returns true when ConfigPath is not empty. func IsUsingConfigFile(config Config) bool { return isNotEmpty(config.ConfigPath) } -// ValidateConfig checks the validity of provided config and returns an error if any of the -// validation rules are broken. If everything is ok, returns nil. +// ValidateConfig checks user args and returns the first validation error. +// +// Parameters: +// config - provides user CLI or YAML settings. // -// config — a struct with user-provided args. +// Returns an error when mode or path settings are invalid. func ValidateConfig(config Config) error { err := validateMode(config.Mode) if err != nil { @@ -52,14 +57,15 @@ func ValidateConfig(config Config) error { return validateConfig(config) } -// ValidateConfigFile performs several checks to ensure that the necessary configuration values are -// present. Also checks for the existence of the config file. +// ValidateConfigFile checks that config-file mode is used correctly. // -// userConfig — is a config given from CLI. +// Parameters: +// userConfig - provides command-line settings before YAML loading. // -// Returns an error with a validation message. If everything is ok, returns nil. +// Returns an error when config-file mode is invalid or the file is missing. func ValidateConfigFile(userConfig Config) error { - // Configs should be read from file, verifying if they are not set already. + // Config values should be read from file, so other root or optional params + // must not be set already. isCodePathSet := len(userConfig.BaseCodePaths) > 0 && isNotEmpty(userConfig.BaseCodePaths[0].Path) isDocsPathSet := isNotEmpty(userConfig.BaseDocsPath) @@ -261,7 +267,7 @@ func validatePathSet(path string) (bool, error) { if isPathSet { exists, err := files.IsDirExist(path) if err != nil { - // Since the path is set, returning true even we have an error. + // Since the path is set, return true even when the path check fails. return true, err } if !exists { @@ -274,11 +280,16 @@ func validatePathSet(path string) (bool, error) { return false, nil } -// validatePaths reports whether all paths are valid. +// validatePaths reports whether all paths are set and valid. +// +// It checks whether each provided path exists in the file system. // -// If paths are provided, checks whether each path exists in the file system. +// Parameters: +// paths - provides source paths to validate. // -// Returns an error if any path name is not a valid folder name. +// Returns: +// bool - whether all paths are set. +// error - when any path does not exist or any path name is invalid. func validatePaths(paths _type.NamedPathList) (bool, error) { allPathsSet := true if len(paths) == 0 { diff --git a/configuration/configuration.go b/configuration/configuration.go index 6ac88fb3..d9dd93b2 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -16,7 +16,7 @@ // (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 configuration contains configuration of the plugin. +// Package configuration contains normalized embed-code settings. package configuration import ( @@ -31,7 +31,7 @@ const ( // DefaultDocIncludes contains the default documentation glob patterns. var DefaultDocIncludes = []string{"**/*.md", "**/*.html"} -// Configuration contains the settings for the plugin to work. +// Configuration contains embed-code processing settings. // // It is used to get data for scanning docs and resolving source files. // The example of creating the Configuration with default values: @@ -78,6 +78,8 @@ type Configuration struct { } // NewConfiguration builds the default config. +// +// Returns configuration with default include patterns and separator. func NewConfiguration() Configuration { return Configuration{ DocIncludes: DefaultDocIncludes, diff --git a/embedding/commentfilter/config.go b/embedding/commentfilter/config.go index 0bf81515..01600515 100644 --- a/embedding/commentfilter/config.go +++ b/embedding/commentfilter/config.go @@ -83,7 +83,10 @@ var filtersByExtension = map[string]filterEntry{ // filterEntry stores a comment filter and supported modes for its language. type filterEntry struct { - filter CommentFilter + // filter implements comment removal for the language. + filter CommentFilter + + // supportedModes contains comment modes accepted for the language. supportedModes []Mode } diff --git a/embedding/commentfilter/filter.go b/embedding/commentfilter/filter.go index 63ab5ec9..b29eca47 100644 --- a/embedding/commentfilter/filter.go +++ b/embedding/commentfilter/filter.go @@ -28,17 +28,38 @@ import ( // EmbeddingCommentFilter filters comments for one embed-code instruction. type EmbeddingCommentFilter struct { - filePath string + // filePath is the path to the source code file. + filePath string + + // embeddingDocPath is the path to the documentation containing the instruction. embeddingDocPath string - embeddingLine int + + // embeddingLine is the line containing the embedding instruction. + embeddingLine int } // CommentFilter strips source comments according to the requested mode. type CommentFilter interface { + // Filter removes or preserves comments in lines according to mode. + // + // Parameters: + // lines - provides source lines. + // mode - selects comments to retain. + // + // Returns filtered source lines. Filter(lines []string, mode Mode) []string } // Filter returns source lines with comments stripped according to the requested mode. +// +// Parameters: +// lines - provides source lines. +// filePath - selects the language filter. +// mode - selects comments to retain. +// embeddingDocPath - identifies the instruction document for warnings. +// embeddingLine - identifies the instruction line for warnings. +// +// Returns filtered source lines. func Filter( lines []string, filePath string, @@ -56,6 +77,12 @@ func Filter( } // Filter strips comments using the filter registered in the filtersByExtension. +// +// Parameters: +// lines - provides source lines. +// mode - selects comments to retain. +// +// Returns filtered source lines, or original lines when filtering is unsupported or disabled. func (f EmbeddingCommentFilter) Filter(lines []string, mode Mode) []string { if mode == RetainAll { return lines diff --git a/embedding/commentfilter/marker_comment_filter.go b/embedding/commentfilter/marker_comment_filter.go index a3069239..8f49222f 100644 --- a/embedding/commentfilter/marker_comment_filter.go +++ b/embedding/commentfilter/marker_comment_filter.go @@ -22,46 +22,86 @@ import "strings" // BlockMarker describes a block comment marker pair. type BlockMarker struct { + // Start is the block comment opening marker. Start string - End string + + // End is the block comment closing marker. + End string } // DocumentationMarker describes API documentation comment markers. type DocumentationMarker struct { + // Inline contains documentation line-comment markers. Inline []string - Block []BlockMarker + + // Block contains documentation block-comment marker pairs. + Block []BlockMarker } // CommentMarker describes lexical comment markers and string delimiters for a language family. type CommentMarker struct { - Inline []string - Block []BlockMarker + // Inline contains line-comment markers. + Inline []string + + // Block contains block-comment marker pairs. + Block []BlockMarker + + // Documentation contains API documentation comment markers. Documentation DocumentationMarker - QuoteChars string + + // QuoteChars contains characters that open and close quoted strings. + QuoteChars string } // MarkerCommentFilter removes comments using lexical markers declared in CommentMarker. type MarkerCommentFilter struct { + // Syntax contains the comment markers and string delimiters to recognize. Syntax CommentMarker } +// blockState tracks an active block comment across source lines. type blockState struct { + // active reports whether scanning is inside a block comment. active bool - block BlockMarker - keep bool + + // block contains the active block comment markers. + block BlockMarker + + // keep reports whether the active comment should be retained. + keep bool } +// markerLineFilter tracks lexical comment filtering state for one source line. type markerLineFilter struct { - filter MarkerCommentFilter - line string - mode Mode - state *blockState - result strings.Builder - position int + // filter contains the language syntax configuration. + filter MarkerCommentFilter + + // line is the source line being filtered. + line string + + // mode selects which comments to retain. + mode Mode + + // state tracks block comments across lines. + state *blockState + + // result accumulates the filtered source line. + result strings.Builder + + // position is the current byte index in line. + position int + + // hadComment reports whether the line contained a recognized comment. hadComment bool } // Filter removes or preserves recognized comments across all lines. +// +// Parameters: +// lines - provides source lines. +// mode - selects comments to retain. +// +// Returns filtered source lines. func (f MarkerCommentFilter) Filter(lines []string, mode Mode) []string { var filtered []string state := blockState{} diff --git a/embedding/commentfilter/mode.go b/embedding/commentfilter/mode.go index 99864716..bdf9fb77 100644 --- a/embedding/commentfilter/mode.go +++ b/embedding/commentfilter/mode.go @@ -39,6 +39,13 @@ const ( ) // ParseMode converts an embed-code `comments` attribute value into a comment filter Mode. +// +// Parameters: +// value - provides the raw instruction attribute. +// +// Returns: +// Mode - parsed comment filter mode. +// error - when value is unsupported. func ParseMode(value string) (Mode, error) { switch Mode(value) { case "": diff --git a/embedding/commentfilter/visual_basic.go b/embedding/commentfilter/visual_basic.go index e6e7dcf3..cd6adcbb 100644 --- a/embedding/commentfilter/visual_basic.go +++ b/embedding/commentfilter/visual_basic.go @@ -36,6 +36,12 @@ const ( type VisualBasicCommentFilter struct{} // Filter removes or preserves Visual Basic comments according to mode. +// +// Parameters: +// lines - provides Visual Basic source lines. +// mode - selects comments to retain. +// +// Returns filtered source lines. func (VisualBasicCommentFilter) Filter(lines []string, mode Mode) []string { var filtered []string for _, line := range lines { diff --git a/embedding/embedding_test.go b/embedding/embedding_test.go index 8020ce14..72483de2 100644 --- a/embedding/embedding_test.go +++ b/embedding/embedding_test.go @@ -39,6 +39,7 @@ import ( const temporaryTestDir = "../test/docs" +// TestEmbedding runs the embedding test suite. func TestEmbedding(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Data Suite") @@ -346,6 +347,7 @@ var _ = Describe("Embedding", func() { }) }) +// buildConfigWithSourceFiles returns a configuration using source-code fixtures. func buildConfigWithSourceFiles() configuration.Configuration { var config = configuration.NewConfiguration() config.DocumentationRoot = temporaryTestDir @@ -354,6 +356,7 @@ func buildConfigWithSourceFiles() configuration.Configuration { return config } +// newProcessor creates an embedding processor for a test documentation file. func newProcessor( docPath string, config configuration.Configuration, @@ -365,6 +368,7 @@ func newProcessor( return processor } +// copyDirRecursive copies a directory tree into the test workspace. func copyDirRecursive(sourceDirPath string, targetDirPath string) { info, err := os.Stat(sourceDirPath) if err != nil { @@ -396,6 +400,7 @@ func copyDirRecursive(sourceDirPath string, targetDirPath string) { } } +// copyFile copies one fixture file into the test workspace. func copyFile(sourceFilePath string, targetFilePath string) (err error) { sourceFile, err := os.Open(sourceFilePath) if err != nil { diff --git a/embedding/error.go b/embedding/error.go index ddad64cc..7b4d6c82 100644 --- a/embedding/error.go +++ b/embedding/error.go @@ -26,12 +26,19 @@ import ( // ProcessingError wraps a parser or source-resolution error with documentation location. type ProcessingError struct { + // DocFilePath is the path to the documentation file being processed. DocFilePath string - Line int - Err error + + // Line is the one-based documentation line where processing failed. + Line int + + // Err is the underlying parser or source-resolution error. + Err error } // Error returns a user-facing description of the failed documentation processing operation. +// +// Returns formatted processing error text. func (e ProcessingError) Error() string { return fmt.Sprintf( "failed to embed code fragment into doc file `%s`: %s", @@ -41,6 +48,8 @@ func (e ProcessingError) Error() string { } // Unwrap returns the parser or source-resolution error that caused processing to fail. +// +// Returns the underlying parser or source-resolution error. func (e ProcessingError) Unwrap() error { return e.Err } diff --git a/embedding/orchestration.go b/embedding/orchestration.go index a9e0108d..78e291b7 100644 --- a/embedding/orchestration.go +++ b/embedding/orchestration.go @@ -33,24 +33,28 @@ import ( ) // EmbedAllResult contains the result of an EmbedAll operation. -// -// 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 { - TotalEmbeddings int + // TotalEmbeddings is the total number of embeddings found in the target documentation files. + TotalEmbeddings int + + // UpdatedTargetFiles contains documentation files changed by embedding. UpdatedTargetFiles []string } // 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. +// EmbedAll embeds code fragments into all documentation files selected by config. // -// Iterates over patterns in the configuration, finds documentation files matching those patterns, -// creates a Processor for each file, and embeds code fragments in them. +// It resolves documentation files from configured patterns, creates a Processor +// for each file, and embeds code fragments into those documents. // -// config — a configuration for embedding. +// Parameters: +// config - provides embedding configuration. +// +// Returns: +// EmbedAllResult - embedding result. +// error - when selected documents fail to process. func EmbedAll(config configuration.Configuration) (EmbedAllResult, error) { totalEmbeddings := 0 var updatedTargetFiles []string @@ -107,7 +111,12 @@ func configNameLabel(config configuration.Configuration) string { // CheckUpToDate returns documentation files that are not up-to-date with code files. // -// config — a configuration for embedding. +// Parameters: +// config - provides embedding configuration. +// +// Returns: +// []string - stale documentation file paths. +// error - when selected documents fail to process. func CheckUpToDate(config configuration.Configuration) ([]string, error) { changedFiles, checkErrors := findChangedFiles(config) if len(checkErrors) > 0 { @@ -118,8 +127,6 @@ func CheckUpToDate(config configuration.Configuration) ([]string, error) { } // 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( diff --git a/embedding/parsing/blank_line.go b/embedding/parsing/blank_line.go index d5290800..ba94fffd 100644 --- a/embedding/parsing/blank_line.go +++ b/embedding/parsing/blank_line.go @@ -29,8 +29,10 @@ type BlankLineState struct{} // Recognize reports whether the current line is blank. // -// Checks if the current line is empty and not part of a code fence, and if there is an embedding. -// If these conditions are met, it returns true. Otherwise, it returns false. +// Parameters: +// context - provides current parser state. +// +// Returns true for blank lines between an embedding instruction and its code fence. func (b BlankLineState) Recognize(context Context) bool { if !context.ReachedEOF() && strings.TrimSpace(context.CurrentLine()) == "" { return !context.CodeFenceStarted && context.EmbeddingInstruction != nil @@ -40,6 +42,11 @@ func (b BlankLineState) Recognize(context Context) bool { } // Accept appends the current line of the context to the result, and moves to the next line. +// +// Parameters: +// context - provides mutable parser state. +// +// Returns nil. func (b BlankLineState) Accept(context *Context, _ configuration.Configuration) error { line := context.CurrentLine() context.Result = append(context.Result, line) diff --git a/embedding/parsing/code_fence_end.go b/embedding/parsing/code_fence_end.go index f225fdb7..13fdc4f4 100644 --- a/embedding/parsing/code_fence_end.go +++ b/embedding/parsing/code_fence_end.go @@ -27,12 +27,15 @@ import ( // CodeFenceEndState represents the end of a code fence. type CodeFenceEndState struct{} -// Recognize reports whether the current line meets this conditions: -// - the end of file is not reached; -// - the code fence has started; -// - the current line starts with the appropriate indentation and "```" +// Recognize reports whether the current line closes the active embedding fence. // -// context — a context of the parsing process. +// It requires EOF not reached, an active code fence, matching fence indentation, +// and a closing fence marker compatible with the opening marker. +// +// Parameters: +// context - provides current parser state. +// +// Returns true when the current line closes the active embedding code fence. func (c CodeFenceEndState) Recognize(context Context) bool { if context.ReachedEOF() { return false @@ -52,12 +55,15 @@ func (c CodeFenceEndState) Recognize(context Context) bool { return isClosingCodeFence(line, context.CodeFenceMarker) } -// Accept adds the current line to the result, resets certain context variables, and moves to -// the next line. +// Accept renders the embedding content and closes the active embedding fence. +// +// It appends the closing fence when rendering succeeds. When rendering fails, +// it restores the original Markdown for the embedding before advancing. // -// context — a context of the parsing process. +// Parameters: +// context - provides mutable parser state. // -// Returns an error if the rendering was not successful. +// Returns an error when embedded content cannot be produced. func (c CodeFenceEndState) Accept(context *Context, _ configuration.Configuration) error { line := context.CurrentLine() err := renderSample(context) @@ -75,11 +81,12 @@ func (c CodeFenceEndState) Accept(context *Context, _ configuration.Configuratio return err } -// Renders the sample content of the embedding. +// renderSample appends rendered embedding source lines to the parse result. // -// context — a context of the parsing process. +// Parameters: +// context - provides mutable parser state and the current embedding instruction. // -// Returns an error if the reading of the embedding's content was not successful. +// Returns an error when reading the embedding content fails. func renderSample(context *Context) error { content, err := context.EmbeddingInstruction.Content() if err != nil { @@ -93,6 +100,7 @@ func renderSample(context *Context) error { return nil } +// isClosingCodeFence reports whether line closes a fence opened with marker. func isClosingCodeFence(line string, marker string) bool { if line == "" { return false diff --git a/embedding/parsing/code_fence_start.go b/embedding/parsing/code_fence_start.go index d50e64d0..62df4c3c 100644 --- a/embedding/parsing/code_fence_start.go +++ b/embedding/parsing/code_fence_start.go @@ -24,12 +24,15 @@ import ( "embed-code/embed-code-go/configuration" ) -// CodeFenceStartState represents the StartState of a code fence. +// CodeFenceStartState represents the start state of an embedding code fence. type CodeFenceStartState struct{} -// Recognize reports whether the current line is not reached the end and starts with "```". +// Recognize reports whether the current line starts a code fence. // -// context — a context of the parsing process. +// Parameters: +// context - provides current parser state. +// +// Returns true when EOF is not reached and the current line starts a Markdown code fence. func (c CodeFenceStartState) Recognize(context Context) bool { if !context.ReachedEOF() { return strings.HasPrefix(strings.TrimSpace(context.CurrentLine()), "```") @@ -38,11 +41,15 @@ func (c CodeFenceStartState) Recognize(context Context) bool { return false } -// Accept appends the current line from the parsing context to the result, sets a flag to indicate -// that a code fence has started, calculates the indentation level of the code fence, and moves -// to the next line in the context. +// Accept records code fence state and advances to the first embedded source line. +// +// It appends the current line to the result, records that the code fence has started, +// records its indentation, and advances to the next line. +// +// Parameters: +// context - provides mutable parser state. // -// context — a context of the parsing process. +// Returns nil. func (c CodeFenceStartState) Accept(context *Context, _ configuration.Configuration) error { line := context.CurrentLine() trimmedLine := strings.TrimSpace(line) @@ -52,13 +59,14 @@ func (c CodeFenceStartState) Accept(context *Context, _ configuration.Configurat leadingSpaces := len(line) - len(strings.TrimLeft(line, " ")) context.CodeFenceIndentation = leadingSpaces context.ToNextLine() - // As we accepted this state and moved to the next line, we assume that the code lines - // start here. + // After accepting the opening fence and moving to the next line, + // embedded source lines start at the current context position. context.SetCodeStart() return nil } +// codeFenceMarker returns the repeated fence marker characters at the start of line. func codeFenceMarker(line string) string { if line == "" { return "" diff --git a/embedding/parsing/code_sample_line.go b/embedding/parsing/code_sample_line.go index af3e623a..c2588ff6 100644 --- a/embedding/parsing/code_sample_line.go +++ b/embedding/parsing/code_sample_line.go @@ -23,17 +23,22 @@ import "embed-code/embed-code-go/configuration" // CodeSampleLineState represents a line of a code sample. type CodeSampleLineState struct{} -// Recognize reports whether the current line is a code sample line: the code fence is started, and -// it is not the end of a file. +// Recognize reports whether the current line belongs to an active embedding fence. // -// context — a context of the parsing process. +// Parameters: +// context - provides current parser state. +// +// Returns true for source lines inside an embedding code fence. func (c CodeSampleLineState) Recognize(context Context) bool { return !context.ReachedEOF() && context.CodeFenceStarted } -// Accept moves to the next line. +// Accept skips the original embedded source line. +// +// Parameters: +// context - provides mutable parser state. // -// context — a context of the parsing process. +// Returns nil. func (c CodeSampleLineState) Accept(context *Context, _ configuration.Configuration) error { context.ToNextLine() diff --git a/embedding/parsing/constants.go b/embedding/parsing/constants.go index 325ad867..0f092a04 100644 --- a/embedding/parsing/constants.go +++ b/embedding/parsing/constants.go @@ -42,12 +42,27 @@ var Transitions = TransitionMap{ } var ( - Start = StartState{} - RegularLine = RegularLineState{} + // Start is the initial parser state. + Start = StartState{} + + // RegularLine handles ordinary Markdown content. + RegularLine = RegularLineState{} + + // EmbedInstruction handles `` instruction tags. EmbedInstruction = EmbedInstructionTokenState{} - BlankLine = BlankLineState{} - CodeFenceStart = CodeFenceStartState{} - CodeFenceEnd = CodeFenceEndState{} - CodeSampleLine = CodeSampleLineState{} - Finish = FinishState{} + + // BlankLine handles blank lines between an instruction and its code fence. + BlankLine = BlankLineState{} + + // CodeFenceStart handles opening code fences after instructions. + CodeFenceStart = CodeFenceStartState{} + + // CodeFenceEnd handles closing code fences after embedded samples. + CodeFenceEnd = CodeFenceEndState{} + + // CodeSampleLine handles source lines inside an embedding code fence. + CodeSampleLine = CodeSampleLineState{} + + // Finish is the terminal parser state. + Finish = FinishState{} ) diff --git a/embedding/parsing/context.go b/embedding/parsing/context.go index c037dfea..0f367c28 100644 --- a/embedding/parsing/context.go +++ b/embedding/parsing/context.go @@ -24,62 +24,83 @@ import ( "regexp" ) -// Context represents the context for parsing a file containing code embeddings. -// -// EmbeddingInstruction - a pointer to the embedding instruction. -// -// MarkdownFilePath - a path to the markdown file. -// -// Result - a list of strings representing the markdown file updated with embedding. -// -// CodeFenceStarted - a flag indicating whether a code fence has been started. -// -// CodeFenceIndentation - an indentation of the markdown's code fences. -// -// EmbeddingsNotFound - a list of embedding instructions that are not found in the code. -// -// UnacceptedEmbeddings - a list of embedding instructions that are not accepted by the parser. +// Context represents the state of parsing a documentation file containing code embeddings. type Context struct { - EmbeddingInstruction *Instruction - MarkdownFilePath string - Result []string - CodeFenceStarted bool - CodeFenceMarker string - CodeFenceIndentation int - MarkdownFenceStarted bool - MarkdownFenceMarker string + // EmbeddingInstruction is the instruction currently being parsed. + EmbeddingInstruction *Instruction + + // MarkdownFilePath is the path to the documentation file. + MarkdownFilePath string + + // Result contains the documentation lines produced by parsing. + Result []string + + // CodeFenceStarted reports whether an embedding code fence is open. + CodeFenceStarted bool + + // CodeFenceMarker is the marker used by the open embedding code fence. + CodeFenceMarker string + + // CodeFenceIndentation is the indentation of the embedding code fence. + CodeFenceIndentation int + + // MarkdownFenceStarted reports whether an ordinary Markdown code fence is open. + MarkdownFenceStarted bool + + // MarkdownFenceMarker is the marker used by the open ordinary Markdown code fence. + MarkdownFenceMarker string + + // MarkdownFenceIndentation is the indentation of the ordinary Markdown code fence. MarkdownFenceIndentation int - EmbeddingsNotFound []Instruction - UnacceptedEmbeddings []Instruction - // source - a list of strings representing the original markdown file. + + // EmbeddingsNotFound contains instructions whose source fragments were not found. + EmbeddingsNotFound []Instruction + + // UnacceptedEmbeddings contains instructions rejected by the parser. + UnacceptedEmbeddings []Instruction + + // source contains the original documentation lines. source []string - // lineIndex - an index of the current line in the markdown file. + + // lineIndex is the zero-based index of the current documentation line. lineIndex int - // fileContainsEmbedding - a flag indicating whether the file contains an embedding instruction. + + // fileContainsEmbedding reports whether the file contains an embedding instruction. fileContainsEmbedding bool - // embeddings - a list of embedding instructions found in the markdown file. + + // embeddings contains accepted embedding instructions and their source positions. embeddings []EmbeddingContext } -// EmbeddingsCount returns number of found embeddings. +// EmbeddingsCount returns the number of found embeddings. +// +// Returns accepted embedding count. func (c *Context) EmbeddingsCount() int { return len(c.embeddings) } // EmbeddingContext contains an instruction and its position in the source Markdown file. -// -// SourceStartIndex is the zero-based index of the first line after the opening code fence. -// -// 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 + + // SourceStartIndex is the first source-line index belonging to the embedding. + SourceStartIndex int + + // SourceEndIndex is the first source-line index after the embedding. + SourceEndIndex int } -// NewContext Creates and returns a new Context struct with initial values for markdownFile, source, -// lineIndex, and result. +// NewContext creates a parsing context for a documentation file. +// +// It initializes MarkdownFilePath, source lines, line index, and result buffer. +// +// Parameters: +// markdownFile - identifies the documentation file to parse. +// +// Returns: +// Context - initialized parsing context. +// error - when the documentation file cannot be read. func NewContext(markdownFile string) (Context, error) { source, err := readLines(markdownFile) if err != nil { @@ -95,6 +116,11 @@ func NewContext(markdownFile string) (Context, error) { } // NewEmptyContext creates a Context for a documentation file that was not parsed. +// +// Parameters: +// markdownFile - identifies the skipped documentation file. +// +// Returns empty parsing context. func NewEmptyContext(markdownFile string) Context { return Context{ MarkdownFilePath: markdownFile, @@ -103,12 +129,16 @@ func NewEmptyContext(markdownFile string) Context { } } -// CurrentLine returns the line of source code at the current Context line index. +// CurrentLine returns the documentation line at the current parser index. +// +// Returns the current documentation source line. func (c *Context) CurrentLine() string { return c.source[c.lineIndex] } -// CurrentIndex returns the current one-based source line number. +// CurrentIndex returns the current one-based documentation source line number. +// +// Returns one-based line number. func (c *Context) CurrentIndex() int { return c.lineIndex + 1 } @@ -118,13 +148,18 @@ func (c *Context) ToNextLine() { c.lineIndex++ } -// ReachedEOF reports whether the end of the source code file has been reached. +// ReachedEOF reports whether the parser reached the end of the documentation source file. +// +// Returns true when the parser index is at or beyond the source length. func (c *Context) ReachedEOF() bool { return c.lineIndex >= len(c.source) } -// IsContentChanged Reports whether the content of the code file has changed compared to the -// embedding of the markdown file. +// IsContentChanged reports whether generated documentation differs from the source content. +// +// It compares generated result lines with original documentation source lines. +// +// Returns true when generated lines differ from original source lines. func (c *Context) IsContentChanged() bool { for i := 0; i < c.lineIndex; i++ { if c.source[i] != c.Result[i] { @@ -136,12 +171,15 @@ func (c *Context) IsContentChanged() bool { } // IsContainsEmbedding reports whether the doc file contains an embedding. +// +// Returns true after at least one embedding instruction is recognized. func (c *Context) IsContainsEmbedding() bool { return c.fileContainsEmbedding } -// ResolveEmbeddingNotFound writes the source content of the markdown file if embedding -// is not found. +// ResolveEmbeddingNotFound preserves the original Markdown when source content is missing. +// +// It also records the instruction for logging. func (c *Context) ResolveEmbeddingNotFound() { currentEmbedding := *c.CurrentEmbedding() source := c.readEmbeddingSource(currentEmbedding) @@ -149,9 +187,9 @@ func (c *Context) ResolveEmbeddingNotFound() { c.EmbeddingsNotFound = append(c.EmbeddingsNotFound, currentEmbedding.embeddingInstruction) } -// ResolveUnacceptedEmbedding deletes embedding from the list of embeddings if it is not accepted. +// ResolveUnacceptedEmbedding records and removes an instruction rejected by the parser. // -// Also appends it to the list of such embeddings for logging. +// It also records the instruction for logging. func (c *Context) ResolveUnacceptedEmbedding() { currentEmbeddingInstruction := c.CurrentEmbedding().embeddingInstruction c.UnacceptedEmbeddings = append(c.UnacceptedEmbeddings, currentEmbeddingInstruction) @@ -160,6 +198,9 @@ func (c *Context) ResolveUnacceptedEmbedding() { } // StartEmbedding records an instruction as the current embedding. +// +// Parameters: +// instruction - provides parsed embedding instruction data. func (c *Context) StartEmbedding(instruction Instruction) { c.fileContainsEmbedding = true embeddingContext := EmbeddingContext{ @@ -177,8 +218,9 @@ func (c *Context) FinishEmbedding() { c.EmbeddingInstruction = nil } -// SetCodeStart sets the current line as a start of a code lines in the result. It's needed to not -// include instructions in the embedding. +// SetCodeStart records the first source line belonging to the current embedding fence. +// +// It excludes the instruction and opening fence from the original embedded source range. func (c *Context) SetCodeStart() { if c.fileContainsEmbedding { lastEmbedding := c.CurrentEmbedding() @@ -186,18 +228,24 @@ func (c *Context) SetCodeStart() { } } -// GetResult returns the result lines of the Context. +// GetResult returns the generated documentation lines. +// +// Returns generated documentation lines. func (c *Context) GetResult() []string { return c.Result } -// Returns a string representation of Context. +// String returns a string representation of Context. +// +// Returns diagnostic context text. func (c *Context) String() string { return fmt.Sprintf("Context[embedding=`%s`, file=`%s`, line=`%d`]", c.EmbeddingInstruction, c.MarkdownFilePath, c.lineIndex) } // CurrentEmbedding returns the embedding currently being parsed. +// +// Returns current embedding context. func (c *Context) CurrentEmbedding() *EmbeddingContext { return &c.embeddings[c.currentEmbeddingIndex()] } @@ -212,7 +260,14 @@ func (c *Context) readEmbeddingSource(context EmbeddingContext) []string { return c.source[context.SourceStartIndex:context.SourceEndIndex] } -// readLines returns the content of a file placed at filepath as a list of strings. +// readLines returns file content as lines split on Unix or Windows line endings. +// +// Parameters: +// filepath - provides the file to read. +// +// Returns: +// []string - file content lines. +// error - when the file cannot be read. func readLines(filepath string) ([]string, error) { bytes, err := os.ReadFile(filepath) if err != nil { diff --git a/embedding/parsing/finish.go b/embedding/parsing/finish.go index d87ba8e0..1ca52896 100644 --- a/embedding/parsing/finish.go +++ b/embedding/parsing/finish.go @@ -25,14 +25,23 @@ import ( // FinishState represents the end of the file. type FinishState struct{} -// Recognize reports whether the current line satisfies the transition. +// Recognize reports whether the parser reached the end of the documentation file. // -// context — a context of the parsing process. +// Parameters: +// context - provides current parser state. +// +// Returns true when the parser index is at EOF. func (f FinishState) Recognize(context Context) bool { return context.ReachedEOF() } -// Accept accepts FinishState, as there's no need to do anything, returns nil. +// Accept completes parsing without changing the context. +// +// Parameters: +// context - provides mutable parser state. +// config - provides embedding configuration. +// +// Returns nil. func (f FinishState) Accept(_ *Context, _ configuration.Configuration) error { return nil } diff --git a/embedding/parsing/instruction.go b/embedding/parsing/instruction.go index 7f98dbc4..b3705315 100644 --- a/embedding/parsing/instruction.go +++ b/embedding/parsing/instruction.go @@ -29,52 +29,57 @@ import ( "embed-code/embed-code-go/indent" ) -// Instruction specifies the code fragment to embed into a Markdown file, and the -// embedding parameters. +// Instruction specifies the code fragment to embed into a Markdown file. // -// Takes form of an XML processing instruction . -// -// CodeFile — a path to a code file to embed. The path is relative to the corresponding code root. -// -// Fragment — name of the particular fragment in the code. If Fragment is empty, the whole file -// is embedded. -// -// StartPattern — an optional glob-like pattern. If specified, lines before the matching one -// are excluded. -// -// EndPattern — an optional glob-like pattern. If specified, lines after the matching one -// are excluded. -// -// LinePattern — an optional glob-like pattern. If specified, only the matching line is embedded. -// -// CommentMode — specifies which comments are retained in the embedded code. -// -// DocumentationFile — a documentation file containing the instruction. -// -// DocumentationLine — a line containing the start of the instruction. -// -// Configuration — a Configuration with all embed-code settings. +// It is parsed from an XML-like `` instruction such as +// ``. type Instruction struct { - CodeFile string - Fragment string - StartPattern *Pattern - EndPattern *Pattern - LinePattern *Pattern - CommentMode commentfilter.Mode + // CodeFile is the path to the source file relative to its code root. + CodeFile string + + // Fragment identifies a named fragment; an empty value selects the whole file. + Fragment string + + // StartPattern excludes lines before its first match when set. + StartPattern *Pattern + + // EndPattern excludes lines after its first match when set. + EndPattern *Pattern + + // LinePattern selects only its matching line when set. + LinePattern *Pattern + + // CommentMode selects which comments are retained in embedded code. + CommentMode commentfilter.Mode + + // DocumentationFile is the path to the documentation containing the instruction. DocumentationFile string + + // DocumentationLine is the line containing the start of the instruction. DocumentationLine int - Configuration configuration.Configuration + + // Configuration contains the embedding settings. + Configuration configuration.Configuration } // PatternNotFoundError reports that an instruction pattern did not match the code file. type PatternNotFoundError struct { - Line int + // Line is the documentation line containing the instruction. + Line int + + // CodeFileReference is the user-facing reference to the searched source file. CodeFileReference string - Kind string - Pattern *Pattern + + // Kind identifies the unmatched pattern as start or end. + Kind string + + // Pattern is the source-line pattern that did not match. + Pattern *Pattern } // Error returns a user-facing description of an unmatched start or end pattern. +// +// Returns formatted pattern error text. func (e PatternNotFoundError) Error() string { pattern := "" if e.Pattern != nil { @@ -89,21 +94,22 @@ func (e PatternNotFoundError) Error() string { ) } -// NewInstruction creates an Instruction based on provided attributes and configuration. +// NewInstruction builds an instruction from parsed `` attributes and configuration. // -// attributes — a map with string-typed both keys and values. Possible keys are: -// - file — a mandatory relative path to the file with the code; -// - fragment — an optional name of the particular fragment in the code. If no fragment -// is specified, the whole file is embedded; -// - start — an optional glob-like pattern. If specified, lines before the matching one -// are excluded; -// - end — an optional glob-like pattern. If specified, lines after the matching one are excluded. -// - line — an optional glob-like pattern. If specified, only the matching line is embedded. -// - comments — an optional comment filtering mode. If omitted, all comments are retained. +// Parameters: +// attributes - provides embed-code tag attributes. Supported keys are: +// - file - mandatory relative path to the source file; +// - fragment - optional source fragment name. When omitted, the whole file is embedded; +// - start - optional glob-like pattern. Matching lines before it are excluded; +// - end - optional glob-like pattern. Matching lines after it are excluded; +// - line - optional glob-like pattern. Only the matching line is embedded; +// - comments - optional comment filtering mode. When omitted, all comments are retained. // -// config — a Configuration with all embed-code settings. +// config - provides embedding configuration. // -// Returns an error if the instruction is wrong. +// Returns: +// Instruction - parsed embedding instruction. +// error - when instruction attributes are invalid. func NewInstruction( attributes map[string]string, config configuration.Configuration) (Instruction, error) { codeFile := attributes["file"] @@ -154,9 +160,14 @@ func validateExclusiveAttributes(fragment string, start string, end string, line // instructionPatterns holds the optional source-line patterns from instruction attributes. type instructionPatterns struct { + // start is the optional start pattern. start *Pattern - end *Pattern - line *Pattern + + // end is the optional end pattern. + end *Pattern + + // line is the optional single-line pattern. + line *Pattern } // parseInstructionPatterns parses all optional source-line pattern attributes. @@ -197,9 +208,14 @@ func parseInstructionPattern(attribute string, value string) (Pattern, error) { return pattern, nil } -// Content reads and returns the lines for specified fragment from the code. +// Content returns source lines selected and filtered by this instruction. +// +// It reads source content for the configured file and fragment before applying +// optional source-line patterns and comment filtering. // -// Returns an error if there was an error during reading the content. +// Returns: +// []string - selected and filtered source lines. +// error - when source resolution or pattern matching fails. func (e Instruction) Content() ([]string, error) { fileContent, err := fragmentation.ResolveContent(e.CodeFile, e.Fragment, e.Configuration) if err != nil { @@ -267,6 +283,8 @@ func patternLabel(kind string, pattern *Pattern) string { } // String returns a string representation of Instruction. +// +// Returns diagnostic instruction text. func (e Instruction) String() string { return fmt.Sprintf( "EmbeddingInstruction[file=`%s`, fragment=`%s`, start=`%s`, end=`%s`, line=`%s`, comments=`%s`]", @@ -275,8 +293,6 @@ func (e Instruction) String() string { } // matchingLines filters and returns input lines based on start, end, or line patterns. -// -// lines — a list of strings representing the input lines. func (e Instruction) matchingLines(lines []string, codeFileReference string) ([]string, error) { var selectedLines []string var err error @@ -343,13 +359,19 @@ func removeCommonIndent(lines []string) []string { return indent.CutIndent(lines, indentation) } -// matchPattern returns the first line range that matches given pattern. -// -// pattern — a pattern to search in lines for. +// matchPattern returns the first source-line range matching pattern. // -// lines — a list of lines to search in. +// Parameters: +// pattern - provides the source-line pattern to search for. +// lines - provides source lines to search in. +// startFrom - provides the first index to search. +// kind - identifies the pattern kind for errors. +// codeFileReference - identifies the searched file for errors. // -// startFrom — an index from which to start searching. +// Returns: +// int - inclusive start index. +// int - inclusive end index. +// error - when pattern does not match. func (e Instruction) matchPattern( pattern *Pattern, lines []string, startFrom int, kind string, codeFileReference string, ) (int, int, error) { diff --git a/embedding/parsing/instruction_test.go b/embedding/parsing/instruction_test.go index 45882d85..9722722f 100644 --- a/embedding/parsing/instruction_test.go +++ b/embedding/parsing/instruction_test.go @@ -34,15 +34,28 @@ import ( . "github.com/onsi/gomega" ) +// TestInstructionParams contains instruction attributes used by parser tests. type TestInstructionParams struct { - fragment string + // fragment is the optional fragment name. + fragment string + + // startGlob is the optional start pattern. startGlob string - endGlob string - lineGlob string - comments string - closeTag bool + + // endGlob is the optional end pattern. + endGlob string + + // lineGlob is the optional single-line pattern. + lineGlob string + + // comments is the requested comment filtering mode. + comments string + + // closeTag reports whether the instruction includes a closing tag. + closeTag bool } +// TestInstruction runs the instruction parsing test suite. func TestInstruction(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Data Suite") @@ -531,6 +544,7 @@ var _ = Describe("Instruction", func() { }) }) +// getXMLExtractionContent returns source lines selected by an XML instruction fixture. func getXMLExtractionContent(fileName string, params TestInstructionParams, config configuration.Configuration) []string { xmlString := buildInstruction(fileName, params) @@ -539,6 +553,7 @@ func getXMLExtractionContent(fileName string, params TestInstructionParams, return readInstructionContent(instruction) } +// buildConfigWithSourceFiles returns a configuration using parser source fixtures. func buildConfigWithSourceFiles() configuration.Configuration { var config = configuration.NewConfiguration() config.DocumentationRoot = "../../test/resources/docs" @@ -547,6 +562,7 @@ func buildConfigWithSourceFiles() configuration.Configuration { return config } +// buildInstruction builds an XML instruction string from test parameters. func buildInstruction(fileName string, params TestInstructionParams) string { fragmentAttr := xmlAttribute("fragment", params.fragment) instructionLine := fmt.Sprintf("' XML tag and creates new Instruction. +// FromXML parses an XML-like `` tag into an Instruction. +// +// The line can be self-closing: +// ``. +// It can also use a closing tag: +// ``. // -// line — a line which contains '' XML tag. -// For example: ''. -// The line can also contain closing tag: -// ''. -// The following parameters are currently supported: -// - file — a mandatory relative path to the file with the code; -// - fragment — an optional name of the particular fragment in the code. If no fragment -// is specified, the whole file is embedded; -// - start — an optional glob-like pattern. If specified, lines before the matching one -// are excluded; -// - end — an optional glob-like pattern. If specified, lines after the matching one are excluded. -// - line — an optional glob-like pattern. If specified, only the matching line is embedded. -// - comments — an optional comment filtering mode. If omitted, all comments are retained. +// Supported instruction attributes: +// - file - mandatory relative path to the source file; +// - fragment - optional source fragment name. When omitted, the whole file is embedded; +// - start - optional glob-like pattern. Matching lines before it are excluded; +// - end - optional glob-like pattern. Matching lines after it are excluded; +// - line - optional glob-like pattern. Only the matching line is embedded; +// - comments - optional comment filtering mode. When omitted, all comments are retained. // -// config — a Configuration with all embed-code settings. +// Parameters: +// line - provides raw instruction text. +// config - provides embedding configuration. // -// Returns an error if the paring of XML instruction failed. +// Returns: +// Instruction - parsed embedding instruction. +// error - when XML or instruction attributes are invalid. func FromXML(line string, config configuration.Configuration) (Instruction, error) { fields, err := ParseXMLLine(line) if err != nil { @@ -63,11 +65,14 @@ func FromXML(line string, config configuration.Configuration) (Instruction, erro return NewInstruction(fields, config) } -// ParseXMLLine parses given XML-encoded xmlLine and returns attributes data as key-value pairs. +// ParseXMLLine parses an XML-like `` tag into attribute key-value pairs. // -// xmlLine — an XML-encoded line. +// Parameters: +// xmlLine - provides raw instruction text. // -// Returns a map of key-value pairs. If the provided line is not valid, returns an error. +// Returns: +// map[string]string - instruction attributes by name. +// error - when the line is not a valid embed-code XML element. func ParseXMLLine(xmlLine string) (map[string]string, error) { var root Item err := xml.Unmarshal([]byte(quoteEscapedXMLLine(xmlLine)), &root) diff --git a/embedding/processor.go b/embedding/processor.go index 0f385c48..9a6aa69e 100644 --- a/embedding/processor.go +++ b/embedding/processor.go @@ -47,7 +47,15 @@ type Processor struct { requiredDocPaths []string } -// NewProcessor creates and returns new Processor with given docFile and config. +// NewProcessor creates and returns a new Processor with the given docFile and config. +// +// Parameters: +// docFile - identifies the documentation file to process. +// config - provides embedding configuration. +// +// Returns: +// Processor - documentation file processor. +// error - when configured documentation patterns cannot be resolved. func NewProcessor(docFile string, config configuration.Configuration) (Processor, error) { requiredDocPaths, err := requiredDocs(config) if err != nil { @@ -74,8 +82,9 @@ func newProcessor( // Embed constructs embedding and modifies the doc file if embedding is needed. // -// Returns an empty context without parsing the file when it is excluded by configuration. -// If any problems faced, an error is returned. +// Returns: +// *parsing.Context - parsing context, empty when the file is excluded by configuration. +// error - when processing or writing fails. func (p Processor) Embed() (*parsing.Context, error) { if !slices.Contains(p.requiredDocPaths, p.DocFilePath) { slog.Info(fmt.Sprintf("Skipping `%s`; it is excluded by the configuration.", @@ -109,6 +118,8 @@ func (p Processor) Embed() (*parsing.Context, error) { } // IsUpToDate reports whether the embedding of the target markdown is up-to-date with the code file. +// +// Returns false when processing fails or content is stale. func (p Processor) IsUpToDate() bool { upToDate, err := p.isUpToDate() if err != nil { @@ -143,12 +154,12 @@ func (p Processor) isUpToDate() (bool, error) { return upToDate, nil } +// fillEmbeddingContext runs the parser state machine over one documentation file. +// // 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 // the result. -// -// Returns a parsing.Context and an error if any occurs. func (p Processor) fillEmbeddingContext() (parsing.Context, error) { context, err := parsing.NewContext(p.DocFilePath) if err != nil { @@ -229,8 +240,16 @@ func unacceptedTransitionError(context parsing.Context) error { return fmt.Errorf("unexpected parser state at line %d", 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. +// moveToNextState advances the parser through the transition map. +// +// Parameters: +// state - provides the current parser state. +// context - provides mutable parser state. +// +// Returns: +// bool - whether a matching next state was accepted. +// *parsing.State - accepted next state, or current state when none matches. +// error - when accepting the next state fails. func (p Processor) moveToNextState(state *parsing.State, context *parsing.Context) ( bool, *parsing.State, error) { for _, nextState := range p.TransitionsMap[*state] { diff --git a/files/files.go b/files/files.go index 338c9c36..d96d74ea 100644 --- a/files/files.go +++ b/files/files.go @@ -33,8 +33,14 @@ const ( WritePermission uint32 = 0600 ) -// IsFileExist reports whether the given path (relative or absolute) to a file exists in the -// file system. +// IsFileExist reports whether the given path exists as a file. +// +// Parameters: +// filePath - provides a file path or glob pattern. +// +// Returns: +// bool - whether the first match exists as a file. +// error - when the path cannot be inspected or points to a directory. func IsFileExist(filePath string) (bool, error) { exists, info, err := validatePathExists(filePath) if err != nil { @@ -51,8 +57,14 @@ func IsFileExist(filePath string) (bool, error) { return false, nil } -// IsDirExist reports whether the given directory exists in the file system by the path -// (relative or absolute). +// IsDirExist reports whether the given path exists as a directory. +// +// Parameters: +// path - provides a directory path or glob pattern. +// +// Returns: +// bool - whether the first match exists as a directory. +// error - when the path cannot be inspected or points to a file. func IsDirExist(path string) (bool, error) { exists, info, err := validatePathExists(path) if err != nil { diff --git a/files/files_test.go b/files/files_test.go index 153ea085..2b96e205 100644 --- a/files/files_test.go +++ b/files/files_test.go @@ -29,6 +29,7 @@ import ( . "github.com/onsi/gomega" ) +// TestFiles runs the filesystem helper test suite. func TestFiles(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Data Suite") diff --git a/fragmentation/cache.go b/fragmentation/cache.go index bc5a3feb..bee12dc2 100644 --- a/fragmentation/cache.go +++ b/fragmentation/cache.go @@ -25,12 +25,23 @@ import ( // cache is a limited collection of recently used values by key. type cache[K comparable, V any] struct { + // Mutex guards all cache state. sync.Mutex - limit int - loader func(K) (V, error) - values map[K]V + + // limit is the maximum number of retained values. + limit int + + // loader resolves values that are not cached. + loader func(K) (V, error) + + // values contains cached values indexed by key. + values map[K]V + + // entries maps keys to their positions in the usage order. entries map[K]*list.Element - order *list.List + + // order tracks keys from least to most recently used. + order *list.List } // newCache creates a cache with a loader and least-recently-used eviction. diff --git a/fragmentation/encoding.go b/fragmentation/encoding.go index 1b984fb3..cbb315b4 100644 --- a/fragmentation/encoding.go +++ b/fragmentation/encoding.go @@ -26,6 +26,8 @@ import ( type unsupportedEncodingError struct{} // Error describes the required source encoding. +// +// Returns formatted encoding error text. func (*unsupportedEncodingError) Error() string { return "unsupported source encoding: expected UTF-8" } diff --git a/fragmentation/fragment.go b/fragmentation/fragment.go index 07efdbf9..2c4bf17e 100644 --- a/fragmentation/fragment.go +++ b/fragmentation/fragment.go @@ -28,16 +28,17 @@ import ( const DefaultFragmentName = "_default" // Fragment is a single fragment in a file. -// -// Name — a name of a Fragment. -// -// Partitions — a list of partitions found for a Fragment. type Fragment struct { - Name string + // Name is the fragment name. + Name string + + // Partitions contains the source partitions that form the fragment. Partitions []Partition } -// CreateDefaultFragment creates and returns Fragment with DefaultFragmentName. +// CreateDefaultFragment creates a whole-file fragment. +// +// Returns whole-file fragment. func CreateDefaultFragment() Fragment { return Fragment{ Name: DefaultFragmentName, @@ -50,11 +51,15 @@ func (f Fragment) isDefault() bool { return f.Name == DefaultFragmentName } -// text returns the rendered text for the fragment. +// text returns source text selected by the fragment. // -// lines — a list with every line of the file. +// Parameters: +// lines - provides every source line in the file. +// separator - provides text inserted between multiple partitions of one fragment. // -// separator — string to insert between multiple partitions of a single fragment. +// Returns: +// string - rendered fragment text. +// error - when a partition cannot select its lines. func (f Fragment) text(lines []string, separator string) (string, error) { if f.isDefault() { return strings.Join(lines, "\n"), nil @@ -84,11 +89,14 @@ func (f Fragment) text(lines []string, separator string) (string, error) { return text, nil } -// obtainPartitionTexts returns source lines selected for every partition. +// obtainPartitionTexts returns source lines selected for every fragment partition. // -// lines — a list with every line of the file. +// Parameters: +// lines - provides every source line in the file. // -// partitions — a list with partitions to select lines from. +// Returns: +// [][]string - selected lines grouped by partition. +// error - when a partition cannot select its lines. func (f Fragment) obtainPartitionTexts(lines []string) ([][]string, error) { var partitionLines [][]string for _, part := range f.Partitions { diff --git a/fragmentation/fragment_builder.go b/fragmentation/fragment_builder.go index 96a18871..f55d2ddd 100644 --- a/fragmentation/fragment_builder.go +++ b/fragmentation/fragment_builder.go @@ -24,24 +24,23 @@ import ( ) // FragmentBuilder is a single fragment builder. -// -// CodeFilePath — a path to a file to fragment. -// -// Partitions — a list of partitions of a file to fragment. -// -// Name — a name of a Fragment. type FragmentBuilder struct { + // CodeFilePath is the path to the file being fragmented. CodeFilePath string - Partitions []Partition - Name string + + // Partitions contains the partitions found for the fragment. + Partitions []Partition + + // Name is the fragment name. + Name string } -// AddStartPosition adds a new partition with given startPosition. +// AddStartPosition adds a new fragment partition starting at startPosition. // -// AddEndPosition is need to be called when the end of the fragment is reached, -// or else it will be considered that the end of partition is in the end of the file. +// Parameters: +// startPosition - provides the zero-based source line where the partition starts. // -// startPosition — starting position of the fragment. +// Returns an error when the previous partition is still open. func (b *FragmentBuilder) AddStartPosition(startPosition int) error { if !b.isPartitionsEmpty() { lastPartition := b.lastAddedPartition() @@ -58,10 +57,15 @@ func (b *FragmentBuilder) AddStartPosition(startPosition int) error { return nil } -// AddEndPosition completes previously created fragment partition with its endPosition. -// It should be called after AddStartPosition. +// AddEndPosition completes the latest fragment partition at endPosition. +// +// It is needed to be called when the end of the fragment is reached, +// or else it will be considered that the end of partition is in the end of the file. // -// endPosition — end position of the fragment. +// Parameters: +// endPosition - provides the zero-based source line where the partition ends. +// +// Returns an error when no partition is open or the latest partition already has an end. func (b *FragmentBuilder) AddEndPosition(endPosition int) error { if b.isPartitionsEmpty() { return errors.New("the list of partitions is empty") @@ -77,7 +81,9 @@ func (b *FragmentBuilder) AddEndPosition(endPosition int) error { return nil } -// Build creates and returns new Fragment with the previously added and filled Partitions. +// Build creates a Fragment from the collected partition positions. +// +// Returns fragment with collected partitions. func (b *FragmentBuilder) Build() Fragment { return Fragment{ Name: b.Name, @@ -85,10 +91,12 @@ func (b *FragmentBuilder) Build() Fragment { } } +// isPartitionsEmpty reports whether no partition positions have been collected. func (b *FragmentBuilder) isPartitionsEmpty() bool { return len(b.Partitions) == 0 } +// lastAddedPartition returns the most recently collected partition. func (b *FragmentBuilder) lastAddedPartition() *Partition { lastIndex := len(b.Partitions) - 1 diff --git a/fragmentation/fragmentation.go b/fragmentation/fragmentation.go index b16f7081..1334cda4 100644 --- a/fragmentation/fragmentation.go +++ b/fragmentation/fragmentation.go @@ -43,20 +43,26 @@ import ( "path/filepath" ) -// NamedPathPrefix the prefix before the named code source. +// NamedPathPrefix is the prefix before a named code source. const NamedPathPrefix = "$" // Fragmentation splits the given file into fragments. type Fragmentation struct { // 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 } -// NewFragmentation builds Fragmentation for the given code file. +// NewFragmentation builds Fragmentation for a relative or absolute source path. +// +// Parameters: +// codeFile - provides the source file path. // -// codeFile — a relative or absolute path to a code file to fragment. +// Returns: +// Fragmentation - source file fragmentation context. +// error - when codeFile cannot be made absolute. func NewFragmentation(codeFile string) (Fragmentation, error) { absoluteCodeFile, err := filepath.Abs(codeFile) if err != nil { @@ -69,10 +75,12 @@ func NewFragmentation(codeFile string) (Fragmentation, error) { }, nil } -// DoFragmentation splits the file into fragments. +// DoFragmentation splits the source file into renderable content and named fragments. // -// Returns a refined content of the file to be cut into fragments, and the Fragments. -// Also returns an error if the fragmentation couldn't be done. +// Returns: +// []string - renderable source lines. +// map[string]Fragment - parsed fragments by name. +// error - when the source file cannot be read, decoded, or parsed. func (f Fragmentation) DoFragmentation() ([]string, map[string]Fragment, error) { var contentToRender []string @@ -110,16 +118,18 @@ func (f Fragmentation) DoFragmentation() ([]string, map[string]Fragment, error) return contentToRender, fragments, 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; -// - appends non-fragment lines to contentToRender. +// parseLine parses one source line and updates fragment builders or renderable content. // -// line — a string to parse. +// It identifies fragment start and end markers, updates fragment builders, +// and appends non-fragment lines to renderable content. // -// contentToRender — a list of strings which meant to be rendered. It fills up here. +// Parameters: +// line - provides one source line to parse. +// contentToRender - provides accumulated renderable source lines. // -// Returns updated contentToRender, and error if there's any. +// Returns: +// []string - updated renderable source lines. +// error - when fragment marker parsing fails. func (f Fragmentation) parseLine(line string, contentToRender []string) ([]string, error) { cursor := len(contentToRender) @@ -148,8 +158,9 @@ func (f Fragmentation) parseLine(line string, contentToRender []string) ([]strin return contentToRender, nil } -// Iterates through the fragments` starts, creates fragments builders (if necessary), and adds a -// new partition to the fragment. +// parseStartDocFragments starts a new partition for each named fragment marker. +// +// It creates fragment builders when necessary. func (f Fragmentation) parseStartDocFragments(docFragments []string, cursor int) error { for _, fragmentName := range docFragments { fragment, exists := f.fragmentBuilders[fragmentName] @@ -169,8 +180,9 @@ func (f Fragmentation) parseStartDocFragments(docFragments []string, cursor int) return nil } -// Iterates through the fragments` ends, creates fragments builders (if necessary), and adds a -// new partition to the fragment. +// parseEndDocFragments closes the latest partition for each named fragment marker. +// +// It requires a matching fragment builder to have been started earlier. func (f Fragmentation) parseEndDocFragments(endDocFragments []string, cursor int) error { for _, fragmentName := range endDocFragments { if fragment, exists := f.fragmentBuilders[fragmentName]; exists { diff --git a/fragmentation/fragmentation_test.go b/fragmentation/fragmentation_test.go index 24d7a6e5..3ae5e826 100644 --- a/fragmentation/fragmentation_test.go +++ b/fragmentation/fragmentation_test.go @@ -42,6 +42,7 @@ const ( indent = " " ) +// TestFragmentation runs the fragmentation test suite. func TestFragmentation(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Data Suite") @@ -244,6 +245,7 @@ var _ = Describe("Fragmentation", func() { }) }) +// buildTestFragmentation creates Fragmentation for a source fixture. func buildTestFragmentation(testFileName string, config configuration.Configuration) fragmentation.Fragmentation { codeRoot := config.CodeRoots[0] @@ -255,6 +257,7 @@ func buildTestFragmentation(testFileName string, return frag } +// doTestFragmentation fragments a source fixture and returns its rendered lines. func doTestFragmentation( testFileName string, config configuration.Configuration, @@ -268,6 +271,7 @@ func doTestFragmentation( return lines, fragments } +// resolveTestFragment returns one named fragment from a source fixture. func resolveTestFragment( testFileName string, fragmentName string, diff --git a/fragmentation/lookup.go b/fragmentation/lookup.go index 8a9cba66..5c569ca7 100644 --- a/fragmentation/lookup.go +++ b/fragmentation/lookup.go @@ -28,44 +28,55 @@ import ( var quotedNamePattern = regexp.MustCompile("\"(.*)\"") const ( + // FragmentStart marks the beginning of a named source fragment. FragmentStart = "#docfragment" - FragmentEnd = "#enddocfragment" + + // FragmentEnd marks the end of a named source fragment. + FragmentEnd = "#enddocfragment" ) -// FindDocFragments finds all the names for the fragment's openings using the opening prefix. +// FindDocFragments finds fragment names declared with the start marker. // // For example, FindDocFragments("// #docfragment \"main\",\"sub-main\"\n") // returns ["main", "sub-main"] // -// line — a line to search in. +// Parameters: +// line - provides one source line. // -// Returns the list of the names found. +// Returns: +// []string - fragment names declared on the line. +// error - when a declaration is malformed. func FindDocFragments(line string) ([]string, error) { return lookup(line, FragmentStart) } -// FindEndDocFragments finds all the names for the fragment's endings using the ending prefix. +// FindEndDocFragments finds fragment names declared with the end marker. // // For example, FindEndDocFragments("// #enddocfragment \"main\",\"sub-main\"\n") // returns ["main", "sub-main"] // -// line — a line to search in. +// Parameters: +// line - provides one source line. // -// Returns the list of the names found. +// Returns: +// []string - fragment names closed on the line. +// error - when a declaration is malformed. func FindEndDocFragments(line string) ([]string, error) { return lookup(line, FragmentEnd) } -// Looks up for fragments' names from given line. +// lookup finds fragment names in line after the given fragment marker prefix. // // For example, lookup("// #enddocfragment \"main\",\"sub-main\"\n", "#enddocfragment") // returns ["main", "sub-main"] // -// line — a line to search in. -// -// prefix — a user-defined indicator of a fragment, e.g. "#docfragment". +// Parameters: +// line - provides one source line to search in. +// prefix - provides the fragment marker prefix, for example "#docfragment". // -// Returns the list of the names found and error if prefix found without names. +// Returns: +// []string - fragment names found on the line. +// error - when prefix is found without valid names. func lookup(line string, prefix string) ([]string, error) { var unquotedNames []string if strings.Contains(line, prefix) { @@ -89,7 +100,14 @@ func lookup(line string, prefix string) ([]string, error) { return unquotedNames, nil } -// Returns the unquoted name from given quotedName. +// unquoteName removes quotes from a fragment marker name. +// +// Parameters: +// quotedName - provides a quoted fragment name. +// +// Returns: +// string - unquoted fragment name. +// error - when quotedName cannot be unquoted. func unquoteName(quotedName string) (string, error) { nameQuoted := quotedNamePattern.FindString(quotedName) nameCleaned, err := strconv.Unquote(nameQuoted) diff --git a/fragmentation/partition.go b/fragmentation/partition.go index f2285ae7..e0d14e2f 100644 --- a/fragmentation/partition.go +++ b/fragmentation/partition.go @@ -26,17 +26,17 @@ import "fmt" // In the resulting doc file, the partitions are joined by the Configuration.Separator. // StartPosition and EndPosition are both set to -1 by default as the default int value for them // is 0, which is wrong, because 0 is in the scope of possible values for them. -// -// StartPosition — an index from which the scope of partition exists. -// -// EndPosition — an index on which the scope of partition ends. type Partition struct { + // StartPosition is the first source-line index included in the partition. StartPosition int - EndPosition int + + // EndPosition is the last source-line index included in the partition. + EndPosition int } -// NewPartition returns a new Partition with both positions set to -1, as they should to be -// positive once set by a user. +// NewPartition returns a Partition with both positions unset as -1. +// +// Returns empty partition ready to receive start and end positions. func NewPartition() Partition { return Partition{ -1, @@ -45,12 +45,18 @@ func NewPartition() Partition { } // Select returns the partition-related lines from given lines. -// If EndPosition is not set, returns all the lines started from StartPosition. +// +// Parameters: +// lines - provides source lines indexed by StartPosition and EndPosition. +// +// Returns: +// []string - selected source lines. +// error - when configured positions are outside lines. func (p Partition) Select(lines []string) ([]string, error) { startPosition := p.StartPosition endPosition := p.EndPosition - // Verifying lines actually have those indexes. + // Verify source lines actually contain configured partition indexes. hasStartPosition := safeAccess(lines, startPosition) if !hasStartPosition { return nil, fmt.Errorf( @@ -74,6 +80,7 @@ func (p Partition) Select(lines []string) ([]string, error) { return lines[startPosition : endPosition+1], nil } +// safeAccess reports whether slice contains index. func safeAccess(slice []string, index int) bool { var hasIndex bool defer func() { diff --git a/fragmentation/resolver.go b/fragmentation/resolver.go index 6b21f43a..bdae4b92 100644 --- a/fragmentation/resolver.go +++ b/fragmentation/resolver.go @@ -36,7 +36,10 @@ const resolverCacheLimit = 100 // fragmentedFile stores cleaned source lines and parsed fragments for one source file. type fragmentedFile struct { - lines []string + // lines contains the cleaned source lines. + lines []string + + // fragments contains parsed fragments indexed by name. fragments map[string]Fragment } @@ -51,7 +54,17 @@ var resolverCache = newCache[absolutePath, fragmentedFile]( // 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. +// Named fragments are extracted directly from the source file on demand and +// cached by source file. +// +// Parameters: +// codePath - identifies the source file relative to configured source roots. +// fragmentName - selects a named fragment, or the whole file when empty. +// config - provides embedding configuration. +// +// Returns: +// []string - selected source lines. +// error - when the source file or fragment cannot be resolved. func ResolveContent( codePath string, fragmentName string, @@ -102,6 +115,14 @@ func missingFragmentLogMessage(fragmentName string, sourcePath absolutePath) str } // ResolveCodeFileReference returns a user-facing reference to the source file. +// +// Parameters: +// codePath - identifies the source file relative to configured source roots. +// config - provides embedding configuration. +// +// Returns: +// string - user-facing source file reference. +// error - when source resolution fails. func ResolveCodeFileReference(codePath string, config config.Configuration) (string, error) { source, found, err := resolveSource(codePath, config) if err != nil { diff --git a/indent/indent.go b/indent/indent.go index d95ff4f1..f2f15d01 100644 --- a/indent/indent.go +++ b/indent/indent.go @@ -25,17 +25,15 @@ import ( // MaxCommonIndentation finds the maximal common indentation of given lines. // -// If all given lines are empty, contain only whitespace, or there are no lines at all, -// returns zero. +// Parameters: +// lines - provides source lines to inspect. // -// lines — a list of lines which may or may not have leading whitespaces. -// -// Returns the maximum number of leading whitespaces among all lines except for the empty ones. +// Returns maximal common indentation, or zero when no non-blank lines exist. func MaxCommonIndentation(lines []string) int { indent := math.MaxInt32 for _, line := range lines { if strings.TrimSpace(line) != "" { - trimmedLine := strings.TrimLeft(line, "\n\t ") // Check if it changes a line in-place. + trimmedLine := strings.TrimLeft(line, "\n\t ") lineIndent := len(line) - len(trimmedLine) if lineIndent < indent { indent = lineIndent @@ -52,13 +50,14 @@ func MaxCommonIndentation(lines []string) int { // CutIndent reduces indentation to given redundantSpaces amount. // -// lines — a list of strings representing the lines to process. -// -// redundantSpaces — the number of leading spaces to remove from each line. -// +// It copies lines before trimming, so the input slice is not modified. // If a line is shorter than redundantSpaces, the whole line is removed. // -// Returns processed lines. +// Parameters: +// lines - provides source lines to trim. +// redundantSpaces - provides the maximum indentation to remove. +// +// Returns source lines with indentation removed. func CutIndent(lines []string, redundantSpaces int) []string { linesChanged := make([]string, len(lines)) copy(linesChanged, lines) diff --git a/indent/indent_test.go b/indent/indent_test.go index 41143c69..a932487f 100644 --- a/indent/indent_test.go +++ b/indent/indent_test.go @@ -27,6 +27,7 @@ import ( . "github.com/onsi/gomega" ) +// TestIndent runs the indentation helper test suite. func TestIndent(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Data Suite") diff --git a/logging/error.go b/logging/error.go index 03aabb7a..b08bf28c 100644 --- a/logging/error.go +++ b/logging/error.go @@ -24,6 +24,12 @@ import ( ) // FormatError formats a single error inline and joined errors as a bullet list. +// +// Parameters: +// message - provides the formatted error prefix. +// err - provides the error to format. +// +// Returns formatted error text. func FormatError(message string, err error) string { errs := flattenedErrors(err) if len(errs) <= 1 { diff --git a/logging/logger.go b/logging/logger.go index 67d0f3fe..4917284e 100644 --- a/logging/logger.go +++ b/logging/logger.go @@ -41,12 +41,22 @@ const fileScheme = "file" // // Only messages with level greater than or equal to Handler.Level are printed. type Handler struct { - Level slog.Level + // Level is the minimum enabled logging level. + Level slog.Level + + // attributes contains attributes added through WithAttrs. attributes []slog.Attr - groups []string + + // groups contains group names added through WithGroup. + groups []string } // Enabled returns true if the log level is greater than or equal to the Handler's Level. +// +// Parameters: +// level - provides the record level to check. +// +// Returns true when level is enabled. func (h *Handler) Enabled(_ context.Context, level slog.Level) bool { return level >= h.Level } @@ -54,6 +64,11 @@ func (h *Handler) Enabled(_ context.Context, level slog.Level) bool { // Handle formats the log record and writes it to standard output in a simple readable format: // // HH:MM:SS LEVEL - message +// +// Parameters: +// record - provides the slog record to print. +// +// Returns nil. func (h *Handler) Handle(_ context.Context, record slog.Record) error { time := record.Time.Format("15:04:05") fmt.Printf("%s %s - %s\n", @@ -81,6 +96,11 @@ func (h *Handler) Handle(_ context.Context, record slog.Record) error { } // WithAttrs returns a copy of the handler with extra attributes. +// +// Parameters: +// attributes - provides attributes for future records. +// +// Returns derived slog handler. func (h *Handler) WithAttrs(attributes []slog.Attr) slog.Handler { newHandler := *h newHandler.attributes = append(append([]slog.Attr{}, h.attributes...), attributes...) @@ -89,6 +109,11 @@ func (h *Handler) WithAttrs(attributes []slog.Attr) slog.Handler { } // WithGroup returns a copy of the handler for a new group. +// +// Parameters: +// name - provides the group name. +// +// Returns derived slog handler. func (h *Handler) WithGroup(name string) slog.Handler { newHandler := *h newHandler.groups = append(append([]string{}, h.groups...), name) @@ -97,6 +122,11 @@ func (h *Handler) WithGroup(name string) slog.Handler { } // FileReference returns a clickable file URL when the path can be made absolute. +// +// Parameters: +// path - provides a local file path. +// +// Returns file URL, or original path when absolute resolution fails. func FileReference(path string) string { absPath, err := filepath.Abs(path) if err != nil { @@ -107,6 +137,12 @@ func FileReference(path string) string { } // FileReferenceWithLine returns a clickable file URL with an optional line suffix. +// +// Parameters: +// path - provides a local file path. +// line - provides an optional one-based line number. +// +// Returns file reference with line suffix when line is positive. func FileReferenceWithLine(path string, line int) string { reference := FileReference(path) if line <= 0 { @@ -160,6 +196,9 @@ func isWindowsDrivePath(path string) bool { // or invokes other methods that may call panic. // // defer HandlePanic(withStacktrace) +// +// Parameters: +// withStacktrace - controls whether a panic stack trace is printed. func HandlePanic(withStacktrace bool) { if r := recover(); r != nil { fmt.Println(formatPanicMessage(r)) diff --git a/main.go b/main.go index 4cc775f6..c0bee9dd 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ var Version = strings.TrimSpace(versionFile) // then the checking for up-to-date is performed. If it is set to 'embed', // the embedding is performed. // -// EmbeddingInstruction is the process that consists of the following steps: +// Embedding is the process that consists of the following steps: // - the code fragments are extracted from the code files; // - the docs files are scanned for tags; // - for each tag, the code fragments are embedded into the docs. The embedding diff --git a/showcase/README.md b/showcase/README.md index 86eec3a2..8a151999 100644 --- a/showcase/README.md +++ b/showcase/README.md @@ -19,7 +19,7 @@ sync with the application. - [Negative examples](embedding/negative/docs): intentionally broken examples that document diagnostics. -## Run The Showcase +## Run the Showcase Run commands from the repository root. diff --git a/showcase/showcase_test.go b/showcase/showcase_test.go index 3dc5a164..ab95afde 100644 --- a/showcase/showcase_test.go +++ b/showcase/showcase_test.go @@ -113,14 +113,22 @@ var _ = Describe("Showcase", func() { // negativeShowcaseCase describes one intentionally broken showcase document. type negativeShowcaseCase struct { - name string - doc string + // name identifies the test case. + name string + + // doc is the path to the broken showcase document. + doc string + + // expected contains substrings expected in command output. expected []string } // namedSource is the named code source path. type namedSource struct { + // name identifies the source root. name string + + // path is the source root path. path string } diff --git a/type/named_path_list.go b/type/named_path_list.go index 474d10de..7d019f8d 100644 --- a/type/named_path_list.go +++ b/type/named_path_list.go @@ -27,7 +27,10 @@ import ( // NamedPath represents a path that may optionally have a name. type NamedPath struct { + // Name is the optional path identifier. Name string `yaml:"name"` + + // Path is the filesystem path. Path string `yaml:"path"` } @@ -55,6 +58,11 @@ type NamedPathList []NamedPath // path: "../examples" // - name: runtime // path: "../runtime" +// +// Parameters: +// value - provides the YAML node to decode. +// +// Returns an error when the node kind or sequence item format is unsupported. func (pathList *NamedPathList) UnmarshalYAML(value *yaml.Node) error { switch value.Kind { case yaml.ScalarNode: diff --git a/type/string_list.go b/type/string_list.go index d8e1bba7..8485d796 100644 --- a/type/string_list.go +++ b/type/string_list.go @@ -42,6 +42,11 @@ type StringList []string // - a // - b // - c +// +// Parameters: +// value - provides the YAML node to decode. +// +// Returns an error when the node kind is unsupported. func (s *StringList) UnmarshalYAML(value *yaml.Node) error { switch value.Kind { case yaml.ScalarNode: