individualizer

package
v0.0.0-...-f4d26ab Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: Apache-2.0 Imports: 21 Imported by: 0

README

Individualizer Package

This package contains the internal token-replacement and file-manipulation machinery used during individualization and distribution.

It is an internal implementation package, not a public library.

Responsibilities

The package is responsible for:

  • token replacement in file contents
  • token replacement in paths
  • delegating file-specific behavior to manipulators
  • handling richer formats such as UML artifacts where needed

Role In Divekit

The individualizer sits inside the content-transformation part of the distribution flow.

Higher-level code prepares:

  • the files to transform
  • the resolved token set
  • distribution or member context

The individualizer applies the transformations and returns the resulting file set.

Design Notes

  • keep file-format-specific logic in focused manipulators
  • keep token and path replacement behavior deterministic
  • avoid UI, CLI, or repository-provider concerns in this package
  • docs/architecture/config-resolution.md
  • docs/architecture/module-boundaries.md

Documentation

Overview

Package individualizer provides a flexible token replacement and file manipulation system.

The package consists of three main components:

  1. 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) }

  2. 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) }

  3. 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

View Source
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

func BuildMetaTokens(base map[string]string, ug UserGroup) map[string]string

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

func CleanGitLabProjectName(name string) string

CleanGitLabProjectName cleans up a project name to meet GitLab requirements. It replaces umlauts, removes illegal characters, and cleans up hyphens.

func Creation

func Creation(format string) string

Creation returns the current date and time in the specified format - it changes between calls.

func GenerateUniqueLocalPath

func GenerateUniqueLocalPath(uuid string, projectName string) string

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

func Hash(input string) string

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

func Now(format string) string

Now returns the current date and time in the specified format - it doesn't change between calls.

func ProjectNameSlug

func ProjectNameSlug(label string) string

ProjectNameSlug returns a lowercase, GitLab-safe slug for a label.

func UsesStableProjectIndex

func UsesStableProjectIndex(namingPattern string) bool

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.

func Uuid

func Uuid(providedUuid string) string

Uuid generates a new uuid if the input is empty.

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
)

func (ErrorType) String

func (t ErrorType) String() string

String returns a human-readable name for the error type.

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

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.

func (*ProjectIndexAllocator) IndexFor

func (a *ProjectIndexAllocator) IndexFor(uuid string, allocate bool) int

IndexFor returns an existing index for uuid. When allocate is true, a missing index is assigned once and retained by the allocator.

type ProjectIndexRecord

type ProjectIndexRecord struct {
	UUID  string
	Index int
}

ProjectIndexRecord is a persisted UUID/index pair from any repository target.

type StandardManipulatorOptions

type StandardManipulatorOptions struct {
	UMLMode        string
	UMLImageFormat string
}

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

type UMLConverter interface {
	Convert(inputPath, outputPath string) error
}

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.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL