UNPKG

fake-it-til-you-git

Version:

A modern CLI tool to generate fake Git commit history for your GitHub/GitLab profile

690 lines (683 loc) 15.6 kB
/** * Pattern generation utilities for creating visual patterns in GitHub contribution graphs * This module handles converting various pattern types into commit intensity maps. */ /** * Predefined patterns as ASCII art * Each pattern is defined as a string where: * - ' ' (space) = no commits (0) * - '.' = low intensity (1) * - 'o' = medium intensity (2) * - 'O' = high intensity (3) * - '█' = maximum intensity (4) */ const PRESET_PATTERNS = { heart: ` ███ ███ █████████ █████████ ███████ █████ ███ █ `, star: ` █ ███ █O█ O███████O █████████ O███████O █████ ███ █ `, diamond: ` █ ███ █████ ███████ █████ ███ █ `, square: ` ███████ █ooooo█ █ooooo█ █ooooo█ █ooooo█ ███████ `, triangle: ` █ ███ █████ ███████ █████████ ███████████ `, cross: ` █ █ █ ███████ █ █ █ `, wave: ` █ █ ██ ██ ██ ██ ██ ██ ███ ███ ██ ██ ██ ██ ██ ██ █ █ `, }; /** * Text patterns for letters and numbers * Each character is 5x7 pixels for readability */ const TEXT_PATTERNS = { 'A': ` ███ █ █ █ █ ███████ █ █ █ █ █ █ `, 'B': ` ██████ █ █ █ █ ██████ █ █ █ █ ██████ `, 'C': ` ██████ █ █ █ █ █ ██████ `, 'D': ` ██████ █ █ █ █ █ █ █ █ █ █ ██████ `, 'E': ` ███████ █ █ ██████ █ █ ███████ `, 'F': ` ███████ █ █ ██████ █ █ █ `, 'G': ` ██████ █ █ █ ████ █ █ █ █ ██████ `, 'H': ` █ █ █ █ █ █ ███████ █ █ █ █ █ █ `, 'I': ` ███████ █ █ █ █ █ ███████ `, 'J': ` ███████ █ █ █ █ █ █ █████ `, 'K': ` █ █ █ █ █ █ ████ █ █ █ █ █ █ `, 'L': ` █ █ █ █ █ █ ███████ `, 'M': ` █ █ ██ ██ █ █ █ █ █ █ █ █ █ █ █ █ █ `, 'N': ` █ █ ██ █ █ █ █ █ █ █ █ █ █ █ ██ █ █ `, 'O': ` █████ █ █ █ █ █ █ █ █ █ █ █████ `, 'P': ` ██████ █ █ █ █ ██████ █ █ █ `, 'Q': ` █████ █ █ █ █ █ █ █ █ █ █ ██ ██████ █ `, 'R': ` ██████ █ █ █ █ ██████ █ █ █ █ █ █ `, 'S': ` ██████ █ █ █████ █ █ ██████ `, 'T': ` ███████ █ █ █ █ █ █ `, 'U': ` █ █ █ █ █ █ █ █ █ █ █ █ █████ `, 'V': ` █ █ █ █ █ █ █ █ █ █ █ █ █ `, 'W': ` █ █ █ █ █ █ █ █ █ █ █ █ █ ██ ██ █ █ `, 'X': ` █ █ █ █ █ █ █ █ █ █ █ █ █ `, 'Y': ` █ █ █ █ █ █ █ █ █ █ `, 'Z': ` ███████ █ █ █ █ █ ███████ `, '0': ` █████ █ █ █ ██ █ █ █ █ █ █ ██ █ █████ `, '1': ` █ ██ █ █ █ █ ███████ `, '2': ` █████ █ █ █ █████ █ █ ███████ `, '3': ` █████ █ █ █ █████ █ █ █ █████ `, '4': ` █ █ █ █ █ █ ███████ █ █ █ `, '5': ` ███████ █ █ ██████ █ █ █ █████ `, '6': ` █████ █ █ █ ██████ █ █ █ █ █████ `, '7': ` ███████ █ █ █ █ █ █ `, '8': ` █████ █ █ █ █ █████ █ █ █ █ █████ `, '9': ` █████ █ █ █ █ ██████ █ █ █ █████ `, ' ': ` `, }; /** * Convert a character to its intensity value */ function charToIntensity(char) { switch (char) { case ' ': return 0; case '.': return 1; case 'o': return 2; case 'O': return 3; case '█': return 4; default: return 2; // Default to medium intensity for any other character } } /** * Parse a pattern string into a matrix */ function parsePatternString(patternStr) { const lines = patternStr.trim().split('\n').map(line => line.trimEnd()); const height = lines.length; const width = Math.max(...lines.map(line => line.length)); const data = []; for (let y = 0; y < height; y++) { const row = []; const line = lines[y] || ''; for (let x = 0; x < width; x++) { const char = line[x] || ' '; row.push(charToIntensity(char)); } data.push(row); } return { width, height, data }; } /** * Generate a pattern matrix from preset pattern name */ export function generatePresetPattern(patternName) { const patternStr = PRESET_PATTERNS[patternName]; if (!patternStr) { throw new Error(`Unknown preset pattern: ${patternName}. Available: ${Object.keys(PRESET_PATTERNS).join(', ')}`); } return parsePatternString(patternStr); } /** * Generate a pattern matrix from custom ASCII art string */ export function generateCustomPattern(patternStr) { // Replace common escape sequences const normalizedPattern = patternStr .replace(/\\n/g, '\n') .replace(/\\t/g, '\t') .replace(/\\\\/g, '\\'); return parsePatternString(normalizedPattern); } /** * Generate a pattern matrix from text */ export function generateTextPattern(text) { const normalizedText = text.toUpperCase(); const patterns = []; // Generate pattern for each character for (const char of normalizedText) { const charPattern = TEXT_PATTERNS[char]; if (charPattern) { patterns.push(parsePatternString(charPattern)); } else { // Use space for unknown characters patterns.push(parsePatternString(TEXT_PATTERNS[' '])); } } if (patterns.length === 0) { throw new Error('No valid characters found in text'); } // Combine patterns horizontally with 1-pixel spacing const height = Math.max(...patterns.map(p => p.height)); let totalWidth = 0; // Calculate total width including spacing for (let i = 0; i < patterns.length; i++) { totalWidth += patterns[i].width; if (i < patterns.length - 1) { totalWidth += 1; // spacing between characters } } const data = []; for (let y = 0; y < height; y++) { const row = []; for (let i = 0; i < patterns.length; i++) { const pattern = patterns[i]; // Add character data for (let x = 0; x < pattern.width; x++) { if (y < pattern.height) { row.push(pattern.data[y][x]); } else { row.push(0); // Fill with empty space if character is shorter } } // Add spacing between characters (except for the last one) if (i < patterns.length - 1) { row.push(0); } } data.push(row); } return { width: totalWidth, height, data }; } /** * Scale a pattern matrix by a given factor */ export function scalePattern(pattern, scale) { if (scale === 1) { return pattern; } const newWidth = pattern.width * scale; const newHeight = pattern.height * scale; const data = []; for (let y = 0; y < newHeight; y++) { const row = []; const sourceY = Math.floor(y / scale); for (let x = 0; x < newWidth; x++) { const sourceX = Math.floor(x / scale); const intensity = pattern.data[sourceY]?.[sourceX] || 0; row.push(intensity); } data.push(row); } return { width: newWidth, height: newHeight, data }; } /** * Apply intensity scaling to pattern */ export function applyIntensity(pattern, intensity) { const multipliers = { low: 0.5, medium: 1.0, high: 1.5 }; const multiplier = multipliers[intensity]; const data = pattern.data.map(row => row.map(value => { if (value === 0) return 0; return Math.min(4, Math.max(1, Math.round(value * multiplier))); })); return { ...pattern, data }; } /** * Repeat a pattern horizontally with spacing */ export function repeatPattern(pattern, times) { if (times <= 1) { return pattern; } // Calculate spacing between repetitions (1-2 pixels depending on pattern size) const spacing = Math.max(1, Math.floor(pattern.width / 10)); const newWidth = pattern.width * times + spacing * (times - 1); const newHeight = pattern.height; const data = []; for (let y = 0; y < newHeight; y++) { const row = []; for (let rep = 0; rep < times; rep++) { // Add pattern data for (let x = 0; x < pattern.width; x++) { const value = pattern.data[y]?.[x] || 0; row.push(value); } // Add spacing between repetitions (except for the last one) if (rep < times - 1) { for (let s = 0; s < spacing; s++) { row.push(0); } } } data.push(row); } return { width: newWidth, height: newHeight, data }; } /** * Generate a complete pattern matrix from pattern configuration */ export function generatePattern(config) { let pattern; // Generate base pattern if (config.type === 'preset' && config.preset) { pattern = generatePresetPattern(config.preset); } else if (config.type === 'custom' && config.custom) { pattern = generateCustomPattern(config.custom); } else if (config.type === 'text' && config.text) { pattern = generateTextPattern(config.text); } else { // Default to heart pattern if no valid configuration pattern = generatePresetPattern('heart'); } // Apply scaling if (config.scale && config.scale > 1) { pattern = scalePattern(pattern, config.scale); } // Apply repetition if (config.repeat && config.repeat > 1) { pattern = repeatPattern(pattern, config.repeat); } // Apply intensity if (config.intensity) { pattern = applyIntensity(pattern, config.intensity); } return pattern; } /** * Map pattern to date range, properly handling GitHub's contribution graph layout */ export function mapPatternToDateRange(pattern, totalDays, config, startDate) { const commits = new Array(totalDays).fill(0); // If no start date provided, assume we're starting from today minus totalDays const firstDate = startDate || new Date(Date.now() - (totalDays - 1) * 24 * 60 * 60 * 1000); const firstDayOfWeek = firstDate.getDay(); // 0 = Sunday, 1 = Monday, etc. // Calculate the actual grid dimensions based on GitHub's layout // GitHub shows contributions in a grid where: // - Each column is a week // - Each row is a day of the week (0=Sunday, 6=Saturday) // - The first week may be incomplete (depending on what day the period starts) const totalWeeks = Math.ceil((totalDays + firstDayOfWeek) / 7); const gridWidth = totalWeeks; const gridHeight = 7; // Center the pattern within the available space const centerX = config.centerX ?? 0.5; const centerY = config.centerY ?? 0.5; // Calculate pattern placement, ensuring it fits within the grid const maxStartX = Math.max(0, gridWidth - pattern.width); const maxStartY = Math.max(0, gridHeight - pattern.height); const startX = Math.floor(maxStartX * centerX); const startY = Math.floor(maxStartY * centerY); // Map pattern to the contribution grid for (let py = 0; py < pattern.height; py++) { for (let px = 0; px < pattern.width; px++) { const gridX = startX + px; // Week number const gridY = startY + py; // Day of week (0=Sunday, 6=Saturday) // Check if this grid position is valid if (gridX >= 0 && gridX < gridWidth && gridY >= 0 && gridY < gridHeight) { // Convert grid coordinates to day index in our linear array // For the first week, we need to account for the offset let dayIndex; if (gridX === 0) { // First week - may be incomplete if (gridY >= firstDayOfWeek) { dayIndex = gridY - firstDayOfWeek; } else { continue; // This day doesn't exist in our range } } else { // Subsequent weeks dayIndex = (7 - firstDayOfWeek) + (gridX - 1) * 7 + gridY; } // Apply the pattern if the day index is valid if (dayIndex >= 0 && dayIndex < totalDays) { const intensity = pattern.data[py][px]; if (intensity > 0) { commits[dayIndex] = intensity; } } } } } return commits; } //# sourceMappingURL=patterns.js.map