[READ-ONLY] Mirror of https://github.com/mrgnw/cv.
0

Configure Feed

Select the types of activity you want to include in your feed.

cv / src / lib / versionReader.ts
6.1 kB 187 lines
1import type { CVData, Experience, Project } from "../types"; 2import JSON5 from 'json5'; 3 4/** 5 * Dynamically import all JSON files in the versions directory. 6 */ 7const versionFiles = { 8 ...import.meta.glob<string>('/src/lib/versions/*.json', { query: '?raw', import: 'default', eager: true }), 9 ...import.meta.glob<string>('/src/lib/versions/*.json5', { query: '?raw', import: 'default', eager: true }), 10 ...import.meta.glob<string>('/src/lib/versions/*.jsonc', { query: '?raw', import: 'default', eager: true }), 11}; 12 13/** 14 * Maps each version slug to its corresponding CV data. 15 */ 16const versionMap: Record<string, CVData> = {}; 17 18for (const path in versionFiles) { 19 const slugMatch = path.match(/\/src\/lib\/versions\/(.+?)\.(json[c5]?)$/); 20 if (slugMatch) { 21 const [, slug] = slugMatch; 22 try { 23 const content = versionFiles[path]; 24 if (!content) { 25 console.error(`No content found for ${path}`); 26 continue; 27 } 28 // Remove BOM if present 29 const cleanContent = content.replace(/^\uFEFF/, ''); 30 const isSpanish = slug.endsWith('.es'); 31 const baseSlug = isSpanish ? slug.slice(0, -3) : slug; 32 versionMap[slug] = { 33 ...JSON5.parse(cleanContent), 34 pdfLink: isSpanish ? 35 (baseSlug === 'main' ? '/es/morgan-williams.pdf' : `/es/morgan-williams.${baseSlug}.pdf`) : 36 (baseSlug === 'main' ? '/morgan-williams.pdf' : `/morgan-williams.${baseSlug}.pdf`), 37 }; 38 } catch (error) { 39 console.error(`Error parsing ${path}:`, error); 40 throw error; 41 } 42 } 43} 44 45/** 46 * Retrieves a specific version by its slug. 47 * @param slug - The slug identifier for the version. 48 * @returns The corresponding CVData or null if not found. 49 */ 50export function getVersion(slug: string): CVData | null { 51 return versionMap[slug] || null; 52} 53 54/** 55 * Combines the main version with a specified version. 56 * @param slug - The slug identifier for the version to combine. 57 * @returns The merged CVData or null if merging isn't possible. 58 */ 59export function coalesceVersion(slug: string): CVData | null { 60 const main = versionMap["main"]; 61 const version = getVersion(slug); 62 63 if (!main || !version) { 64 return null; 65 } 66 67 // Import and parse projects 68 const projectFiles = { 69 ...import.meta.glob<string>('/src/lib/projects.jsonc', { query: '?raw', import: 'default', eager: true }), 70 ...import.meta.glob<string>('/src/lib/projects-es.jsonc', { query: '?raw', import: 'default', eager: true }), 71 }; 72 73 const getProjectsPath = (slug: string) => `/src/lib/projects${slug.endsWith('.es') ? '-es' : ''}.jsonc`; 74 75 const projectContent = projectFiles[getProjectsPath(slug)]; 76 if (!projectContent) { 77 console.error(`Could not find projects file for ${slug}`); 78 throw new Error('Projects file not found'); 79 } 80 81 // Remove BOM if present and parse 82 const cleanProjectContent = projectContent.replace(/^\uFEFF/, ''); 83 const projectsList = JSON5.parse(cleanProjectContent) as Project[]; 84 85 // Create projects map 86 const projectsMap = new Map<string, Project>( 87 projectsList.map(project => [project.name, project]) 88 ); 89 90 function resolveProjects(projects: (string | Project)[] | undefined): Project[] { 91 if (!projects) return []; 92 93 return projects.map(project => { 94 if (typeof project === "string") { 95 // If project is specified by name, look it up in projectsMap 96 const fullProject = projectsMap.get(project); 97 if (!fullProject) { 98 console.warn(`Project "${project}" not found in projects.jsonc`); 99 return null; 100 } 101 return fullProject; 102 } 103 // If it's a full project definition, use it as is 104 return project; 105 }).filter((p): p is Project => p !== null); 106 } 107 108 // Create merged object without projects first 109 const { projects: mainProjects = [], ...mainRest } = main; 110 const { projects: versionProjects = [], ...versionRest } = version; 111 const merged: CVData = { ...mainRest, ...versionRest }; 112 113 // Resolve projects from version only 114 merged.projects = resolveProjects(versionProjects || []); 115 116 // Merge experience sections if they exist in both 117 if (main.experience && version.experience) { 118 merged.experience = mergeExperiences(main.experience, version.experience); 119 } 120 121 return merged; 122} 123 124/** 125 * Merges two arrays of Experience objects by their index. 126 * @param mainExperiences - The primary array of experiences. 127 * @param versionExperiences - The array of experiences to merge from the version. 128 * @returns A new array of merged Experience objects. 129 */ 130function mergeExperiences( 131 mainExperiences: Experience[], 132 versionExperiences: Experience[] 133): Experience[] { 134 const maxLength = Math.max(mainExperiences.length, versionExperiences.length); 135 const mergedExperiences: Experience[] = []; 136 137 for (let i = 0; i < maxLength; i++) { 138 const mainExp = mainExperiences[i]; 139 const versionExp = versionExperiences[i]; 140 141 if (mainExp && versionExp) { 142 mergedExperiences.push({ 143 ...mainExp, 144 ...versionExp, 145 description: mergeDescriptions(mainExp.description, versionExp.description), 146 }); 147 } else if (versionExp) { 148 mergedExperiences.push(versionExp); 149 } else if (mainExp) { 150 mergedExperiences.push(mainExp); 151 } 152 } 153 154 return mergedExperiences; 155} 156 157/** 158 * Merges two arrays of description strings line by line. 159 * Prefer version descriptions over main descriptions when available. 160 * @param mainDescription - The primary array of descriptions. 161 * @param versionDescription - The array of descriptions to merge from the version. 162 * @returns A new array of merged description strings. 163 */ 164function mergeDescriptions( 165 mainDescription: string[], 166 versionDescription: string[] 167): string[] { 168 const maxLength = Math.max(mainDescription.length, versionDescription.length); 169 const mergedDescription: string[] = []; 170 171 for (let i = 0; i < maxLength; i++) { 172 const versionLine = typeof versionDescription[i] === "string" 173 ? versionDescription[i].trim() 174 : ""; 175 mergedDescription[i] = versionLine || mainDescription[i] || ""; 176 } 177 178 return mergedDescription; 179} 180 181/** 182 * Retrieves all available versions. 183 * @returns An array of slugs representing all versions. 184 */ 185export function getAllVersions(): string[] { 186 return Object.keys(versionMap); 187}