Documentation
¶
Overview ¶
Package individualizer provides a flexible token replacement and file manipulation system.
The package consists of three main components:
Individualizer Interface The main interface that defines how files are processed and individualized:
type Individualizer interface { Apply(userGroup UserGroup, files map[string][]byte) (map[string][]byte, error) Preview(userGroup UserGroup, fileContent []byte) ([]byte, error) }
Manipulator Interface Defines how different file types are handled:
type Manipulator interface { CanHandle(filePath string, content []byte) bool Manipulate(filePath string, content []byte, tokens map[string]string) ([]ManipulatedFile, error) }
ManipulatorManager Manages multiple manipulators and applies them based on file type.
Basic Usage:
// Create individualizer with $ as token delimiter
individualizer := NewTokenBasedIndividualizer("$", "$")
// Add custom tokens
individualizer.SetTokens(map[string]string{
"VEHICLE": "car",
"COLOR": "red",
})
// Process files
result, err := individualizer.Apply(userGroup, files)
Extending the System:
1. Creating a New Manipulator:
type MyManipulator struct {
// Add necessary fields
}
func (m *MyManipulator) CanHandle(filePath string, content []byte) bool {
// Implement file type detection logic
return strings.HasSuffix(filePath, ".myext")
}
func (m *MyManipulator) Manipulate(filePath string, content []byte, tokens map[string]string) ([]ManipulatedFile, error) {
// Implement content manipulation
return []ManipulatedFile{{
Path: processedPath,
Content: processedContent,
}}, nil
}
2. Using the New Manipulator:
// Create individualizer
individualizer := NewTokenBasedIndividualizer("$", "$")
// Add custom manipulator
myManipulator := NewMyManipulator()
individualizer.AddManipulator(myManipulator)
Built-in Manipulators:
1. TokenManipulator
- Basic text-based token replacement
- Configurable file extensions and token delimiters
Example:
manipulator := NewTokenManipulator([]string{".txt", ".md"}, "$", "$")
2. UMLManipulator
- Handles UML diagram files (.uxf, .uml)
- Replaces tokens in diagrams
- Preserves the original individualized UML file
- Emits an additional rendered image artifact
Example:
manipulator := NewUMLManipulator("$", "$", "/tmp/uml", "jpg")
Best Practices:
1. File Type Detection
- Implement CanHandle() to clearly define which files your manipulator handles
- Consider both file extension and content when appropriate
- Return false if unsure to let other manipulators handle the file
2. Token Replacement
- Always handle the case where tokens don't exist in the content
- Consider the context of the replacement (e.g., XML vs plain text)
- Preserve file formatting and structure
3. Error Handling
- Return meaningful errors that help diagnose issues
- Clean up temporary files and resources
- Handle edge cases (empty content, invalid tokens)
4. Path Manipulation
- Consider platform-specific path separators
- Handle nested paths correctly
- Maintain file extension conventions
Example: Creating a JSON Manipulator:
type JSONManipulator struct {
TokenStart string
TokenEnd string
}
func (m *JSONManipulator) CanHandle(filePath string, _ []byte) bool {
return strings.HasSuffix(filePath, ".json")
}
func (m *JSONManipulator) Manipulate(filePath string, content []byte, tokens map[string]string) ([]ManipulatedFile, error) {
var data interface{}
if err := json.Unmarshal(content, &data); err != nil {
return nil, NewJSONParsingError(err)
}
// Process the JSON structure recursively
processed := processJSON(data, tokens)
result, err := json.MarshalIndent(processed, "", " ")
if err != nil {
return nil, err
}
return []ManipulatedFile{{
Path: filePath,
Content: result,
}}, nil
}
For more examples and detailed documentation, see the test files in the package.
Package individualizer provides error types for the individualizer package.
Index ¶
- Constants
- func ApplyDynamicTemplate(namingPattern string, data TemplateData) (string, error)
- func BuildMetaTokens(base map[string]string, ug UserGroup) map[string]string
- func CleanGitLabProjectName(name string) string
- func Creation(format string) string
- func GenerateUniqueLocalPath(uuid string, projectName string) string
- func Hash(input string) string
- func MetaTokenNames() []string
- func Now(format string) string
- func ProjectNameSlug(label string) string
- func UsesStableProjectIndex(namingPattern string) bool
- func Uuid(providedUuid string) string
- type ErrorType
- type Individualizer
- type IndividualizerError
- func NewDefaultManipulatorFailureError(filePath string, err error) *IndividualizerError
- func NewDuplicateProjectIndexError(index int, existingUUID, nextUUID string) *IndividualizerError
- func NewEmptyUserGroupError() *IndividualizerError
- func NewFileManipulationError(path string, err error) *IndividualizerError
- func NewImageNotFoundError(imagePath string, err error) *IndividualizerError
- func NewImageReadError(err error) *IndividualizerError
- func NewIndividualizerError(errorType ErrorType, message string, err error) *IndividualizerError
- func NewJSONParsingError(err error) *IndividualizerError
- func NewNoManipulatorFoundError(filePath string, fileSize int, availableManipulators int) *IndividualizerError
- func NewNoManipulatorFoundForPathError(filePath string, availableManipulators int) *IndividualizerError
- func NewNoVariationFoundError(objectKey, selectedVariationID string) *IndividualizerError
- func NewPathManipulationError(path string, err error) *IndividualizerError
- func NewProjectIndexConflictError(uuid string, existing, next int) *IndividualizerError
- func NewProjectNameIndexError(message string) *IndividualizerError
- func NewSpecializedManipulatorFailureError(filePath string, err error) *IndividualizerError
- func NewTempDirectoryCreationError(err error) *IndividualizerError
- func NewTempFileWriteError(err error) *IndividualizerError
- func NewTemplateExecutionError(template string, data interface{}, err error) *IndividualizerError
- func NewTemplateParsingError(template string, err error) *IndividualizerError
- func NewUMLCommandNotFoundError() *IndividualizerError
- func NewUMLConversionError(err error) *IndividualizerError
- func NewUMLConversionFailedError(command []string, err error) *IndividualizerError
- func NewUMLJarExtractionError() *IndividualizerError
- func NewUMLJarNotFoundError(os string) *IndividualizerError
- func NewUMLJarNotFoundForConversionError(err error) *IndividualizerError
- func NewUMLScriptReadError(err error) *IndividualizerError
- func NewVariationConfigLoadError(err error) *IndividualizerError
- type ManipulatedFile
- type Manipulator
- type ManipulatorManager
- type ProjectIndexAllocator
- type ProjectIndexRecord
- type StandardManipulatorOptions
- type TemplateData
- type TokenBasedIndividualizer
- func (i *TokenBasedIndividualizer) AddManipulator(manipulator Manipulator)
- func (i *TokenBasedIndividualizer) Apply(userGroup UserGroup, files map[string][]byte, targetName string) (map[string][]byte, error)
- func (i *TokenBasedIndividualizer) Preview(userGroup UserGroup, fileContent []byte) ([]byte, error)
- func (i *TokenBasedIndividualizer) SetTokens(tokens map[string]string)
- type TokenManipulator
- type UMLConverter
- type UMLManipulator
- func NewUMLManipulator(tokenStart, tokenEnd, tempDir, imageFormat string) *UMLManipulator
- func NewUMLManipulatorWithConverter(tokenStart, tokenEnd, tempDir, imageFormat string, ...) *UMLManipulator
- func NewUMLManipulatorWithOptions(tokenStart, tokenEnd, tempDir, imageFormat string, ...) *UMLManipulator
- type User
- type UserGroup
Constants ¶
const ( // UMLArtifactsOff disables the built-in UML artifact processor. UMLArtifactsOff = "off" // UMLArtifactsSource emits only the individualized UML source. UMLArtifactsSource = "source" // UMLArtifactsImage emits only the rendered image artifact. UMLArtifactsImage = "image" // UMLArtifactsBoth emits both the source UML file and a rendered image artifact. UMLArtifactsBoth = "both" )
Variables ¶
This section is empty.
Functions ¶
func ApplyDynamicTemplate ¶
func ApplyDynamicTemplate(namingPattern string, data TemplateData) (string, error)
ApplyDynamicTemplate applies a template string with dynamic functions
func BuildMetaTokens ¶
BuildMetaTokens merges well-known meta tokens (UUID/RepositoryId/USER) into the provided base map when the values are available from the user group. Existing keys in base take precedence; meta tokens are only added if missing.
func CleanGitLabProjectName ¶
CleanGitLabProjectName cleans up a project name to meet GitLab requirements. It replaces umlauts, removes illegal characters, and cleans up hyphens.
func Creation ¶
Creation returns the current date and time in the specified format - it changes between calls.
func GenerateUniqueLocalPath ¶
GenerateUniqueLocalPath creates a unique path segment for a project that prevents file conflicts when writing files with the same paths in different projects
func Hash ¶
Hash creates a SHA-256 hash of the input string and returns it as a hex-encoded string If the input is empty, a new UUID is generated and used as input.
func MetaTokenNames ¶
func MetaTokenNames() []string
MetaTokenNames returns the list of well-known meta token names that are not defined via variation.json, but are supplied by the individualization flow. These names should be considered valid placeholders across commands.
func Now ¶
Now returns the current date and time in the specified format - it doesn't change between calls.
func ProjectNameSlug ¶
ProjectNameSlug returns a lowercase, GitLab-safe slug for a label.
func UsesStableProjectIndex ¶
UsesStableProjectIndex reports whether a repository naming template invokes the zero-argument index function or its legacy autoincrement alias. Calls to the standard index form, such as {{index .Usernames 0}}, do not opt in.
Types ¶
type ErrorType ¶
type ErrorType int
ErrorType defines domain-based error categories for the individualizer package. Consolidated from 22+ specific types to 6 semantic domains.
const ( // ErrValidation indicates input validation errors. ErrValidation ErrorType = iota // ErrTemplate indicates template parsing or execution errors. ErrTemplate // ErrFile indicates file manipulation errors. ErrFile // ErrConfig indicates configuration loading errors. ErrConfig // ErrUML indicates UML conversion errors. ErrUML // ErrManipulator indicates manipulator processing errors. ErrManipulator )
type Individualizer ¶
type Individualizer interface {
// Apply transforms the provided files for a specific user group and target.
// It takes a collection of files (path -> content) and returns the modified versions
// after applying individualization rules such as token replacement and file manipulations.
//
// Parameters:
// - userGroup: The target user group containing user information and UUID
// - files: Map of file paths to their content that should be individualized
// - targetName: The name of the distribution target for context-specific transformations
//
// Returns the modified files with the same path structure, or an error if individualization fails.
Apply(userGroup UserGroup, files map[string][]byte, targetName string) (map[string][]byte, error)
// Preview generates a preview of how a single file would be transformed for a user group.
// This is primarily used for debugging, logging, and validation purposes to understand
// what changes would be applied without performing the full individualization process.
//
// Parameters:
// - userGroup: The target user group for the preview
// - fileContent: The original file content to preview
//
// Returns the transformed content or an error if preview generation fails.
Preview(userGroup UserGroup, fileContent []byte) ([]byte, error)
}
Individualizer defines the interface for customizing files and content for specific user groups. This is the core abstraction for the individualization system, allowing different implementation strategies for token replacement, file manipulation, and content customization.
The individualizer operates on collections of files and applies transformations based on user group data, enabling the creation of personalized versions of template content for different teams or individuals.
type IndividualizerError ¶
type IndividualizerError struct {
ErrorType ErrorType
// Message is the user-facing message; it must not contain Path.
Message string
// Path is the file the error is about, empty when it is about nothing.
Path string
Err error // Wrapped error
}
IndividualizerError represents a specific error in the individualizer package
func NewDefaultManipulatorFailureError ¶
func NewDefaultManipulatorFailureError(filePath string, err error) *IndividualizerError
func NewDuplicateProjectIndexError ¶
func NewDuplicateProjectIndexError(index int, existingUUID, nextUUID string) *IndividualizerError
func NewEmptyUserGroupError ¶
func NewEmptyUserGroupError() *IndividualizerError
func NewFileManipulationError ¶
func NewFileManipulationError(path string, err error) *IndividualizerError
func NewImageNotFoundError ¶
func NewImageNotFoundError(imagePath string, err error) *IndividualizerError
func NewImageReadError ¶
func NewImageReadError(err error) *IndividualizerError
func NewIndividualizerError ¶
func NewIndividualizerError(errorType ErrorType, message string, err error) *IndividualizerError
NewIndividualizerError creates a new IndividualizerError
func NewJSONParsingError ¶
func NewJSONParsingError(err error) *IndividualizerError
func NewNoManipulatorFoundError ¶
func NewNoManipulatorFoundError(filePath string, fileSize int, availableManipulators int) *IndividualizerError
func NewNoManipulatorFoundForPathError ¶
func NewNoManipulatorFoundForPathError(filePath string, availableManipulators int) *IndividualizerError
func NewNoVariationFoundError ¶
func NewNoVariationFoundError(objectKey, selectedVariationID string) *IndividualizerError
func NewPathManipulationError ¶
func NewPathManipulationError(path string, err error) *IndividualizerError
func NewProjectIndexConflictError ¶
func NewProjectIndexConflictError(uuid string, existing, next int) *IndividualizerError
func NewProjectNameIndexError ¶
func NewProjectNameIndexError(message string) *IndividualizerError
func NewSpecializedManipulatorFailureError ¶
func NewSpecializedManipulatorFailureError(filePath string, err error) *IndividualizerError
func NewTempDirectoryCreationError ¶
func NewTempDirectoryCreationError(err error) *IndividualizerError
func NewTempFileWriteError ¶
func NewTempFileWriteError(err error) *IndividualizerError
func NewTemplateExecutionError ¶
func NewTemplateExecutionError(template string, data interface{}, err error) *IndividualizerError
func NewTemplateParsingError ¶
func NewTemplateParsingError(template string, err error) *IndividualizerError
func NewUMLCommandNotFoundError ¶
func NewUMLCommandNotFoundError() *IndividualizerError
func NewUMLConversionError ¶
func NewUMLConversionError(err error) *IndividualizerError
func NewUMLConversionFailedError ¶
func NewUMLConversionFailedError(command []string, err error) *IndividualizerError
func NewUMLJarExtractionError ¶
func NewUMLJarExtractionError() *IndividualizerError
func NewUMLJarNotFoundError ¶
func NewUMLJarNotFoundError(os string) *IndividualizerError
func NewUMLJarNotFoundForConversionError ¶
func NewUMLJarNotFoundForConversionError(err error) *IndividualizerError
func NewUMLScriptReadError ¶
func NewUMLScriptReadError(err error) *IndividualizerError
func NewVariationConfigLoadError ¶
func NewVariationConfigLoadError(err error) *IndividualizerError
func (*IndividualizerError) Base ¶
func (e *IndividualizerError) Base() *apperror.BaseError
Base exposes the message, cause, and path to the shared error helpers. The package still uses its own error struct rather than apperror.Carrier, so the category stays the one an uncategorized error had before.
func (*IndividualizerError) Error ¶
func (e *IndividualizerError) Error() string
Error implements the error interface
type ManipulatedFile ¶
type ManipulatedFile struct {
Path string
// SkipContentPathNormalization preserves raw bytes for generated binary artifacts such as images.
SkipContentPathNormalization bool
Content []byte
}
ManipulatedFile represents one emitted file produced by a manipulator.
type Manipulator ¶
type Manipulator interface {
// CanHandle determines whether this manipulator can process the given file.
// This method examines the file path and optionally the content to decide if this
// manipulator is appropriate for handling the file type.
//
// Parameters:
// - filePath: The path of the file to potentially manipulate
// - content: The raw content of the file (may be nil for path-only checks)
//
// Returns true if this manipulator can handle the file, false otherwise.
CanHandle(filePath string, content []byte) bool
// Manipulate performs the actual transformation on the file.
// This is where the core individualization logic is applied, such as token replacement,
// content filtering, or format-specific transformations. A manipulator may emit more than
// one output file for a single input file.
//
// Parameters:
// - filePath: The path of the file being manipulated (for context)
// - content: The original file content to transform
// - tokens: Map of token names to replacement values for individualization
//
// Returns the transformed output files or an error if manipulation fails.
Manipulate(filePath string, content []byte, tokens map[string]string) ([]ManipulatedFile, error)
}
Manipulator defines the interface for content and path manipulation within the individualization system. Manipulators are responsible for handling specific file types and applying customizations such as token replacement, content transformations, and path modifications.
Different manipulators can be registered to handle different file types (e.g., text files, UML diagrams, JSON) allowing for specialized processing logic while maintaining a consistent interface.
type ManipulatorManager ¶
type ManipulatorManager struct {
// contains filtered or unexported fields
}
ManipulatorManager manages multiple manipulators and applies them to files
func NewManipulatorManager ¶
func NewManipulatorManager(defaultManipulator Manipulator) *ManipulatorManager
NewManipulatorManager creates a new ManipulatorManager
func (*ManipulatorManager) AddManipulator ¶
func (m *ManipulatorManager) AddManipulator(manipulator Manipulator)
AddManipulator adds a new manipulator to the manager
func (*ManipulatorManager) ManipulateFile ¶
func (m *ManipulatorManager) ManipulateFile(filePath string, content []byte, tokens map[string]string) ([]ManipulatedFile, error)
ManipulateFile applies the appropriate manipulator to the file
type ProjectIndexAllocator ¶
type ProjectIndexAllocator struct {
// contains filtered or unexported fields
}
ProjectIndexAllocator keeps stable, one-based repository indices keyed by group UUID. New indices are allocated only when requested.
func NewProjectIndexAllocator ¶
func NewProjectIndexAllocator(existing map[string]int) *ProjectIndexAllocator
NewProjectIndexAllocator seeds an allocator from persisted UUID indices.
func NewProjectIndexAllocatorFromRecords ¶
func NewProjectIndexAllocatorFromRecords(records []ProjectIndexRecord) (*ProjectIndexAllocator, error)
NewProjectIndexAllocatorFromRecords validates persisted UUID/index pairs and rejects divergent UUID assignments or duplicate indices.
type ProjectIndexRecord ¶
ProjectIndexRecord is a persisted UUID/index pair from any repository target.
type StandardManipulatorOptions ¶
StandardManipulatorOptions configures the built-in manipulators used by the default individualizer.
func DefaultStandardManipulatorOptions ¶
func DefaultStandardManipulatorOptions() StandardManipulatorOptions
DefaultStandardManipulatorOptions returns the default built-in manipulator settings.
type TemplateData ¶
type TemplateData struct {
Usernames []string
Group string
Uuid string
Index int
Label string
Slug string
Metadata map[string]string
}
TemplateData contains data available to naming templates for repositories.
func NewProjectNameTemplateData ¶
func NewProjectNameTemplateData(uuid string, usernames []string, label string, metadata map[string]string) TemplateData
NewProjectNameTemplateData builds the repository naming template data from group metadata while preserving UUID as the stable fallback identifier.
func NewProjectNameTemplateDataWithIndex ¶
func NewProjectNameTemplateDataWithIndex(uuid string, usernames []string, label string, metadata map[string]string, index int) TemplateData
NewProjectNameTemplateDataWithIndex builds repository naming template data with an optional stable, persisted group index.
type TokenBasedIndividualizer ¶
type TokenBasedIndividualizer struct {
// contains filtered or unexported fields
}
TokenBasedIndividualizer implements the Individualizer interface using a token replacement system. It uses a collection of manipulators to handle different file types and performs customizations based on configurable token patterns and replacement values.
This is the primary implementation used by the divekit system for content individualization.
func NewIndividualizerWithStandardManipulators ¶
func NewIndividualizerWithStandardManipulators(tokenStart, tokenEnd string, opts StandardManipulatorOptions) *TokenBasedIndividualizer
NewIndividualizerWithStandardManipulators creates a new TokenBasedIndividualizer with all standard manipulators configured with the given token delimiters
func NewTokenBasedIndividualizer ¶
func NewTokenBasedIndividualizer(tokenStart, tokenEnd string) *TokenBasedIndividualizer
NewTokenBasedIndividualizer creates a new TokenBasedIndividualizer
func (*TokenBasedIndividualizer) AddManipulator ¶
func (i *TokenBasedIndividualizer) AddManipulator(manipulator Manipulator)
AddManipulator adds a specialized manipulator
func (*TokenBasedIndividualizer) Apply ¶
func (i *TokenBasedIndividualizer) Apply(userGroup UserGroup, files map[string][]byte, targetName string) (map[string][]byte, error)
Apply implements the Individualizer interface
func (*TokenBasedIndividualizer) Preview ¶
func (i *TokenBasedIndividualizer) Preview(userGroup UserGroup, fileContent []byte) ([]byte, error)
Preview implements the Individualizer interface
func (*TokenBasedIndividualizer) SetTokens ¶
func (i *TokenBasedIndividualizer) SetTokens(tokens map[string]string)
SetTokens sets the token-value pairs for replacement
type TokenManipulator ¶
type TokenManipulator struct {
// FileExtensions defines which file extensions this manipulator handles.
// If empty, it handles all files as text files. Extensions should include the dot (e.g., ".txt", ".java").
FileExtensions []string
// TokenStart defines the start delimiter of a token (e.g., "$", "{{", "${").
// This marker identifies the beginning of a replaceable token in the content.
TokenStart string
// TokenEnd defines the end of a token (e.g. "$" or "}}")
TokenEnd string
}
TokenManipulator implements the Manipulator interface for basic text-based token replacement. This is the standard manipulator for handling text files, configuration files, and source code that contains token placeholders to be replaced with individualized values.
The manipulator supports configurable token delimiters and can be restricted to specific file extensions.
func NewTokenManipulator ¶
func NewTokenManipulator(fileExts []string, tokenStart, tokenEnd string) *TokenManipulator
NewTokenManipulator creates a new TokenManipulator with the given configuration
func (*TokenManipulator) CanHandle ¶
func (t *TokenManipulator) CanHandle(filePath string, _ []byte) bool
CanHandle implements the Manipulator interface
func (*TokenManipulator) Manipulate ¶
func (t *TokenManipulator) Manipulate(filePath string, content []byte, tokens map[string]string) ([]ManipulatedFile, error)
Manipulate implements the Manipulator interface
type UMLConverter ¶
UMLConverter converts UML files to image files using a concrete backend.
type UMLManipulator ¶
type UMLManipulator struct {
// TokenStart defines the start of a token (e.g. "$" or "{{")
TokenStart string
// TokenEnd defines the end of a token (e.g. "$" or "}}")
TokenEnd string
// TempDir is where temporary files will be stored during processing
TempDir string
// ImageFormat specifies the output image format (e.g., "jpg", "png")
ImageFormat string
// PreserveSource controls whether the individualized UML source file is emitted.
PreserveSource bool
// RenderImage controls whether a rendered image artifact is emitted.
RenderImage bool
Converter UMLConverter
}
UMLManipulator is a specialized manipulator for UML files
func NewUMLManipulator ¶
func NewUMLManipulator(tokenStart, tokenEnd, tempDir, imageFormat string) *UMLManipulator
NewUMLManipulator creates a new UMLManipulator with default converter
func NewUMLManipulatorWithConverter ¶
func NewUMLManipulatorWithConverter(tokenStart, tokenEnd, tempDir, imageFormat string, preserveSource, renderImage bool, converter UMLConverter) *UMLManipulator
NewUMLManipulatorWithConverter creates a new UMLManipulator with custom converter for dependency injection
func NewUMLManipulatorWithOptions ¶
func NewUMLManipulatorWithOptions(tokenStart, tokenEnd, tempDir, imageFormat string, preserveSource, renderImage bool) *UMLManipulator
NewUMLManipulatorWithOptions creates a UMLManipulator with explicit artifact behavior.
func (*UMLManipulator) CanHandle ¶
func (u *UMLManipulator) CanHandle(filePath string, _ []byte) bool
CanHandle implements the Manipulator interface
func (*UMLManipulator) Manipulate ¶
func (u *UMLManipulator) Manipulate(filePath string, content []byte, tokens map[string]string) ([]ManipulatedFile, error)
Manipulate implements the Manipulator interface
type User ¶
type User struct {
Username string // Unique username used for personalization and identification
}
User represents an individual user within a user group. This contains the essential user information needed for individualization processes.
type UserGroup ¶
type UserGroup struct {
Users []User // List of users belonging to this group
Uuid string // Unique identifier for this user group
}
UserGroup represents a collection of users that should receive the same individualized content. Each user group has a unique identifier and contains the user information needed for customization.