From e3809c428cacc841987e5d05b260c0ddc85ea896 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Fri, 13 Mar 2026 18:08:45 -0400 Subject: [PATCH 01/45] some cleanup before summaries --- .github/workflows/build.yml | 3 ++- generator/main.go | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d162fe1..2ca0e6e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -130,7 +130,8 @@ jobs: deploy: needs: [build-quartz, generate-markdown] if: | - always() && !cancelled() + github.ref == 'refs/heads/main' + && always() && !cancelled() && (needs.generate-markdown.result == 'success') && needs.build-quartz.result == 'success' runs-on: ubuntu-latest diff --git a/generator/main.go b/generator/main.go index 9ff3bc7..e885b88 100644 --- a/generator/main.go +++ b/generator/main.go @@ -100,9 +100,9 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? b.WriteString(frontmatterFor("CV")) // TODO: HEADER AREA FOR LINKS TO CV, RESUME, BLOG, NOTES? - b.WriteString("Welcome to my CV! It is a living document that is updated occasionally.\n\n") // Why does this need 2 newlines? // TODO: move down? - b.WriteString("Looking for a resume instead? [Download it here](Resume.pdf)ENSURE WORKING\n\n") // TODO: ENSURE OK - b.WriteString("Feel free to check out the [source code](https://github.com/reeceappling/CV) for this website\n") // TODO: ENSURE OK + b.WriteString("Welcome to my CV! It is a living document that is updated occasionally.\n\n") // Why does this need 2 newlines? + b.WriteString("Looking for a resume instead? [Download it here](Resume.pdf)ENSURE WORKING\n\n") // TODO: ENSURE OK + b.WriteString("Feel free to check out the [source code](https://github.com/reeceappling/CV) for this website\n") b.WriteString("# About\n") @@ -157,10 +157,10 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? b.WriteString("## All\n") b.WriteString(alphabetizedLinksCompressed(langs, "language")) - b.WriteString("# Databases\n[Full Page](dbs.md)\n\n") // TODO: why does this take 2 newlines?? + b.WriteString("# Databases\n[Full Page](dbs.md)\n\n") b.WriteString(alphabetizedLinksCompressed(dbs, "db")) - b.WriteString("# Caches\n[Full Page](caches.md)\n\n") // TODO: why does this take 2 newlines?? + b.WriteString("# Caches\n[Full Page](caches.md)\n\n") b.WriteString(alphabetizedLinksCompressed(caches, "cache")) b.WriteString("# Cloud Providers\n[Full Page](providers.md)\n") @@ -173,10 +173,12 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? // TODO: ADD GCP! Azure! b.WriteString("# Technologies and Libraries\n[Full Page](technologies.md)\n\n") // TODO: why does this take 2 newlines?? - b.WriteString(alphabetizedLinksCompressed(techs, "technology")) // TODO: dir correct? + b.WriteString(alphabetizedLinksCompressed(techs, "technology")) - b.WriteString("## Containerization\n\n") // TODO: why does this take 2 newlines?? - b.WriteString("## Distributed Computing\n\n") // TODO: why does this take 2 newlines?? + b.WriteString("## Containerization\n\n") + b.WriteString(fixmeLink + "\n") // TODO: this! + b.WriteString("## Distributed Computing\n\n") + b.WriteString(fixmeLink + "\n") // TODO: this! // TODO: POPULATE THIS AREA!!!!!!!!!!!!! From a12df53d512e747be2dbbe9bcb0d7ec2a9a2494a Mon Sep 17 00:00:00 2001 From: Reece Appling <19520870+reeceappling@users.noreply.github.com> Date: Sat, 14 Mar 2026 16:06:04 -0400 Subject: [PATCH 02/45] Update client info. Add a couple I missed earlier --- generator/client.go | 48 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/generator/client.go b/generator/client.go index a3e102f..b93b6cb 100644 --- a/generator/client.go +++ b/generator/client.go @@ -166,31 +166,58 @@ func (pg *Client) GetAllLowest() (outCaches map[string]*CachePage, outDbs map[st var ( jdClient = NewClient("John Deere", - "Fortune 100 Agricultural Business (Think: Green Tractors)", // TODO: FIX ALL POINTS + "Most senior consulting engineer on a high-performance, global-scale, team of 4-8 at John Deere’s Intelligent Solutions Group; responsible for architecture, implementation, testing, optimization, and support of a complex set of diverse cloud services utilizing geospatiotemporal agribusiness data", + "Architected, implemented, and maintained cloud infrastructure and services for ingest, distributed processing, storage, manipulation, and retrieval of data for, datastores totalling over 50PB", + "Created multiple ECS clusters for use in, and consuming data from, John Deere AI platforms", + "Designed CUDA C/C++ Kernels used through CGo for statistics and image processing via GPU", + "Improved a mission-critical image manipulation API from 65% reliability to 99.9999% success rate", + "Spearheaded implementation of an ECS cluster using an advanced topology algorithm (from a PhD thesis), achieving >100x performance gains over its original implementation on TB-scale datasets", + "Built a Go-based compiler that transformed nested JSON instructions into machine-executable operations across EC2 clusters for for retrieving and manipulating geospatial agricultural data", + "Saved $18M of a $28M budget (64%) in 2024, while still increasing service stability and throughput", + "Ensured maximum service uptime via careful design and rollout of CI/CD pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via Terraform", + "Setup monitoring, dashboards, alerting, traces, and profiling (Datadog/Grafana/CloudWatch)", + "Responsible for educating engineers on infrastructure, codebase, domain, and best practices", + "Utilized primarily Go, Terraform, Bash, and Docker on AWS, but also used Scala, Github Actions, C/C++ with CUDA, DroneCI, python, javascript, typescript, Kotlin, and more", + "Platforms utilized: AWS (>30 separate services), Datadog, LogCentral, Rally, Azure DevOps, Github, DroneCI, Grafana, Prometheus, Confluence, and more", + ) + simpsonUniversityClient = NewClient("Simpson University", + "Upgraded the University’s payment and donation gateway. Remediated resulting bugs", ) sourceAlliesClient = NewClient("Source Allies", - "Source Allies internal projects", // TODO: FIX ALL POINTS + "Source Allies internal projects", + "Upgraded company internal payment gateway to a newer version of Java Spring" + "Secured all company machines via JAMF to ensure the protection of company and client data", + "Designed and created a Slack bot integration with Small Improvements to automate monthly announcements, and employee creation and completion of personal and professional goals", + "Redesigned Jira workflows streamlining the hiring process and onboarding systems for remote coworkers", ) critColaClient = NewClient("CritCola", - "ADD SUMMARY POINTS", // TODO: FIX ALL POINTS + "Consulted on hosting game servers and discord bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing GitLabCI, Terraform, CloudFlare, and AWS (EC2, EBS, IAM)", + "Created a final product with a spin-up time of approximately 3 minutes", + ) + arrowNailClient = NewClient("ArrowNail LLC", // TODO: ARROWNAIL CLIENT + "Used serverless services on AWS to support a React geospatial web app, Node API via lambda functions, and an aurora database. Provided IT and Systems Administration Support", + "Set up CI/CD pipeline in Gitlab CI to make future deployments seamless", ) wellAwareClient = NewClient("Well Aware NC", - "ADD SUMMARY POINTS", // TODO: FIX ALL POINTS + "Designed, created, and hosted a website for Well Aware NC, a University of North Carolina Chapel Hill affiliated nonprofit focused on the testing of well water contaminants within North Carolina", ) clarkClient = NewClient("Chapel Hill Masters Student in Public Health", - "ADD SUMMARY POINTS", // TODO: FIX ALL POINTS + "Created programs for a student doing research for his Masters Degree in Public Health. Provided data on E.Coli samples from different waterways, the programs checked the statistical validity on different E.Coli indicating kits", + ) + wildlifeRClient = NewClient("Wildlife mapping with R", // TODO: WILDLIFE MAPPING IN R: . + "Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps", ) charityClient = NewClient("Undisclosed Charity", - "ADD SUMMARY POINTS", // TODO: FIX ALL POINTS + "Designed, created, and hosted a website for an undisclosed local charity", ) teiClient = NewClient("TEI", - "ADD SUMMARY POINTS", // TODO: FIX ALL POINTS + "Internal company work for TEI", ) taeClient = NewClient("Talley Associates of Engineering", // TODO: JS photo parser - "ADD SUMMARY POINTS", // TODO: FIX ALL POINTS + "Internal company work for Talley Associates of Engineering", ) mafcClient = NewClient("Monroe Aquatics and Fitness Center", - "ADD SUMMARY POINTS", // TODO: FIX ALL POINTS + "Lifeguarding work, both at the indoor and outdoor pools of the fitness center", ) ) @@ -205,6 +232,5 @@ func initClientsAfterProjectsComplete() { teiClient = teiClient.WithProjects(teiProjects) // TODO: add projects (like NM, TX, IA, NC?) taeClient = taeClient.WithProjects(taeProjects) // TODO: JS photo parser mafcClient = mafcClient.WithProjects(mafcProjects) // TODO: Indoor and outdoor pool? + // TODO: wildlifeRClient, arrowNailClient, simpsonUniversityClient } - -var () From 76e324d8f6d6876289fc6b535efed57de99dee3d Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:27:32 -0400 Subject: [PATCH 03/45] fix projects --- generator/client.go | 16 ++-- generator/main.go | 5 +- generator/project.go | 147 ++++++++++++++++++++++++------------- generator/subjectMatter.go | 1 + 4 files changed, 111 insertions(+), 58 deletions(-) diff --git a/generator/client.go b/generator/client.go index b93b6cb..49e6ee3 100644 --- a/generator/client.go +++ b/generator/client.go @@ -185,7 +185,7 @@ var ( ) sourceAlliesClient = NewClient("Source Allies", "Source Allies internal projects", - "Upgraded company internal payment gateway to a newer version of Java Spring" + "Upgraded company internal payment gateway to a newer version of Java Spring", "Secured all company machines via JAMF to ensure the protection of company and client data", "Designed and created a Slack bot integration with Small Improvements to automate monthly announcements, and employee creation and completion of personal and professional goals", "Redesigned Jira workflows streamlining the hiring process and onboarding systems for remote coworkers", @@ -204,7 +204,7 @@ var ( clarkClient = NewClient("Chapel Hill Masters Student in Public Health", "Created programs for a student doing research for his Masters Degree in Public Health. Provided data on E.Coli samples from different waterways, the programs checked the statistical validity on different E.Coli indicating kits", ) - wildlifeRClient = NewClient("Wildlife mapping with R", // TODO: WILDLIFE MAPPING IN R: . + wildlifeRClient = NewClient("Wildlife mapping with R", "Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps", ) charityClient = NewClient("Undisclosed Charity", @@ -213,7 +213,7 @@ var ( teiClient = NewClient("TEI", "Internal company work for TEI", ) - taeClient = NewClient("Talley Associates of Engineering", // TODO: JS photo parser + taeClient = NewClient("Talley Associates of Engineering", "Internal company work for Talley Associates of Engineering", ) mafcClient = NewClient("Monroe Aquatics and Fitness Center", @@ -223,14 +223,16 @@ var ( func initClientsAfterProjectsComplete() { jdClient = jdClient.WithProjects(polygonBuilderProject, ogreProject, renderProject, statsProject, billingProject, explorerProject, wqdbProject, supportProject, scudsProject, ufoProject, goweProject, tileGenProject, GhaRunnersProject) - sourceAlliesClient = sourceAlliesClient.WithProjects(simpsonUnivProject) // TODO: USE! + sourceAlliesClient = sourceAlliesClient.WithProjects(jamfProject, smallImprovementsProject, internalResumeGeneratorProject) // TODO: USE OTHERS + simpsonUniversityClient = simpsonUniversityClient.WithProjects(simpsonUnivProject) // TODO: SIMPSON COLLEGE // TODO: USE! critColaClient = critColaClient.WithProjects(CritColaProject) wellAwareClient = wellAwareClient.WithProjects(WellAwareProject) clarkClient = clarkClient.WithProjects(MastersDataAnalysisProject) charityClient = charityClient.WithProjects(CharityProject) - teiClient = teiClient.WithProjects(teiProjects) // TODO: add projects (like NM, TX, IA, NC?) - taeClient = taeClient.WithProjects(taeProjects) // TODO: JS photo parser + teiClient = teiClient.WithProjects(teiProjects) // TODO: WEBSITE? // TODO: add projects (like NM, TX, IA, NC?) + taeClient = taeClient.WithProjects(taePhotoImporter) mafcClient = mafcClient.WithProjects(mafcProjects) // TODO: Indoor and outdoor pool? - // TODO: wildlifeRClient, arrowNailClient, simpsonUniversityClient + arrowNailClient = arrowNailClient.WithProjects(ArrowNailProject) + wildlifeRClient = wildlifeRClient.WithProjects(WildlifeRProject) } diff --git a/generator/main.go b/generator/main.go index e885b88..2eaab8e 100644 --- a/generator/main.go +++ b/generator/main.go @@ -96,6 +96,10 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? // TODO: https://quartz.jzhao.xyz/configuration PAGE TITLE // TODO: THEMEING // https://quartz.jzhao.xyz/configuration // TODO: FORCE DARK MODE + // TODO: figure out how to change rich text preview (when sent via discord, snapchat, or RCS + // TODO: ADD QR CODES???? + // TODO: MODIFY FAVICON + // TODO: MODIFY TAB TITLES b := strings.Builder{} b.WriteString(frontmatterFor("CV")) // TODO: HEADER AREA FOR LINKS TO CV, RESUME, BLOG, NOTES? @@ -105,7 +109,6 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? b.WriteString("Feel free to check out the [source code](https://github.com/reeceappling/CV) for this website\n") b.WriteString("# About\n") - b.WriteString(fixmeLink + "\n") // TODO: SUMMARY/About b.WriteString("# Work History ([Companies](companies.md), [Positions](positions.md))\n") // TODO: RENAME diff --git a/generator/project.go b/generator/project.go index c7f2d47..2fceaad 100644 --- a/generator/project.go +++ b/generator/project.go @@ -549,41 +549,46 @@ var nfcScannerUrl = "github.com/reeceappling/nfcScanner" // TODO: ensure ok var coreShufflerUrl = "github.com/reeceappling/coreShuffler" // TODO: ensure ok var ( - projectAgentSwarm = NewPersonalProject("AI Agent Swarm", fixmeLink, nil) - polygonBuilderProject = NewProfessionalProject("Polygon Builder", fixmeLink, jdClient, nil) - tileGenProject = NewProfessionalProject("Tile Generator", fixmeLink, jdClient, nil) - GhaRunnersProject = NewProfessionalProject("Github Actions GPU Runners", "FIX SUMMARY", jdClient, nil) - ogreProject = NewProfessionalProject("Organizational Geospatial Rollup Engine", fixmeLink, jdClient, nil) - renderProject = NewProfessionalProject("Render Cluster", fixmeLink, jdClient, nil) // TODO: MORE! - statsProject = NewProfessionalProject("Statistics Cluster", fixmeLink, jdClient, nil) - billingProject = NewProfessionalProject("Billing Cluster", fixmeLink, jdClient, nil) - explorerProject = NewProfessionalProject("Transform Explorer", fixmeLink, jdClient, nil) - wqdbProject = NewProfessionalProject("Work Queue Database", fixmeLink, jdClient, nil) - supportProject = NewProfessionalProject("Support Cluster", fixmeLink, jdClient, nil) - scudsProject = NewProfessionalProject("Scuds API", fixmeLink, jdClient, nil) - ufoProject = NewProfessionalProject("UFO API", fixmeLink, jdClient, nil) - goweProject = NewProfessionalProject("Gowe Builder", fixmeLink, jdClient, nil) - simpsonUnivProject = NewProfessionalProject("Simpson University", fixmeLink, sourceAlliesClient, nil) // TODO: MORE! - mushDbProject = NewPersonalProject("MushDb", fixmeLink, &mushDbUrl) - cvProject = NewPersonalProject("Personal Site and CV", "This project! A generator which creates markdown files that can be viewed via Obsidian, or published to the web.", &cvUrl) // TODO: make multiple strings an available option for summary - linksPage = NewPersonalProject("Personal Links Page", "A page to put all my links", nil) // TODO: LINKS PAGE - measurementsProject = NewPersonalProject("Measurements", fixmeLink, &measurementsUrl) - nfcScannerProject = NewPersonalProject("Nfc Scanner", fixmeLink, &nfcScannerUrl) - teiProjects = NewProfessionalProject("Tei Projects", fixmeLink, teiClient, nil) // TODO: maybe add an actual project - taeProjects = NewProfessionalProject("Tae Projects", fixmeLink, taeClient, nil) // TODO: maybe add actual projects? - mafcProjects = NewProfessionalProject("Mafc Projects", fixmeLink, mafcClient, nil) // TODO: maybe add actual projects? - coreShufflerProject = NewPersonalProject("Simulate Core Shuffler", fixmeLink, &coreShufflerUrl) - CharityProject = NewProfessionalProject("Charity Site", fixmeLink, charityClient, nil) - WellAwareProject = NewProfessionalProject("Well Aware NC", fixmeLink, clarkClient, nil) // TODO: change client to the lab??? - CritColaProject = NewProfessionalProject("CritCola", fixmeLink, critColaClient, nil) // TODO: ADD OTHER CRITCOLA PROJECTS - MastersDataAnalysisProject = NewProfessionalProject("Masters Data Analysis", fixmeLink, clarkClient, nil) - capstoneProject = NewSchoolProject("Capstone Project-Uranium Silicide Accident Tolerant Fuel cycle design for Duke Energy Catawba Nuclear Plant", "FIX M_E", schoolNCSU, &coreShufflerUrl) - projectLinAlgCryptography = NewSchoolProject("Linear algebra cryptography algorithm", "FIX M_E", schoolNCSU, nil) - cherenkovProject = NewSchoolProject("Cherenkov radiation sensor", fixmeLink, schoolNCSU, nil) - roboticsTeamProject = NewSchoolProject("Robotics team 3720", fixmeLink, schoolCata, nil) - cncLaserCutterProject = NewSchoolProject("CNC Laser Cutter", fixmeLink, schoolCata, nil) - aerospaceFinalProject = NewSchoolProject("Aerospace senior design course", "Designed, created, and tested a rocket from scratch", schoolCata, nil) - miscSmallPersonalProjects = NewPersonalProject("Misc small personal projects", "A conglomeration of personal projects which did not each deserve their own entry", nil) + projectAgentSwarm = NewPersonalProject("AI Agent Swarm", fixmeLink, nil) + polygonBuilderProject = NewProfessionalProject("Polygon Builder", fixmeLink, jdClient, nil) + tileGenProject = NewProfessionalProject("Tile Generator", fixmeLink, jdClient, nil) + GhaRunnersProject = NewProfessionalProject("Github Actions GPU Runners", "FIX SUMMARY", jdClient, nil) + ogreProject = NewProfessionalProject("Organizational Geospatial Rollup Engine", fixmeLink, jdClient, nil) + renderProject = NewProfessionalProject("Render Cluster", fixmeLink, jdClient, nil) // TODO: MORE! + statsProject = NewProfessionalProject("Statistics Cluster", fixmeLink, jdClient, nil) + billingProject = NewProfessionalProject("Billing Cluster", fixmeLink, jdClient, nil) + explorerProject = NewProfessionalProject("Transform Explorer", fixmeLink, jdClient, nil) + wqdbProject = NewProfessionalProject("Work Queue Database", fixmeLink, jdClient, nil) + supportProject = NewProfessionalProject("Support Cluster", fixmeLink, jdClient, nil) + scudsProject = NewProfessionalProject("Scuds API", fixmeLink, jdClient, nil) + ufoProject = NewProfessionalProject("UFO API", fixmeLink, jdClient, nil) + goweProject = NewProfessionalProject("Gowe Builder", fixmeLink, jdClient, nil) + simpsonUnivProject = NewProfessionalProject("Simpson University", fixmeLink, simpsonUniversityClient, nil) // TODO: MORE! + jamfProject = NewProfessionalProject("JAMF companywide setup", fixmeLink, sourceAlliesClient, nil) + smallImprovementsProject = NewProfessionalProject("Small Improvements Bot", fixmeLink, sourceAlliesClient, nil) + internalResumeGeneratorProject = NewProfessionalProject("Source Allies Internal Consultant Resume Generator", fixmeLink, sourceAlliesClient, nil) + mushDbProject = NewPersonalProject("MushDb", fixmeLink, &mushDbUrl) + cvProject = NewPersonalProject("Personal Site and CV", "This project! A generator which creates markdown files that can be viewed via Obsidian, or published to the web.", &cvUrl) // TODO: make multiple strings an available option for summary + linksPage = NewPersonalProject("Personal Links Page", "A page to put all my links", nil) // TODO: LINKS PAGE + measurementsProject = NewPersonalProject("Measurements", fixmeLink, &measurementsUrl) + nfcScannerProject = NewPersonalProject("Nfc Scanner", fixmeLink, &nfcScannerUrl) + teiProjects = NewProfessionalProject("Tei Projects", fixmeLink, teiClient, nil) // TODO: maybe add an actual project + taePhotoImporter = NewProfessionalProject("Tae Field Photograph Importer", fixmeLink, taeClient, nil) + mafcProjects = NewProfessionalProject("Mafc Projects", fixmeLink, mafcClient, nil) // TODO: maybe add actual projects? + coreShufflerProject = NewPersonalProject("Simulate Core Shuffler", fixmeLink, &coreShufflerUrl) + CharityProject = NewProfessionalProject("Charity Site", fixmeLink, charityClient, nil) + WellAwareProject = NewProfessionalProject("Well Aware NC", fixmeLink, clarkClient, nil) // TODO: change client to the lab??? + CritColaProject = NewProfessionalProject("CritCola", fixmeLink, critColaClient, nil) // TODO: ADD OTHER CRITCOLA PROJECTS + ArrowNailProject = NewProfessionalProject("Hail History Tracker", fixmeLink, arrowNailClient, nil) + WildlifeRProject = NewProfessionalProject("Wildlife R Data Analysis", fixmeLink, wildlifeRClient, nil) + MastersDataAnalysisProject = NewProfessionalProject("Masters Data Analysis", fixmeLink, clarkClient, nil) + capstoneProject = NewSchoolProject("Capstone Project-Uranium Silicide Accident Tolerant Fuel cycle design for Duke Energy Catawba Nuclear Plant", "FIX M_E", schoolNCSU, &coreShufflerUrl) + projectLinAlgCryptography = NewSchoolProject("Linear algebra cryptography algorithm", "FIX M_E", schoolNCSU, nil) + cherenkovProject = NewSchoolProject("Cherenkov radiation sensor", fixmeLink, schoolNCSU, nil) + roboticsTeamProject = NewSchoolProject("Robotics team 3720", fixmeLink, schoolCata, nil) + cncLaserCutterProject = NewSchoolProject("CNC Laser Cutter", fixmeLink, schoolCata, nil) + aerospaceFinalProject = NewSchoolProject("Aerospace senior design course", "Designed, created, and tested a rocket from scratch", schoolCata, nil) + miscSmallPersonalProjects = NewPersonalProject("Misc small personal projects", "A conglomeration of personal projects which did not each deserve their own entry", nil) ) func initProjectsFinal() { @@ -617,7 +622,7 @@ func initProjectsFinal() { WithCaches("Redis", "memcached"). WithTechnologies("Github Actions", "Parquet", "Avro"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smBackend, smTopology, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). + WithSubjectMatters(smGeospatial, smBackend, smTopology, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). //WithTags(tagBackend, tagTopology, tagLinearAlgebra, tagClusterComputing, tagDistributedComputing, tagContainerization, tagIAC, tagCiCd) finalize() tileGenProject = tileGenProject.WithStatus(statusMaintaining). @@ -634,7 +639,7 @@ func initProjectsFinal() { WithCaches("Redis", "memcached", "DAX"). WithTechnologies("CUDA", "Github Actions", "Parquet", "Avro", "LocalStack"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smBackend, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). + WithSubjectMatters(smGeospatial, smBackend, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). finalize() GhaRunnersProject = GhaRunnersProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -662,7 +667,7 @@ func initProjectsFinal() { WithCloudProvider("AWS", "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", ). - WithSubjectMatters(smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC). + WithSubjectMatters(smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC). finalize() renderProject = renderProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -678,7 +683,7 @@ func initProjectsFinal() { WithCloudProvider("AWS", "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", ). - WithSubjectMatters(smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smGraphics). + WithSubjectMatters(smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smGraphics). finalize() statsProject = statsProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -699,7 +704,7 @@ func initProjectsFinal() { WithCloudProvider("AWS", "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", ). - WithSubjectMatters(smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smGPU, smStatistics). + WithSubjectMatters(smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smGPU, smStatistics). finalize() billingProject = billingProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -716,7 +721,7 @@ func initProjectsFinal() { WithCaches("Redis", "memcached"). WithTechnologies("Github Actions", "Parquet", "Avro"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smStatistics, smBackend). + WithSubjectMatters(smGeospatial, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smStatistics, smBackend). finalize() explorerProject = explorerProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -732,7 +737,7 @@ func initProjectsFinal() { WithCaches("Redis", "memcached"). WithTechnologies("Github Actions"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smCiCd, smContainerization, smIAC, smFullStack). + WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smFullStack). finalize() wqdbProject = wqdbProject.WithStatus(statusComplete). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -777,7 +782,7 @@ func initProjectsFinal() { WithCaches("Redis"). WithTechnologies("ElasticSearch", "Github Actions", "Parquet", "Avro"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smCiCd, smContainerization, smIAC, smStatistics, smBackend). + WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smStatistics, smBackend). finalize() ufoProject = ufoProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -792,7 +797,7 @@ func initProjectsFinal() { WithCaches("Redis"). WithTechnologies("Github Actions"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smCiCd, smContainerization, smIAC, smStatistics, smBackend). + WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smStatistics, smBackend). finalize() goweProject = goweProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -807,7 +812,7 @@ func initProjectsFinal() { WithCaches("Redis"). WithTechnologies("Github Actions", "Parquet", "Avro"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smCiCd, smContainerization, smIAC, smClusterComputing, smDistributedComputing, smLinearAlgebra, smBackend). + WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smClusterComputing, smDistributedComputing, smLinearAlgebra, smBackend). finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -985,13 +990,55 @@ func initProjectsFinal() { teiProjects = teiProjects. WithSummary("A catch-all for all projects at my position at TEI."). WithStatus(statusComplete). - WithSubjectMatters(smStructuralEngineering, smCivilEngineering) - taeProjects = taeProjects. - WithSummary("A catch-all for all projects at my position at Talley Associates of Engineering."). + WithSubjectMatters(smStructuralEngineering, smCivilEngineering). + finalize() + taePhotoImporter = taePhotoImporter. + WithSummary("Script to import photos taken in the field into the proper format and structure for use in engineering documentation"). + WithLang("Javascript", Some). WithStatus(statusComplete). - WithSubjectMatters(smStructuralEngineering, smCivilEngineering) + WithSubjectMatters(smStructuralEngineering, smCivilEngineering). + finalize() mafcProjects = mafcProjects. WithSummary("Lifeguarding stuff, I guess."). WithStatus(statusComplete). - WithSubjectMatters(smFirstAid) + WithSubjectMatters(smFirstAid). + finalize() + ArrowNailProject = ArrowNailProject. + WithLang("Javascript", Extensively). // TODO: MORE STUFF + WithLang("Html", Regularly). // TODO: MORE STUFF + WithLang("CSS", Regularly). // TODO: MORE STUFF + WithSummary("Geospatial mapping app to track historical hail instances for use by a roofing company"). + WithStatus(statusShelved). + WithTechnologies("React"). + WithSubjectMatters(smGeospatial). + WithCloudProvider("AWS", "Lambda", "RDS"). // TODO: MORE STUFF? + finalize() + WildlifeRProject = WildlifeRProject. + WithStatus(statusComplete). + WithLang("R", Often, smGeospatial). + WithSummary("Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps"). + WithSubjectMatters(smGeospatial, smStatistics). + finalize() + jamfProject = jamfProject. + WithSummary("JAMF companywide setup. " + fixmeLink). + WithStatus(statusComplete). + WithPlatforms("Jamf"). + finalize() + smallImprovementsProject = smallImprovementsProject. + WithLang("Javascript", Often). + WithSummary("Created a Small Improvements Slack bot for Source Allies to track goal creation and achievement"). + WithCloudProvider("AWS", "Lambda", "DynamoDB", "SAM", "Cloudformation"). // TODO: AWS + WithPlatforms("Slack", "Small Improvements"). + WithStatus(statusComplete). + finalize() + internalResumeGeneratorProject = internalResumeGeneratorProject. + WithSummary("Updated company internal resume generator"). + // TODO: any aws in here??? + WithStatus(statusComplete). + WithLang("Java", Often, smBackend). + WithLang("Html", Some). + WithLang("CSS", Some). + WithLang("Javascript", Some). + WithTechnologies("Spring"). + finalize() } diff --git a/generator/subjectMatter.go b/generator/subjectMatter.go index cab55fd..750357f 100644 --- a/generator/subjectMatter.go +++ b/generator/subjectMatter.go @@ -104,6 +104,7 @@ var ( smNetworking = NewSubjectMatter("Networking") // TODO: maybe get rid of smChemistry = NewSubjectMatter("Chemistry") smDocumentation = NewSubjectMatter("Documentation") + smGeospatial = NewSubjectMatter("Geospatial") ) func setupSubjectMatters() { From e35e2caab59468285962b319eb8ba40db12c30ec Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:30:04 -0400 Subject: [PATCH 04/45] fix subject matters on new projects --- generator/project.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/generator/project.go b/generator/project.go index 2fceaad..180812c 100644 --- a/generator/project.go +++ b/generator/project.go @@ -815,14 +815,14 @@ func initProjectsFinal() { WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smClusterComputing, smDistributedComputing, smLinearAlgebra, smBackend). finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! - WithSummary("SUMMARY HERE"). // TODO: MORE! - WithLang("Html", Regularly). - WithLang("CSS", Regularly). - WithLang("Javascript", Some). - WithSubjectMatters(smFrontend). - WithTechnologies("Drupal"). - WithPlatforms("Confluence"). // TODO: SOURCE JAMF - finalize() + WithSummary("SUMMARY HERE"). // TODO: MORE! + WithLang("Html", Regularly). + WithLang("CSS", Regularly). + WithLang("Javascript", Some). + WithSubjectMatters(smFrontend). + WithTechnologies("Drupal"). + WithPlatforms("Confluence"). // TODO: SOURCE JAMF + finalize() // TODO: sai project for JAMF mushDbProject = mushDbProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -1004,9 +1004,9 @@ func initProjectsFinal() { WithSubjectMatters(smFirstAid). finalize() ArrowNailProject = ArrowNailProject. - WithLang("Javascript", Extensively). // TODO: MORE STUFF - WithLang("Html", Regularly). // TODO: MORE STUFF - WithLang("CSS", Regularly). // TODO: MORE STUFF + WithLang("Javascript", Extensively). + WithLang("Html", Regularly). + WithLang("CSS", Regularly). WithSummary("Geospatial mapping app to track historical hail instances for use by a roofing company"). WithStatus(statusShelved). WithTechnologies("React"). @@ -1023,13 +1023,16 @@ func initProjectsFinal() { WithSummary("JAMF companywide setup. " + fixmeLink). WithStatus(statusComplete). WithPlatforms("Jamf"). + WithSubjectMatters(smCybersecurity). finalize() smallImprovementsProject = smallImprovementsProject. WithLang("Javascript", Often). WithSummary("Created a Small Improvements Slack bot for Source Allies to track goal creation and achievement"). WithCloudProvider("AWS", "Lambda", "DynamoDB", "SAM", "Cloudformation"). // TODO: AWS - WithPlatforms("Slack", "Small Improvements"). + WithPlatforms("Slack", "Small Improvements", "Github"). + WithTechnologies("Github Actions"). WithStatus(statusComplete). + WithSubjectMatters(smBackend, smServerless). finalize() internalResumeGeneratorProject = internalResumeGeneratorProject. WithSummary("Updated company internal resume generator"). @@ -1040,5 +1043,7 @@ func initProjectsFinal() { WithLang("CSS", Some). WithLang("Javascript", Some). WithTechnologies("Spring"). + WithSubjectMatters(smFullStack). + WithTechnologies("Github Actions"). finalize() } From 4f6435f5f16b0d6c6471d2aaf4e0241d5ae9c0e2 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:32:04 -0400 Subject: [PATCH 05/45] fix new platforms --- generator/client.go | 2 +- generator/platform.go | 2 ++ generator/project.go | 16 ++++++++-------- generator/subjectMatter.go | 1 + 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/generator/client.go b/generator/client.go index 49e6ee3..01bb128 100644 --- a/generator/client.go +++ b/generator/client.go @@ -54,7 +54,7 @@ func (pg *Client) Bytes() []byte { builder := strings.Builder{} builder.WriteString(frontmatterFor(pg.Name, "Client")) if pg.Info != nil && len(pg.Info) > 0 { - builder.WriteString("# Responsibilities and Achievements \n") + builder.WriteString("# Notable Information\n") for _, info := range pg.Info { builder.WriteString(fmt.Sprintf("- %s\n", info)) } diff --git a/generator/platform.go b/generator/platform.go index 220b844..3dce82e 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -64,4 +64,6 @@ func setupPlatformSubjectMatters() { WithSubjectMatters(smDevOps) NewPlatform("Confluence"). // TODO: USE WithSubjectMatters(smDocumentation) + NewPlatform("Slack"). // TODO: USE + WithSubjectMatters(smCommunication) } diff --git a/generator/project.go b/generator/project.go index 180812c..35e0809 100644 --- a/generator/project.go +++ b/generator/project.go @@ -815,14 +815,14 @@ func initProjectsFinal() { WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smClusterComputing, smDistributedComputing, smLinearAlgebra, smBackend). finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! - WithSummary("SUMMARY HERE"). // TODO: MORE! - WithLang("Html", Regularly). - WithLang("CSS", Regularly). - WithLang("Javascript", Some). - WithSubjectMatters(smFrontend). - WithTechnologies("Drupal"). - WithPlatforms("Confluence"). // TODO: SOURCE JAMF - finalize() + WithSummary("SUMMARY HERE"). // TODO: MORE! + WithLang("Html", Regularly). + WithLang("CSS", Regularly). + WithLang("Javascript", Some). + WithSubjectMatters(smFrontend). + WithTechnologies("Drupal"). + WithPlatforms("Confluence"). // TODO: SOURCE JAMF + finalize() // TODO: sai project for JAMF mushDbProject = mushDbProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! diff --git a/generator/subjectMatter.go b/generator/subjectMatter.go index 750357f..f1aefab 100644 --- a/generator/subjectMatter.go +++ b/generator/subjectMatter.go @@ -105,6 +105,7 @@ var ( smChemistry = NewSubjectMatter("Chemistry") smDocumentation = NewSubjectMatter("Documentation") smGeospatial = NewSubjectMatter("Geospatial") + smCommunication = NewSubjectMatter("Communication") ) func setupSubjectMatters() { From da058513b15db77d40b6896e4dce91b5d79122c9 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:32:45 -0400 Subject: [PATCH 06/45] fix another subject matter on a platform --- generator/platform.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/generator/platform.go b/generator/platform.go index 3dce82e..5eab233 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -59,11 +59,13 @@ func setupPlatformSubjectMatters() { NewPlatform("Gitlab"). WithSubjectMatters(smCiCd) NewPlatform("Azure DevOps"). // TODO: USE - WithSubjectMatters(smDevOps) + WithSubjectMatters(smDevOps) NewPlatform("Jira"). // TODO: USE - WithSubjectMatters(smDevOps) + WithSubjectMatters(smDevOps) NewPlatform("Confluence"). // TODO: USE - WithSubjectMatters(smDocumentation) + WithSubjectMatters(smDocumentation) NewPlatform("Slack"). // TODO: USE - WithSubjectMatters(smCommunication) + WithSubjectMatters(smCommunication) + NewPlatform("Small Improvements"). // TODO: USE + WithSubjectMatters(smObservability) } From 1739a97d290d9ba82c253f62166d9188e3adff34 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:34:05 -0400 Subject: [PATCH 07/45] add Teams to runners because they report back to it on failure --- generator/platform.go | 2 ++ generator/project.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/generator/platform.go b/generator/platform.go index 5eab233..9400110 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -68,4 +68,6 @@ func setupPlatformSubjectMatters() { WithSubjectMatters(smCommunication) NewPlatform("Small Improvements"). // TODO: USE WithSubjectMatters(smObservability) + NewPlatform("Teams"). + WithSubjectMatters(smCommunication) } diff --git a/generator/project.go b/generator/project.go index 35e0809..0b48a22 100644 --- a/generator/project.go +++ b/generator/project.go @@ -650,7 +650,7 @@ func initProjectsFinal() { "ECS", "S3", "EC2", "IAM", "SecretsManager", "Cloudwatch", "Lambda", "ECR", "Route53", // Route53 add networking sm ). WithTechnologies("Github Actions"). - WithPlatforms("Datadog", "Logcentral", "Github", "Azure DevOps", "Jira", "Confluence"). + WithPlatforms("Datadog", "Logcentral", "Github", "Azure DevOps", "Jira", "Confluence", "Teams"). WithSubjectMatters(smBackend, smCiCd, smDevOps, smContainerization, smIAC, smCiCd). finalize() ogreProject = ogreProject.WithStatus(statusBuilding). From f133cdb1983de3aa623e9909d8b942280912b22e Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:34:39 -0400 Subject: [PATCH 08/45] now jamf --- generator/platform.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/generator/platform.go b/generator/platform.go index 9400110..6a4964a 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -70,4 +70,6 @@ func setupPlatformSubjectMatters() { WithSubjectMatters(smObservability) NewPlatform("Teams"). WithSubjectMatters(smCommunication) + NewPlatform("Jamf"). + WithSubjectMatters(smCybersecurity) } From 4ebefdeb94f6d747db4c0447832d1727e3128430 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:35:27 -0400 Subject: [PATCH 09/45] should actually run this time. Fixed missing subject matter for spring --- generator/technology.go | 1 + 1 file changed, 1 insertion(+) diff --git a/generator/technology.go b/generator/technology.go index cbef927..f78ef3d 100644 --- a/generator/technology.go +++ b/generator/technology.go @@ -101,6 +101,7 @@ func setupTechSubjectMatters() { NewTechnology("G and M codes", smElectronics, smRobotics) NewTechnology("OpenApi", smDocumentation) // TODO: USE NewTechnology("Swagger", smDocumentation) // TODO: USE + NewTechnology("Spring", smBackend) } // TODO: list all backlinks???? From 442f23f5ab0ed9022a11f9cb648b3173592fefe4 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:43:57 -0400 Subject: [PATCH 10/45] api tags in places --- generator/platform.go | 10 +++---- generator/project.go | 60 +++++++++++++++++++------------------- generator/subjectMatter.go | 1 + generator/technology.go | 5 ++-- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/generator/platform.go b/generator/platform.go index 6a4964a..ec71caf 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -59,15 +59,15 @@ func setupPlatformSubjectMatters() { NewPlatform("Gitlab"). WithSubjectMatters(smCiCd) NewPlatform("Azure DevOps"). // TODO: USE - WithSubjectMatters(smDevOps) + WithSubjectMatters(smDevOps) NewPlatform("Jira"). // TODO: USE - WithSubjectMatters(smDevOps) + WithSubjectMatters(smDevOps) NewPlatform("Confluence"). // TODO: USE - WithSubjectMatters(smDocumentation) + WithSubjectMatters(smDocumentation) NewPlatform("Slack"). // TODO: USE - WithSubjectMatters(smCommunication) + WithSubjectMatters(smCommunication) NewPlatform("Small Improvements"). // TODO: USE - WithSubjectMatters(smObservability) + WithSubjectMatters(smObservability) NewPlatform("Teams"). WithSubjectMatters(smCommunication) NewPlatform("Jamf"). diff --git a/generator/project.go b/generator/project.go index 0b48a22..f7a26ba 100644 --- a/generator/project.go +++ b/generator/project.go @@ -620,9 +620,9 @@ func initProjectsFinal() { ). WithDbs("Aurora", "DynamoDB", "Postgres"). WithCaches("Redis", "memcached"). - WithTechnologies("Github Actions", "Parquet", "Avro"). + WithTechnologies("Github Actions", "Parquet", "Avro", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smGeospatial, smBackend, smTopology, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). + WithSubjectMatters(smApi, smGeospatial, smBackend, smTopology, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). //WithTags(tagBackend, tagTopology, tagLinearAlgebra, tagClusterComputing, tagDistributedComputing, tagContainerization, tagIAC, tagCiCd) finalize() tileGenProject = tileGenProject.WithStatus(statusMaintaining). @@ -637,9 +637,9 @@ func initProjectsFinal() { ). WithDbs("DynamoDB", "Postgres"). WithCaches("Redis", "memcached", "DAX"). - WithTechnologies("CUDA", "Github Actions", "Parquet", "Avro", "LocalStack"). + WithTechnologies("CUDA", "Github Actions", "Parquet", "Avro", "LocalStack", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smGeospatial, smBackend, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). + WithSubjectMatters(smApi, smGeospatial, smBackend, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). finalize() GhaRunnersProject = GhaRunnersProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -662,12 +662,12 @@ func initProjectsFinal() { WithLang("SQL", Some). WithDbs("Aurora", "DynamoDB", "Postgres"). WithCaches("Redis", "memcached"). - WithTechnologies("Github Actions", "Parquet", "Avro"). + WithTechnologies("Github Actions", "Parquet", "Avro", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). WithCloudProvider("AWS", "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", ). - WithSubjectMatters(smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC). + WithSubjectMatters(smApi, smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC). finalize() renderProject = renderProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -676,14 +676,14 @@ func initProjectsFinal() { WithLang("Docker", Regularly). WithLang("Bash", Some). WithLang("SQL", Some). - WithTechnologies("Github Actions", "Parquet", "Avro", "GraphQL"). + WithTechnologies("Github Actions", "Parquet", "Avro", "REST API", "GraphQL"). WithDbs("Postgres"). WithCaches("Redis", "memcached"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "GraphQL Apollo", "Azure DevOps", "Jira", "Confluence"). WithCloudProvider("AWS", "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", ). - WithSubjectMatters(smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smGraphics). + WithSubjectMatters(smApi, smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smGraphics). finalize() statsProject = statsProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -699,12 +699,12 @@ func initProjectsFinal() { WithLang("Rust", Minimal). WithDbs("Aurora", "Postgres"). WithCaches("Redis", "memcached"). - WithTechnologies("CUDA", "Github Actions", "Parquet", "Avro", "GraphQL", "SIMD"). + WithTechnologies("CUDA", "Github Actions", "Parquet", "Avro", "GraphQL", "SIMD", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "GraphQL Apollo", "Azure DevOps", "Jira", "Confluence"). WithCloudProvider("AWS", "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", ). - WithSubjectMatters(smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smGPU, smStatistics). + WithSubjectMatters(smApi, smGeospatial, smBackend, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smGPU, smStatistics). finalize() billingProject = billingProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -719,9 +719,9 @@ func initProjectsFinal() { ). WithDbs("DynamoDB", "Postgres"). WithCaches("Redis", "memcached"). - WithTechnologies("Github Actions", "Parquet", "Avro"). + WithTechnologies("Github Actions", "Parquet", "Avro", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smGeospatial, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smStatistics, smBackend). + WithSubjectMatters(smApi, smGeospatial, smCiCd, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smStatistics, smBackend). finalize() explorerProject = explorerProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -735,9 +735,9 @@ func initProjectsFinal() { ). WithDbs("Aurora", "DynamoDB", "Postgres"). WithCaches("Redis", "memcached"). - WithTechnologies("Github Actions"). + WithTechnologies("Github Actions", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smFullStack). + WithSubjectMatters(smApi, smGeospatial, smCiCd, smContainerization, smIAC, smFullStack). finalize() wqdbProject = wqdbProject.WithStatus(statusComplete). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -766,7 +766,7 @@ func initProjectsFinal() { ). WithTechnologies("Github Actions"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smCiCd, smContainerization, smIAC, smBackend). + WithSubjectMatters(smApi, smCiCd, smContainerization, smIAC, smBackend). finalize() scudsProject = scudsProject.WithStatus(statusComplete). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -780,9 +780,9 @@ func initProjectsFinal() { ). WithDbs("ElasticSearch"). WithCaches("Redis"). - WithTechnologies("ElasticSearch", "Github Actions", "Parquet", "Avro"). + WithTechnologies("ElasticSearch", "Github Actions", "Parquet", "Avro", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smStatistics, smBackend). + WithSubjectMatters(smApi, smGeospatial, smCiCd, smContainerization, smIAC, smStatistics, smBackend). finalize() ufoProject = ufoProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -795,9 +795,9 @@ func initProjectsFinal() { "ECS", "Fargate", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", ). WithCaches("Redis"). - WithTechnologies("Github Actions"). + WithTechnologies("Github Actions", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). - WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smStatistics, smBackend). + WithSubjectMatters(smApi, smGeospatial, smCiCd, smContainerization, smIAC, smStatistics, smBackend). finalize() goweProject = goweProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -834,10 +834,10 @@ func initProjectsFinal() { WithLang("Docker Compose", Often). //WithLang("Kubernetes", Some). // TODO: lang or tech? WithDbs("mongodb"). - WithTechnologies("Github Actions", "React", "NextJs", "RFID", "NFC", "I2C", "SPI", "Cloudflare Tunnels", "Kubernetes"). + WithTechnologies("Github Actions", "React", "NextJs", "RFID", "NFC", "I2C", "SPI", "Cloudflare Tunnels", "Kubernetes", "REST API"). WithPlatforms("Github", "Cloudflare", "Grafana"). WithCloudProvider("GCP", "Google Cloud DNS", "Identity Platform"). - WithSubjectMatters(smContainerization, smDistributedComputing, smMycology, smFullStack). + WithSubjectMatters(smApi, smContainerization, smDistributedComputing, smMycology, smFullStack). WithInterests(string(smMycology)). finalize() cvProject = cvProject.WithStatus(statusBuilding). @@ -872,9 +872,9 @@ func initProjectsFinal() { nfcScannerProject = nfcScannerProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! WithLang("Go", Extensively). - WithTechnologies("Github Actions", "Webhooks", "Websockets", "Server-Sent Events", "Pub-Sub", "NFC", "I2C", "SPI", "RFID"). + WithTechnologies("Github Actions", "Webhooks", "Websockets", "Server-Sent Events", "Pub-Sub", "NFC", "I2C", "SPI", "RFID", "REST API"). WithPlatforms("Github"). - WithSubjectMatters(smContainerization, smDistributedComputing). + WithSubjectMatters(smApi, smContainerization, smDistributedComputing). WithInterests(string(smMycology)). finalize() coreShufflerProject = coreShufflerProject.WithStatus(statusComplete). @@ -985,7 +985,7 @@ func initProjectsFinal() { WithLang("Javascript", Often, smFrontend). WithLang("Typescript", Often, smFullStack). WithTechnologies("Kubernetes"). - WithSubjectMatters(smCryptocurrency). + WithSubjectMatters(smApi, smCryptocurrency). finalize() teiProjects = teiProjects. WithSummary("A catch-all for all projects at my position at TEI."). @@ -1009,8 +1009,8 @@ func initProjectsFinal() { WithLang("CSS", Regularly). WithSummary("Geospatial mapping app to track historical hail instances for use by a roofing company"). WithStatus(statusShelved). - WithTechnologies("React"). - WithSubjectMatters(smGeospatial). + WithTechnologies("React", "REST API"). + WithSubjectMatters(smApi, smGeospatial). WithCloudProvider("AWS", "Lambda", "RDS"). // TODO: MORE STUFF? finalize() WildlifeRProject = WildlifeRProject. @@ -1030,9 +1030,9 @@ func initProjectsFinal() { WithSummary("Created a Small Improvements Slack bot for Source Allies to track goal creation and achievement"). WithCloudProvider("AWS", "Lambda", "DynamoDB", "SAM", "Cloudformation"). // TODO: AWS WithPlatforms("Slack", "Small Improvements", "Github"). - WithTechnologies("Github Actions"). + WithTechnologies("Github Actions", "REST API"). WithStatus(statusComplete). - WithSubjectMatters(smBackend, smServerless). + WithSubjectMatters(smApi, smBackend, smServerless). finalize() internalResumeGeneratorProject = internalResumeGeneratorProject. WithSummary("Updated company internal resume generator"). @@ -1042,8 +1042,8 @@ func initProjectsFinal() { WithLang("Html", Some). WithLang("CSS", Some). WithLang("Javascript", Some). - WithTechnologies("Spring"). - WithSubjectMatters(smFullStack). + WithTechnologies("Spring", "REST API"). + WithSubjectMatters(smApi, smFullStack). WithTechnologies("Github Actions"). finalize() } diff --git a/generator/subjectMatter.go b/generator/subjectMatter.go index f1aefab..1f0f2f7 100644 --- a/generator/subjectMatter.go +++ b/generator/subjectMatter.go @@ -92,6 +92,7 @@ var ( smMycology = NewSubjectMatter("Mycology") smFrontend = NewSubjectMatter("Frontend") smBackend = NewSubjectMatter("Backend") + smApi = NewSubjectMatter("API") // TODO: USE THIS EVERYWHERE smFullStack = NewSubjectMatter("Full Stack") smCloudComputing = NewSubjectMatter("Cloud Computing") smObservability = NewSubjectMatter("Observability") diff --git a/generator/technology.go b/generator/technology.go index f78ef3d..992f05f 100644 --- a/generator/technology.go +++ b/generator/technology.go @@ -58,6 +58,7 @@ func setupTechSubjectMatters() { NewTechnology("AI Agents", smAI) NewTechnology("OpenAI API spec", smAI, smBackend) NewTechnology("CUDA", smGPU, smGraphics) + NewTechnology("REST API", smBackend, smApi) // TODO: use this everywhere necessary... NewTechnology("Avro", smBackend).WithTags("DataFormat") NewTechnology("Parquet", smBackend).WithTags("DataFormat") NewTechnology("JSON", smFullStack).WithTags("DataFormat") @@ -67,7 +68,6 @@ func setupTechSubjectMatters() { NewTechnology("YAML", smFullStack, smCiCd).WithTags("DataFormat") NewTechnology("TOML", smFullStack).WithTags("DataFormat") NewTechnology("CUDA", smGPU, smGraphics) - // TODO: GRAPHQL???? NewTechnology("Kubernetes", smBackend, smDistributedComputing, smContainerization, smIAC, smCloudComputing, smNetworking) // TODO: ansible? chef? NewTechnology("RFID", smRobotics, smEmbeddedSystems, smElectronics) @@ -79,7 +79,7 @@ func setupTechSubjectMatters() { NewTechnology("Quartz 4", smFrontend) NewTechnology("Quartz", smFrontend) NewTechnology("NextJs", smFullStack) - NewTechnology("GraphQL", smBackend, smNetworking) + NewTechnology("GraphQL", smBackend, smNetworking).WithTags("API") NewTechnology("Obsidian", smDocumentation) NewTechnology("SIMD", smBackend, smRobotics, smEmbeddedSystems) NewTechnology("Cloudflare Tunnels", smNetworking) @@ -89,7 +89,6 @@ func setupTechSubjectMatters() { NewTechnology("Markdown", smDocumentation) NewTechnology("Websockets", smFullStack, smNetworking) NewTechnology("Server-Sent Events", smFullStack, smNetworking) - // TODO: ADD NDSF(?) FILES FOR NUC STUFF NewTechnology("SIMULATE3", smNuclearEngineering, smParticlePhysics) NewTechnology("CASMO4e", smNuclearEngineering, smParticlePhysics) NewTechnology("Pub-Sub", smBackend, smNetworking) From 7bb7ef0eb4b658c93bb1f6b9ee437959c73d311a Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 16:46:25 -0400 Subject: [PATCH 11/45] add force deploy tag --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2ca0e6e..760ba38 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -130,7 +130,7 @@ jobs: deploy: needs: [build-quartz, generate-markdown] if: | - github.ref == 'refs/heads/main' + (github.ref == 'refs/heads/main' || contains(github.event.pull_request.labels.*.name, 'force deploy')) && always() && !cancelled() && (needs.generate-markdown.result == 'success') && needs.build-quartz.result == 'success' From 400345480d4a9d5241d39bfcb08d83c80cb03cd2 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:18:33 -0400 Subject: [PATCH 12/45] make link referencing easier --- generator/cache.go | 3 + generator/client.go | 131 +++++++++++++++--------------- generator/common.go | 23 ++++++ generator/company.go | 1 + generator/db.go | 3 + generator/education.go | 1 + generator/interests.go | 3 + generator/language.go | 1 + generator/main.go | 1 - generator/platform.go | 3 + generator/project.go | 159 +++++++++++++++++++------------------ generator/service.go | 6 +- generator/subjectMatter.go | 3 +- generator/technology.go | 7 +- 14 files changed, 199 insertions(+), 146 deletions(-) diff --git a/generator/cache.go b/generator/cache.go index 257f637..defdb76 100644 --- a/generator/cache.go +++ b/generator/cache.go @@ -1,5 +1,7 @@ package main +import "strings" + var caches = map[string]*CachePage{} type CachePage struct { @@ -23,5 +25,6 @@ func NewCache(name string) *CachePage { tracked: newTracked(), } caches[name] = out + addLinkable(strings.ToLower(name), out) return out } diff --git a/generator/client.go b/generator/client.go index 01bb128..e921534 100644 --- a/generator/client.go +++ b/generator/client.go @@ -39,17 +39,22 @@ func (pg *Client) EntryType() string { return "Client" } -func NewClient(name string, info ...string) *Client { +func NewClient(name string) *Client { out := &Client{Name: name, Projects: []*Project{}, Info: nil, projectsSet: utils.Set[string]{}} if _, exists := clients[name]; exists { panic("client already exists") } - out.Info = info clients[name] = out + addLinkable(strings.ToLower(name), out) clientsOrder = append(clientsOrder, name) return out } +func (pg *Client) WithInfo(info ...string) *Client { + pg.Info = info + return pg +} + func (pg *Client) Bytes() []byte { builder := strings.Builder{} builder.WriteString(frontmatterFor(pg.Name, "Client")) @@ -164,75 +169,75 @@ func (pg *Client) GetAllLowest() (outCaches map[string]*CachePage, outDbs map[st return } +func initClientsLast() { + +} + var ( - jdClient = NewClient("John Deere", + jdClient = NewClient("John Deere") + simpsonUniversityClient = NewClient("Simpson University") + sourceAlliesClient = NewClient("Source Allies") + critColaClient = NewClient("CritCola") + arrowNailClient = NewClient("ArrowNail LLC") + wellAwareClient = NewClient("Well Aware NC") + clarkClient = NewClient("Chapel Hill Masters Student in Public Health") + wildlifeRClient = NewClient("Wildlife mapping with R") + charityClient = NewClient("Undisclosed Charity") + teiClient = NewClient("TEI") + taeClient = NewClient("Talley Associates of Engineering") + mafcClient = NewClient("Monroe Aquatics and Fitness Center") +) + +func initClientsAfterProjectsComplete() { + jdClient = jdClient. + WithProjects(polygonBuilderProject, ogreProject, renderProject, statsProject, billingProject, explorerProject, wqdbProject, supportProject, scudsProject, ufoProject, goweProject, tileGenProject, GhaRunnersProject).WithInfo( "Most senior consulting engineer on a high-performance, global-scale, team of 4-8 at John Deere’s Intelligent Solutions Group; responsible for architecture, implementation, testing, optimization, and support of a complex set of diverse cloud services utilizing geospatiotemporal agribusiness data", "Architected, implemented, and maintained cloud infrastructure and services for ingest, distributed processing, storage, manipulation, and retrieval of data for, datastores totalling over 50PB", - "Created multiple ECS clusters for use in, and consuming data from, John Deere AI platforms", - "Designed CUDA C/C++ Kernels used through CGo for statistics and image processing via GPU", + "Created multiple "+lookup("ECS").Link()+" clusters for use in, and consuming data from, John Deere AI platforms", + "Designed "+lookup("CUDA").Link()+" "+lookup("C").Link()+"/[C++](cv/language/Cpp) Kernels used through "+lookup("CGo").Link()+" for statistics and image processing via GPU", "Improved a mission-critical image manipulation API from 65% reliability to 99.9999% success rate", - "Spearheaded implementation of an ECS cluster using an advanced topology algorithm (from a PhD thesis), achieving >100x performance gains over its original implementation on TB-scale datasets", + "Spearheaded implementation of an "+lookup("ECS").Link()+" cluster using an advanced "+lookup("topology").Link()+" algorithm (from a PhD thesis), achieving >100x performance gains over its original implementation on TB-scale datasets", "Built a Go-based compiler that transformed nested JSON instructions into machine-executable operations across EC2 clusters for for retrieving and manipulating geospatial agricultural data", - "Saved $18M of a $28M budget (64%) in 2024, while still increasing service stability and throughput", + "__Saved \\$18M of a \\$28M budget (64%)__ in 2024, while still increasing service stability and throughput", "Ensured maximum service uptime via careful design and rollout of CI/CD pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via Terraform", "Setup monitoring, dashboards, alerting, traces, and profiling (Datadog/Grafana/CloudWatch)", "Responsible for educating engineers on infrastructure, codebase, domain, and best practices", "Utilized primarily Go, Terraform, Bash, and Docker on AWS, but also used Scala, Github Actions, C/C++ with CUDA, DroneCI, python, javascript, typescript, Kotlin, and more", - "Platforms utilized: AWS (>30 separate services), Datadog, LogCentral, Rally, Azure DevOps, Github, DroneCI, Grafana, Prometheus, Confluence, and more", - ) - simpsonUniversityClient = NewClient("Simpson University", - "Upgraded the University’s payment and donation gateway. Remediated resulting bugs", - ) - sourceAlliesClient = NewClient("Source Allies", - "Source Allies internal projects", - "Upgraded company internal payment gateway to a newer version of Java Spring", - "Secured all company machines via JAMF to ensure the protection of company and client data", - "Designed and created a Slack bot integration with Small Improvements to automate monthly announcements, and employee creation and completion of personal and professional goals", - "Redesigned Jira workflows streamlining the hiring process and onboarding systems for remote coworkers", - ) - critColaClient = NewClient("CritCola", - "Consulted on hosting game servers and discord bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing GitLabCI, Terraform, CloudFlare, and AWS (EC2, EBS, IAM)", - "Created a final product with a spin-up time of approximately 3 minutes", - ) - arrowNailClient = NewClient("ArrowNail LLC", // TODO: ARROWNAIL CLIENT - "Used serverless services on AWS to support a React geospatial web app, Node API via lambda functions, and an aurora database. Provided IT and Systems Administration Support", - "Set up CI/CD pipeline in Gitlab CI to make future deployments seamless", - ) - wellAwareClient = NewClient("Well Aware NC", - "Designed, created, and hosted a website for Well Aware NC, a University of North Carolina Chapel Hill affiliated nonprofit focused on the testing of well water contaminants within North Carolina", - ) - clarkClient = NewClient("Chapel Hill Masters Student in Public Health", - "Created programs for a student doing research for his Masters Degree in Public Health. Provided data on E.Coli samples from different waterways, the programs checked the statistical validity on different E.Coli indicating kits", - ) - wildlifeRClient = NewClient("Wildlife mapping with R", - "Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps", - ) - charityClient = NewClient("Undisclosed Charity", - "Designed, created, and hosted a website for an undisclosed local charity", - ) - teiClient = NewClient("TEI", - "Internal company work for TEI", - ) - taeClient = NewClient("Talley Associates of Engineering", - "Internal company work for Talley Associates of Engineering", - ) - mafcClient = NewClient("Monroe Aquatics and Fitness Center", - "Lifeguarding work, both at the indoor and outdoor pools of the fitness center", - ) -) - -func initClientsAfterProjectsComplete() { - jdClient = jdClient.WithProjects(polygonBuilderProject, ogreProject, renderProject, statsProject, billingProject, explorerProject, wqdbProject, supportProject, scudsProject, ufoProject, goweProject, tileGenProject, GhaRunnersProject) - sourceAlliesClient = sourceAlliesClient.WithProjects(jamfProject, smallImprovementsProject, internalResumeGeneratorProject) // TODO: USE OTHERS - simpsonUniversityClient = simpsonUniversityClient.WithProjects(simpsonUnivProject) + "Platforms utilized: AWS (>30 separate services), Datadog, LogCentral, Rally, Azure DevOps, Github, DroneCI, Grafana, Prometheus, Confluence, and more") + sourceAlliesClient = sourceAlliesClient. + WithProjects(jamfProject, smallImprovementsProject, internalResumeGeneratorProject). // TODO: USE OTHERS + WithInfo( + "Source Allies internal projects", + "Upgraded company internal payment gateway to a newer version of Java Spring", + "Secured all company machines via JAMF to ensure the protection of company and client data", + "Designed and created a Slack bot integration with Small Improvements to automate monthly announcements, and employee creation and completion of personal and professional goals", + "Redesigned Jira workflows streamlining the hiring process and onboarding systems for remote coworkers", + ) + simpsonUniversityClient = simpsonUniversityClient. + WithProjects(simpsonUnivProject). + WithInfo("Upgraded the University’s payment and donation gateway. Remediated resulting bugs") // TODO: SIMPSON COLLEGE // TODO: USE! - critColaClient = critColaClient.WithProjects(CritColaProject) - wellAwareClient = wellAwareClient.WithProjects(WellAwareProject) - clarkClient = clarkClient.WithProjects(MastersDataAnalysisProject) - charityClient = charityClient.WithProjects(CharityProject) - teiClient = teiClient.WithProjects(teiProjects) // TODO: WEBSITE? // TODO: add projects (like NM, TX, IA, NC?) - taeClient = taeClient.WithProjects(taePhotoImporter) - mafcClient = mafcClient.WithProjects(mafcProjects) // TODO: Indoor and outdoor pool? - arrowNailClient = arrowNailClient.WithProjects(ArrowNailProject) - wildlifeRClient = wildlifeRClient.WithProjects(WildlifeRProject) + critColaClient = critColaClient. + WithProjects(CritColaProject). + WithInfo( + "Consulted on hosting game servers and discord bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing GitLabCI, Terraform, CloudFlare, and AWS (EC2, EBS, IAM)", + "Created a final product with a spin-up time of approximately 3 minutes", + ) + wellAwareClient = wellAwareClient.WithProjects(WellAwareProject). + WithInfo("Designed, created, and hosted a website for Well Aware NC, a University of North Carolina Chapel Hill affiliated nonprofit focused on the testing of well water contaminants within North Carolina") + clarkClient = clarkClient.WithProjects(MastersDataAnalysisProject). + WithInfo("Created programs for a student doing research for his Masters Degree in Public Health. Provided data on E.Coli samples from different waterways, the programs checked the statistical validity on different E.Coli indicating kits") + charityClient = charityClient.WithProjects(CharityProject). + WithInfo("Designed, created, and hosted a website for an undisclosed local charity") + teiClient = teiClient.WithProjects(teiProjects). + WithInfo("Internal company work for TEI") // TODO: WEBSITE? // TODO: add projects (like NM, TX, IA, NC?) + taeClient = taeClient.WithProjects(taePhotoImporter). + WithInfo("Internal company work for Talley Associates of Engineering") + mafcClient = mafcClient.WithProjects(mafcProjects). + WithInfo("Lifeguarding work, both at the indoor and outdoor pools of the fitness center") // TODO: Indoor and outdoor pool? + arrowNailClient = arrowNailClient.WithProjects(ArrowNailProject). + WithInfo("Used serverless services on AWS to support a React geospatial web app, Node API via lambda functions, and an aurora database. Provided IT and Systems Administration Support", + "Set up CI/CD pipeline in Gitlab CI to make future deployments seamless") + wildlifeRClient = wildlifeRClient.WithProjects(WildlifeRProject). + WithInfo("Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps") } diff --git a/generator/common.go b/generator/common.go index 32f1300..84b8672 100644 --- a/generator/common.go +++ b/generator/common.go @@ -309,3 +309,26 @@ type Linkable interface { // TODO: use??? EntryType() string Link() string } + +var linkables = map[string]Linkable{} // TODO: USE THIS +type NoLinkable struct{} + +func (n NoLinkable) EntryType() string { + panic("nilEntryType") +} + +func (n NoLinkable) Link() string { + return fixmeLink +} + +func addLinkable(name string, item Linkable) { + linkables[strings.ToLower(name)] = item +} + +func lookup(name string) Linkable { + if item, ok := linkables[strings.ToLower(name)]; ok { + return item + } + panic("lookup failed for " + name) // TODO: del? + return NoLinkable{} +} diff --git a/generator/company.go b/generator/company.go index 65e3927..5e2f7db 100644 --- a/generator/company.go +++ b/generator/company.go @@ -75,6 +75,7 @@ func NewCompany(name string, startMo, startYr int, endMo, endYr *int) *CompanyPa } } companies[name] = out + addLinkable(strings.ToLower(name), out) companiesOrder = append(companiesOrder, name) return out } diff --git a/generator/db.go b/generator/db.go index 411d386..2c2d9c3 100644 --- a/generator/db.go +++ b/generator/db.go @@ -1,5 +1,7 @@ package main +import "strings" + var dbs = map[string]*DbPage{} type DbPage struct { @@ -27,6 +29,7 @@ func NewDb(name string) *DbPage { // out.EquivalentLink = cloudServices[name].Link() // TODO: ????????? //} dbs[name] = out + addLinkable(strings.ToLower(name), out) // TODO: will services like DynamoDB overwrite this? return out } diff --git a/generator/education.go b/generator/education.go index 25a688d..1aadac0 100644 --- a/generator/education.go +++ b/generator/education.go @@ -28,6 +28,7 @@ func NewSchool(name string) *SchoolPage { SubjectMatters: map[SubjectMatter]struct{}{}, } schools[name] = out + addLinkable(strings.ToLower(name), out) return out } func (pg *SchoolPage) WithSummary(info string) *SchoolPage { diff --git a/generator/interests.go b/generator/interests.go index 9553c7e..83a0931 100644 --- a/generator/interests.go +++ b/generator/interests.go @@ -1,5 +1,7 @@ package main +import "strings" + var interests = map[string]*Interest{} type Interest struct { @@ -13,6 +15,7 @@ func NewInterest(name string) *Interest { tracked: newTracked(), } interests[name] = out + addLinkable(strings.ToLower(name), out) return out } diff --git a/generator/language.go b/generator/language.go index b6bc99c..8136b46 100644 --- a/generator/language.go +++ b/generator/language.go @@ -117,6 +117,7 @@ func NewLanguage(name string) *LanguagePage { SubjectMattersField: SubjectMattersField{utils.Set[SubjectMatter]{}}, } langs[name] = out + addLinkable(strings.ToLower(name), out) return out } diff --git a/generator/main.go b/generator/main.go index 2eaab8e..1e40319 100644 --- a/generator/main.go +++ b/generator/main.go @@ -42,7 +42,6 @@ func main() { initClientsAfterProjectsComplete() // Sets client on projects as well initPositionsAfterProjects() initCompaniesAfterPositions() // Must be done after positions and project setup, but before projects pages. What about clients? - // TODO: POPULATE SUBJECT MATTERS ON TECHS, SERVICES, MISCSKILLS? // Start creating actual pages diff --git a/generator/platform.go b/generator/platform.go index ec71caf..8fdce72 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -1,5 +1,7 @@ package main +import "strings" + var platforms = map[string]*PlatformPage{} type PlatformPage struct { // Datadog, Github, etc @@ -34,6 +36,7 @@ func NewPlatform(name string) *PlatformPage { SubjectMattersField: SubjectMattersField{SubjectMatters: map[SubjectMatter]struct{}{}}, } platforms[name] = out + addLinkable(strings.ToLower(name), out) return out } diff --git a/generator/project.go b/generator/project.go index f7a26ba..97b8564 100644 --- a/generator/project.go +++ b/generator/project.go @@ -29,84 +29,6 @@ func (pt projectType) isCoursework() bool { } -type projectTypeInfo interface { - Type() projectType - Finalize(*Project) - getClient() *Client - setClient(*Client) projectTypeInfo - getSchool() *SchoolPage - setSchool(page *SchoolPage) projectTypeInfo -} -type schoolProjectTypeInfo struct { - school *SchoolPage -} - -func (schoolProjectTypeInfo) Type() projectType { - return projectTypeSchool -} -func (i schoolProjectTypeInfo) Finalize(pr *Project) { - i.school.withProjects(pr) -} -func (i schoolProjectTypeInfo) getClient() *Client { - return nil -} -func (i schoolProjectTypeInfo) getSchool() *SchoolPage { - return i.school -} -func (i schoolProjectTypeInfo) setClient(cli *Client) projectTypeInfo { - return i -} -func (i schoolProjectTypeInfo) setSchool(s *SchoolPage) projectTypeInfo { - i.school = s - return i -} - -type professionalProjectTypeInfo struct { - client *Client -} - -func (professionalProjectTypeInfo) Type() projectType { - return projectTypeProfessional -} -func (i professionalProjectTypeInfo) Finalize(pr *Project) { - i.client.WithProjects(pr) -} -func (i professionalProjectTypeInfo) getClient() *Client { - return i.client -} -func (i professionalProjectTypeInfo) getSchool() *SchoolPage { - return nil -} -func (i professionalProjectTypeInfo) setClient(cli *Client) projectTypeInfo { - i.client = cli - return i -} -func (i professionalProjectTypeInfo) setSchool(s *SchoolPage) projectTypeInfo { - return i -} - -type personalProjectTypeInfo struct{} - -func (i personalProjectTypeInfo) getClient() *Client { - return nil -} -func (i personalProjectTypeInfo) getSchool() *SchoolPage { - return nil -} -func (i personalProjectTypeInfo) setClient(cli *Client) projectTypeInfo { - return i -} -func (i personalProjectTypeInfo) setSchool(s *SchoolPage) projectTypeInfo { - return i -} - -func (personalProjectTypeInfo) Type() projectType { - return projectTypePersonal -} -func (personalProjectTypeInfo) Finalize(*Project) {} - -type projectStatus string - const ( statusComplete projectStatus = "Complete" statusBuilding projectStatus = "Building" @@ -534,6 +456,7 @@ func newProject(name string, summary string, info projectTypeInfo, link *string) panic("project already exists") } projects[name] = out + addLinkable(strings.ToLower(name), out) return out } @@ -699,7 +622,7 @@ func initProjectsFinal() { WithLang("Rust", Minimal). WithDbs("Aurora", "Postgres"). WithCaches("Redis", "memcached"). - WithTechnologies("CUDA", "Github Actions", "Parquet", "Avro", "GraphQL", "SIMD", "REST API"). + WithTechnologies("CGo", "CUDA", "Github Actions", "Parquet", "Avro", "GraphQL", "SIMD", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "GraphQL Apollo", "Azure DevOps", "Jira", "Confluence"). WithCloudProvider("AWS", "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", @@ -1047,3 +970,81 @@ func initProjectsFinal() { WithTechnologies("Github Actions"). finalize() } + +type projectTypeInfo interface { + Type() projectType + Finalize(*Project) + getClient() *Client + setClient(*Client) projectTypeInfo + getSchool() *SchoolPage + setSchool(page *SchoolPage) projectTypeInfo +} +type schoolProjectTypeInfo struct { + school *SchoolPage +} + +func (schoolProjectTypeInfo) Type() projectType { + return projectTypeSchool +} +func (i schoolProjectTypeInfo) Finalize(pr *Project) { + i.school.withProjects(pr) +} +func (i schoolProjectTypeInfo) getClient() *Client { + return nil +} +func (i schoolProjectTypeInfo) getSchool() *SchoolPage { + return i.school +} +func (i schoolProjectTypeInfo) setClient(cli *Client) projectTypeInfo { + return i +} +func (i schoolProjectTypeInfo) setSchool(s *SchoolPage) projectTypeInfo { + i.school = s + return i +} + +type professionalProjectTypeInfo struct { + client *Client +} + +func (professionalProjectTypeInfo) Type() projectType { + return projectTypeProfessional +} +func (i professionalProjectTypeInfo) Finalize(pr *Project) { + i.client.WithProjects(pr) +} +func (i professionalProjectTypeInfo) getClient() *Client { + return i.client +} +func (i professionalProjectTypeInfo) getSchool() *SchoolPage { + return nil +} +func (i professionalProjectTypeInfo) setClient(cli *Client) projectTypeInfo { + i.client = cli + return i +} +func (i professionalProjectTypeInfo) setSchool(s *SchoolPage) projectTypeInfo { + return i +} + +type personalProjectTypeInfo struct{} + +func (i personalProjectTypeInfo) getClient() *Client { + return nil +} +func (i personalProjectTypeInfo) getSchool() *SchoolPage { + return nil +} +func (i personalProjectTypeInfo) setClient(cli *Client) projectTypeInfo { + return i +} +func (i personalProjectTypeInfo) setSchool(s *SchoolPage) projectTypeInfo { + return i +} + +func (personalProjectTypeInfo) Type() projectType { + return projectTypePersonal +} +func (personalProjectTypeInfo) Finalize(*Project) {} + +type projectStatus string diff --git a/generator/service.go b/generator/service.go index f826477..892a3a6 100644 --- a/generator/service.go +++ b/generator/service.go @@ -1,6 +1,9 @@ package main -import "appli.ng/cv/generator/utils" +import ( + "appli.ng/cv/generator/utils" + "strings" +) var cloudServices = map[string]*CloudServicePage{} @@ -41,6 +44,7 @@ func NewService(name string, provider string) *CloudServicePage { } subjectMatters[smCloudComputing][out.EntryType()].Add() // TODO: ok? cloudServices[name] = out + addLinkable(strings.ToLower(name), out) return out } diff --git a/generator/subjectMatter.go b/generator/subjectMatter.go index 1f0f2f7..573150c 100644 --- a/generator/subjectMatter.go +++ b/generator/subjectMatter.go @@ -12,7 +12,7 @@ type SubjectMatter string func (sm SubjectMatter) Link() string { return linkFor(string(sm), "cv", "subjectMatter", withoutSpaces(string(sm))) } -func (pg *SubjectMatter) EntryType() string { +func (pg SubjectMatter) EntryType() string { return "Subject Matter" } func (sm SubjectMatter) Bytes() []byte { @@ -28,6 +28,7 @@ func NewSubjectMatter(sm string) SubjectMatter { if _, exists := subjectMatters[out]; !exists { subjectMatters[out] = map[string]utils.Set[string]{} } + addLinkable(strings.ToLower(sm), out) return out } diff --git a/generator/technology.go b/generator/technology.go index 992f05f..015fd09 100644 --- a/generator/technology.go +++ b/generator/technology.go @@ -1,6 +1,9 @@ package main -import "appli.ng/cv/generator/utils" +import ( + "appli.ng/cv/generator/utils" + "strings" +) var techs = map[string]*TechologyPage{} @@ -44,6 +47,7 @@ func NewTechnology(name string, subjectMatters ...SubjectMatter) *TechologyPage SubjectMatters: utils.SetFrom(subjectMatters...), } techs[name] = out + addLinkable(strings.ToLower(name), out) return out } @@ -58,6 +62,7 @@ func setupTechSubjectMatters() { NewTechnology("AI Agents", smAI) NewTechnology("OpenAI API spec", smAI, smBackend) NewTechnology("CUDA", smGPU, smGraphics) + NewTechnology("CGo", smBackend) NewTechnology("REST API", smBackend, smApi) // TODO: use this everywhere necessary... NewTechnology("Avro", smBackend).WithTags("DataFormat") NewTechnology("Parquet", smBackend).WithTags("DataFormat") From 4e4c1a17a1b0888f005bf0c022d92c76b094e755 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:31:50 -0400 Subject: [PATCH 13/45] added raster render, and a lookup function for already-created things --- generator/client.go | 6 +++--- generator/platform.go | 2 ++ generator/project.go | 8 ++++++++ generator/provider.go | 1 + generator/subjectMatter.go | 5 +++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/generator/client.go b/generator/client.go index e921534..b2c5dca 100644 --- a/generator/client.go +++ b/generator/client.go @@ -197,12 +197,12 @@ func initClientsAfterProjectsComplete() { "Designed "+lookup("CUDA").Link()+" "+lookup("C").Link()+"/[C++](cv/language/Cpp) Kernels used through "+lookup("CGo").Link()+" for statistics and image processing via GPU", "Improved a mission-critical image manipulation API from 65% reliability to 99.9999% success rate", "Spearheaded implementation of an "+lookup("ECS").Link()+" cluster using an advanced "+lookup("topology").Link()+" algorithm (from a PhD thesis), achieving >100x performance gains over its original implementation on TB-scale datasets", - "Built a Go-based compiler that transformed nested JSON instructions into machine-executable operations across EC2 clusters for for retrieving and manipulating geospatial agricultural data", + "Built a "+lookup("Go").Link()+"-based compiler that transformed nested JSON instructions into machine-executable operations across clusters of "+lookup("EC2").Link()+" instances for for retrieving and manipulating geospatial agricultural data", "__Saved \\$18M of a \\$28M budget (64%)__ in 2024, while still increasing service stability and throughput", - "Ensured maximum service uptime via careful design and rollout of CI/CD pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via Terraform", + "Ensured maximum service uptime via careful design and rollout of [CI/CD](cv/subjectMatter/CI-CD) pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via "+lookup("Terraform").Link(), "Setup monitoring, dashboards, alerting, traces, and profiling (Datadog/Grafana/CloudWatch)", "Responsible for educating engineers on infrastructure, codebase, domain, and best practices", - "Utilized primarily Go, Terraform, Bash, and Docker on AWS, but also used Scala, Github Actions, C/C++ with CUDA, DroneCI, python, javascript, typescript, Kotlin, and more", + "Utilized primarily "+lookup("Go").Link()+", "+lookup("Terraform").Link()+", "+lookup("Bash").Link()+", and "+lookup("Docker").Link()+" on "+lookup("AWS").Link()+", but also used "+lookup("Scala").Link()+", "+lookup("Github Actions").Link()+", "+lookup("C").Link()+"/[C++](cv/language/Cpp) with "+lookup("CUDA").Link()+", "+lookup("DroneCI").Link()+", "+lookup("Python").Link()+", "+lookup("Javascript").Link()+", "+lookup("Typescript").Link()+", "+lookup("Kotlin").Link()+", and more", "Platforms utilized: AWS (>30 separate services), Datadog, LogCentral, Rally, Azure DevOps, Github, DroneCI, Grafana, Prometheus, Confluence, and more") sourceAlliesClient = sourceAlliesClient. WithProjects(jamfProject, smallImprovementsProject, internalResumeGeneratorProject). // TODO: USE OTHERS diff --git a/generator/platform.go b/generator/platform.go index 8fdce72..dfe358a 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -75,4 +75,6 @@ func setupPlatformSubjectMatters() { WithSubjectMatters(smCommunication) NewPlatform("Jamf"). WithSubjectMatters(smCybersecurity) + NewPlatform("DroneCI"). + WithSubjectMatters(smCiCd) } diff --git a/generator/project.go b/generator/project.go index 97b8564..1f3a978 100644 --- a/generator/project.go +++ b/generator/project.go @@ -486,6 +486,7 @@ var ( scudsProject = NewProfessionalProject("Scuds API", fixmeLink, jdClient, nil) ufoProject = NewProfessionalProject("UFO API", fixmeLink, jdClient, nil) goweProject = NewProfessionalProject("Gowe Builder", fixmeLink, jdClient, nil) + rasterRenderProject = NewProfessionalProject("Raster Render", fixmeLink, jdClient, nil) simpsonUnivProject = NewProfessionalProject("Simpson University", fixmeLink, simpsonUniversityClient, nil) // TODO: MORE! jamfProject = NewProfessionalProject("JAMF companywide setup", fixmeLink, sourceAlliesClient, nil) smallImprovementsProject = NewProfessionalProject("Small Improvements Bot", fixmeLink, sourceAlliesClient, nil) @@ -737,6 +738,12 @@ func initProjectsFinal() { WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smClusterComputing, smDistributedComputing, smLinearAlgebra, smBackend). finalize() + rasterRenderProject = rasterRenderProject. // TODO: this whole thing! + WithStatus(statusComplete). + WithSummary(fixmeLink). + WithPlatforms("DroneCI"). + WithSubjectMatters(smBackend). + finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! WithSummary("SUMMARY HERE"). // TODO: MORE! WithLang("Html", Regularly). @@ -907,6 +914,7 @@ func initProjectsFinal() { WithLang("Java", Some, smBackend). WithLang("Javascript", Often, smFrontend). WithLang("Typescript", Often, smFullStack). + WithLang("Kotlin", Rarely, smBackend). WithTechnologies("Kubernetes"). WithSubjectMatters(smApi, smCryptocurrency). finalize() diff --git a/generator/provider.go b/generator/provider.go index d06f8ca..de0f167 100644 --- a/generator/provider.go +++ b/generator/provider.go @@ -79,6 +79,7 @@ func NewCloudProvider(name string, services ...string) *CloudProviderPage { } subjectMatters[smCloudComputing][prov.EntryType()].Add() // TODO: ok? providers[name] = prov + addLinkable(name, prov) return prov } diff --git a/generator/subjectMatter.go b/generator/subjectMatter.go index 573150c..893f2fd 100644 --- a/generator/subjectMatter.go +++ b/generator/subjectMatter.go @@ -10,6 +10,9 @@ var subjectMatters = map[SubjectMatter]map[string]utils.Set[string]{} // Map of type SubjectMatter string func (sm SubjectMatter) Link() string { + if string(sm) == "CI-CD" { + return linkFor("CI/CD", "cv", "subjectMatter", withoutSpaces(string(sm))) + } return linkFor(string(sm), "cv", "subjectMatter", withoutSpaces(string(sm))) } func (pg SubjectMatter) EntryType() string { @@ -121,4 +124,6 @@ func setupSubjectMatters() { setupServiceSubjectMatters() setupTechSubjectMatters() setupPlatformSubjectMatters() + linkables["cicd"] = smCiCd + linkables["ci/cd"] = smCiCd } From 5d9186648832ae0afc795e3381f96e38092c7545 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:39:04 -0400 Subject: [PATCH 14/45] added another project --- generator/project.go | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/generator/project.go b/generator/project.go index 1f3a978..a554e42 100644 --- a/generator/project.go +++ b/generator/project.go @@ -507,12 +507,15 @@ var ( WildlifeRProject = NewProfessionalProject("Wildlife R Data Analysis", fixmeLink, wildlifeRClient, nil) MastersDataAnalysisProject = NewProfessionalProject("Masters Data Analysis", fixmeLink, clarkClient, nil) capstoneProject = NewSchoolProject("Capstone Project-Uranium Silicide Accident Tolerant Fuel cycle design for Duke Energy Catawba Nuclear Plant", "FIX M_E", schoolNCSU, &coreShufflerUrl) - projectLinAlgCryptography = NewSchoolProject("Linear algebra cryptography algorithm", "FIX M_E", schoolNCSU, nil) - cherenkovProject = NewSchoolProject("Cherenkov radiation sensor", fixmeLink, schoolNCSU, nil) - roboticsTeamProject = NewSchoolProject("Robotics team 3720", fixmeLink, schoolCata, nil) - cncLaserCutterProject = NewSchoolProject("CNC Laser Cutter", fixmeLink, schoolCata, nil) - aerospaceFinalProject = NewSchoolProject("Aerospace senior design course", "Designed, created, and tested a rocket from scratch", schoolCata, nil) - miscSmallPersonalProjects = NewPersonalProject("Misc small personal projects", "A conglomeration of personal projects which did not each deserve their own entry", nil) + reactorAnalysisFinal = NewSchoolProject("Reactor Analysis Exam", "Reactor analysis final exam code", schoolNCSU, utils.Pointer(fixmeLink)) // TODO: ADD REAL LINK! + monteCarloProject = NewSchoolProject("MonteCarlo scattering and decay project", "FIX_ME", schoolNCSU, utils.Pointer(fixmeLink)) // TODO: ADD REAL LINK! + // TODO; FORTRAN SCHOOL PROJECT + projectLinAlgCryptography = NewSchoolProject("Linear algebra cryptography algorithm", "FIX M_E", schoolNCSU, nil) + cherenkovProject = NewSchoolProject("Cherenkov radiation sensor", fixmeLink, schoolNCSU, nil) + roboticsTeamProject = NewSchoolProject("Robotics team 3720", fixmeLink, schoolCata, nil) + cncLaserCutterProject = NewSchoolProject("CNC Laser Cutter", fixmeLink, schoolCata, nil) + aerospaceFinalProject = NewSchoolProject("Aerospace senior design course", "Designed, created, and tested a rocket from scratch", schoolCata, nil) + miscSmallPersonalProjects = NewPersonalProject("Misc small personal projects", "A conglomeration of personal projects which did not each deserve their own entry", nil) ) func initProjectsFinal() { @@ -865,6 +868,19 @@ func initProjectsFinal() { WithSubjectMatters(smNuclearEngineering, smLinearAlgebra, smFluidMechanics, smThermodynamics, smParticlePhysics). WithInterests(string(smNuclearEngineering)). finalize() + reactorAnalysisFinal = reactorAnalysisFinal. + WithLang("Javascript", Extensively). + WithStatus(statusComplete). + WithSummary("Final exam for the final course of my degree"). // TODO: this + WithSubjectMatters(smNuclearEngineering, smThermodynamics, smFluidMechanics). + finalize() + monteCarloProject = monteCarloProject. + WithSummary(fixmeLink). + WithStatus(statusComplete). + WithInterests(string(smParticlePhysics)). // TODO: ok? + WithLang("Java", Often). + WithSubjectMatters(smParticlePhysics, smNuclearEngineering). + finalize() projectLinAlgCryptography = projectLinAlgCryptography. WithSummary("Created and analyzed the efficacy of a cryptographic algorithm utilizing basic matrix mathematics. STATS"). // TODO: this WithMiscSkills("Linear Algebra"). @@ -875,6 +891,7 @@ func initProjectsFinal() { cherenkovProject = cherenkovProject.WithStatus(statusComplete). WithSummary("Designed and built a sensor for Cherenkov radiation, which was tested by lowering the sensor into the core of the PULSTAR reactor at "+schoolNCSU.Link()+". The sensor utilized and Arduino for signal processing."). // TODO: this WithLang("Python", Often). + WithLang("Fortran", Often). WithTechnologies("Arduino"). WithPlatforms("Github"). WithSubjectMatters(smNuclearEngineering, smLinearAlgebra, smParticlePhysics, smStatistics, smEmbeddedSystems, smElectronics). From 30c686e032fc0c4495d93412b6cfed819c37a6d6 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:41:54 -0400 Subject: [PATCH 15/45] finish adding to client --- generator/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/generator/client.go b/generator/client.go index b2c5dca..e4c709b 100644 --- a/generator/client.go +++ b/generator/client.go @@ -197,13 +197,13 @@ func initClientsAfterProjectsComplete() { "Designed "+lookup("CUDA").Link()+" "+lookup("C").Link()+"/[C++](cv/language/Cpp) Kernels used through "+lookup("CGo").Link()+" for statistics and image processing via GPU", "Improved a mission-critical image manipulation API from 65% reliability to 99.9999% success rate", "Spearheaded implementation of an "+lookup("ECS").Link()+" cluster using an advanced "+lookup("topology").Link()+" algorithm (from a PhD thesis), achieving >100x performance gains over its original implementation on TB-scale datasets", - "Built a "+lookup("Go").Link()+"-based compiler that transformed nested JSON instructions into machine-executable operations across clusters of "+lookup("EC2").Link()+" instances for for retrieving and manipulating geospatial agricultural data", + "Built a "+lookup("Go").Link()+"-based compiler that transformed nested "+lookup("JSON").Link()+" instructions into machine-executable operations across clusters of "+lookup("EC2").Link()+" instances for for retrieving and manipulating geospatial agricultural data", "__Saved \\$18M of a \\$28M budget (64%)__ in 2024, while still increasing service stability and throughput", "Ensured maximum service uptime via careful design and rollout of [CI/CD](cv/subjectMatter/CI-CD) pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via "+lookup("Terraform").Link(), - "Setup monitoring, dashboards, alerting, traces, and profiling (Datadog/Grafana/CloudWatch)", + "Setup monitoring, dashboards, alerting, traces, and profiling ("+lookup("Datadog").Link()+"/"+lookup("Grafana").Link()+"/"+lookup("Cloudwatch").Link()+")", "Responsible for educating engineers on infrastructure, codebase, domain, and best practices", "Utilized primarily "+lookup("Go").Link()+", "+lookup("Terraform").Link()+", "+lookup("Bash").Link()+", and "+lookup("Docker").Link()+" on "+lookup("AWS").Link()+", but also used "+lookup("Scala").Link()+", "+lookup("Github Actions").Link()+", "+lookup("C").Link()+"/[C++](cv/language/Cpp) with "+lookup("CUDA").Link()+", "+lookup("DroneCI").Link()+", "+lookup("Python").Link()+", "+lookup("Javascript").Link()+", "+lookup("Typescript").Link()+", "+lookup("Kotlin").Link()+", and more", - "Platforms utilized: AWS (>30 separate services), Datadog, LogCentral, Rally, Azure DevOps, Github, DroneCI, Grafana, Prometheus, Confluence, and more") + "Platforms utilized: AWS (>30 separate services), "+lookup("Datadog").Link()+", "+lookup("LogCentral").Link()+", "+lookup("Rally").Link()+", "+lookup("Azure DevOps").Link()+", "+lookup("Github").Link()+", "+lookup("DroneCI").Link()+", "+lookup("Grafana").Link()+", "+lookup("Prometheus").Link()+", "+lookup("Confluence").Link()+", and more") sourceAlliesClient = sourceAlliesClient. WithProjects(jamfProject, smallImprovementsProject, internalResumeGeneratorProject). // TODO: USE OTHERS WithInfo( From 37ab8730cfba3c0dc85c959c67deceaa9c1db19e Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:47:07 -0400 Subject: [PATCH 16/45] last client links for now --- generator/client.go | 16 +++++++++------- generator/platform.go | 2 ++ generator/project.go | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/generator/client.go b/generator/client.go index e4c709b..dbbba1a 100644 --- a/generator/client.go +++ b/generator/client.go @@ -208,10 +208,10 @@ func initClientsAfterProjectsComplete() { WithProjects(jamfProject, smallImprovementsProject, internalResumeGeneratorProject). // TODO: USE OTHERS WithInfo( "Source Allies internal projects", - "Upgraded company internal payment gateway to a newer version of Java Spring", - "Secured all company machines via JAMF to ensure the protection of company and client data", - "Designed and created a Slack bot integration with Small Improvements to automate monthly announcements, and employee creation and completion of personal and professional goals", - "Redesigned Jira workflows streamlining the hiring process and onboarding systems for remote coworkers", + "Upgraded company internal payment gateway to a newer version of "+lookup("Java").Link()+" "+lookup("Spring").Link(), + "Secured all company machines via "+lookup("JAMF").Link()+" to ensure the protection of company and client data", + "Designed and created a "+lookup("Slack").Link()+" bot integration with "+lookup("Small Improvements").Link()+" to automate monthly announcements, and employee creation and completion of personal and professional goals", + "Redesigned "+lookup("Jira").Link()+" workflows streamlining the hiring process and onboarding systems for remote coworkers", ) simpsonUniversityClient = simpsonUniversityClient. WithProjects(simpsonUnivProject). @@ -220,7 +220,8 @@ func initClientsAfterProjectsComplete() { critColaClient = critColaClient. WithProjects(CritColaProject). WithInfo( - "Consulted on hosting game servers and discord bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing GitLabCI, Terraform, CloudFlare, and AWS (EC2, EBS, IAM)", + // TODO; link to discord + "Consulted on hosting game servers and "+lookup("Discord").Link()+" bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing "+lookup("GitlabCI").Link()+", "+lookup("Terraform").Link()+", "+lookup("Cloudflare").Link()+", and "+lookup("AWS").Link()+" ("+lookup("EC2").Link()+", "+lookup("EBS").Link()+", "+lookup("IAM").Link()+")", "Created a final product with a spin-up time of approximately 3 minutes", ) wellAwareClient = wellAwareClient.WithProjects(WellAwareProject). @@ -235,9 +236,10 @@ func initClientsAfterProjectsComplete() { WithInfo("Internal company work for Talley Associates of Engineering") mafcClient = mafcClient.WithProjects(mafcProjects). WithInfo("Lifeguarding work, both at the indoor and outdoor pools of the fitness center") // TODO: Indoor and outdoor pool? + // TODO: links in text for everything below this arrowNailClient = arrowNailClient.WithProjects(ArrowNailProject). - WithInfo("Used serverless services on AWS to support a React geospatial web app, Node API via lambda functions, and an aurora database. Provided IT and Systems Administration Support", - "Set up CI/CD pipeline in Gitlab CI to make future deployments seamless") + WithInfo("Used "+lookup("Serverless").Link()+" services on "+lookup("AWS").Link()+" to support a "+lookup("React").Link()+" geospatial web app, Node API via "+lookup("lambda").Link()+" functions, and an "+lookup("Aurora").Link()+" database. Provided IT and Systems Administration Support", + "Set up [CI/CD](cv/subjectMatter/CI-CD) pipeline in "+lookup("Gitlab CI").Link()+" to make future deployments seamless") wildlifeRClient = wildlifeRClient.WithProjects(WildlifeRProject). WithInfo("Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps") } diff --git a/generator/platform.go b/generator/platform.go index dfe358a..464c1b8 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -77,4 +77,6 @@ func setupPlatformSubjectMatters() { WithSubjectMatters(smCybersecurity) NewPlatform("DroneCI"). WithSubjectMatters(smCiCd) + NewPlatform("Discord"). + WithSubjectMatters(smCommunication) } diff --git a/generator/project.go b/generator/project.go index a554e42..1511037 100644 --- a/generator/project.go +++ b/generator/project.go @@ -845,7 +845,7 @@ func initProjectsFinal() { WithLang("Bash", Some). WithLang("Javascript", Rarely). WithTechnologies("Gitlab CI"). - WithPlatforms("Gitlab"). + WithPlatforms("Gitlab", "Discord"). WithCloudProvider("AWS", "S3", "EC2", "IAM", "SecretsManager", "ECR", "Route53"). WithSubjectMatters(smBackend, smCiCd, smDevOps, smScripting). From 5b560cad1c3db4c8e058678546527c42f1ed42af Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:48:57 -0400 Subject: [PATCH 17/45] should be good for now --- generator/client.go | 2 +- generator/platform.go | 2 ++ generator/project.go | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/generator/client.go b/generator/client.go index dbbba1a..8250c95 100644 --- a/generator/client.go +++ b/generator/client.go @@ -221,7 +221,7 @@ func initClientsAfterProjectsComplete() { WithProjects(CritColaProject). WithInfo( // TODO; link to discord - "Consulted on hosting game servers and "+lookup("Discord").Link()+" bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing "+lookup("GitlabCI").Link()+", "+lookup("Terraform").Link()+", "+lookup("Cloudflare").Link()+", and "+lookup("AWS").Link()+" ("+lookup("EC2").Link()+", "+lookup("EBS").Link()+", "+lookup("IAM").Link()+")", + "Consulted on hosting game servers and "+lookup("Discord").Link()+" bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing "+lookup("Gitlab CI").Link()+", "+lookup("Terraform").Link()+", "+lookup("Cloudflare").Link()+", and "+lookup("AWS").Link()+" ("+lookup("EC2").Link()+", "+lookup("EBS").Link()+", "+lookup("IAM").Link()+")", "Created a final product with a spin-up time of approximately 3 minutes", ) wellAwareClient = wellAwareClient.WithProjects(WellAwareProject). diff --git a/generator/platform.go b/generator/platform.go index 464c1b8..d48aff1 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -79,4 +79,6 @@ func setupPlatformSubjectMatters() { WithSubjectMatters(smCiCd) NewPlatform("Discord"). WithSubjectMatters(smCommunication) + NewPlatform("Rally"). + WithSubjectMatters(smObservability) } diff --git a/generator/project.go b/generator/project.go index 1511037..0648569 100644 --- a/generator/project.go +++ b/generator/project.go @@ -543,7 +543,7 @@ func initProjectsFinal() { WithLang("Python", Rarely, smScripting). WithLang("Rust", Minimal, smScripting). WithCloudProvider("AWS", - "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", "ApiGateway", // TODO: FARGATE NOT BEING ON THIS LIST IS CAUSING PROBLEMS + "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", "ApiGateway", "EBS", // TODO: FARGATE NOT BEING ON THIS LIST IS CAUSING PROBLEMS ). WithDbs("Aurora", "DynamoDB", "Postgres"). WithCaches("Redis", "memcached"). @@ -744,7 +744,7 @@ func initProjectsFinal() { rasterRenderProject = rasterRenderProject. // TODO: this whole thing! WithStatus(statusComplete). WithSummary(fixmeLink). - WithPlatforms("DroneCI"). + WithPlatforms("DroneCI", "Rally"). WithSubjectMatters(smBackend). finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! From ff2be2f7177c74c0338b52b78d9f952d79d1e195 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:53:41 -0400 Subject: [PATCH 18/45] try cplusplus instead of cpp --- generator/language.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/generator/language.go b/generator/language.go index 8136b46..ac9f39a 100644 --- a/generator/language.go +++ b/generator/language.go @@ -34,6 +34,9 @@ func (pg *LanguagePage) Link() string { if pg == nil { return "NO_LINK" } + if pg.Name == "Cpp" { + return linkFor("C++", "cv", "language", withoutSpaces(pg.Name)) + } return linkFor(pg.Name, "cv", "language", withoutSpaces(pg.Name)) } From 26c7aa271716d119e21014760030d1dbdc5fd33c Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:54:11 -0400 Subject: [PATCH 19/45] a comment --- generator/language.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/language.go b/generator/language.go index ac9f39a..88f78b8 100644 --- a/generator/language.go +++ b/generator/language.go @@ -35,7 +35,7 @@ func (pg *LanguagePage) Link() string { return "NO_LINK" } if pg.Name == "Cpp" { - return linkFor("C++", "cv", "language", withoutSpaces(pg.Name)) + return linkFor("C++", "cv", "language", withoutSpaces(pg.Name)) // TODO; will this need escaping on the plusses? } return linkFor(pg.Name, "cv", "language", withoutSpaces(pg.Name)) } From 9db00c61464fc547c08a60c882f527577c33f6b0 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 17:58:23 -0400 Subject: [PATCH 20/45] fix tech tags --- generator/common.go | 4 ++-- generator/main.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/generator/common.go b/generator/common.go index 84b8672..f67313e 100644 --- a/generator/common.go +++ b/generator/common.go @@ -148,10 +148,10 @@ func newTracked() *tracked { Tags: utils.Set[string]{}, } } -func (pg *tracked) Bytes(title string, typ string) []byte { +func (pg *tracked) Bytes(title string, tags ...string) []byte { builder := strings.Builder{} if title != "" { - builder.WriteString(frontmatterFor(title, typ)) + builder.WriteString(frontmatterFor(title, tags...)) } if pg.Companies != nil && len(pg.Companies) > 0 { diff --git a/generator/main.go b/generator/main.go index 1e40319..d4e5ea2 100644 --- a/generator/main.go +++ b/generator/main.go @@ -619,7 +619,8 @@ func createTechnologiesPages() { if len(tech.SubjectMatters) == 0 { panic("no subject matter on technology " + name) } - WriteCVFile("technology/"+withoutSpaces(name)+".md", string(tech.Bytes(name, "Technology"))) + typePlusTags := append([]string{"Technology"}, tech.Tags.AsSlice()...) + WriteCVFile("technology/"+withoutSpaces(name)+".md", string(tech.Bytes(name, typePlusTags...))) } } From 943a3076c20f41689406ec3c54ae834e5f9aad60 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:07:19 -0400 Subject: [PATCH 21/45] EOF --- generator/project.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/generator/project.go b/generator/project.go index 0648569..f24fc65 100644 --- a/generator/project.go +++ b/generator/project.go @@ -543,7 +543,7 @@ func initProjectsFinal() { WithLang("Python", Rarely, smScripting). WithLang("Rust", Minimal, smScripting). WithCloudProvider("AWS", - "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", "ApiGateway", "EBS", // TODO: FARGATE NOT BEING ON THIS LIST IS CAUSING PROBLEMS + "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", "ApiGateway", "EBS", "ELB", "ALB", ). WithDbs("Aurora", "DynamoDB", "Postgres"). WithCaches("Redis", "memcached"). @@ -667,7 +667,7 @@ func initProjectsFinal() { WithSubjectMatters(smApi, smGeospatial, smCiCd, smContainerization, smIAC, smFullStack). finalize() wqdbProject = wqdbProject.WithStatus(statusComplete). - WithSummary("SUMMARY HERE"). // TODO: MORE! + WithSummary("Work queue database for the various builders"). // TODO: list builders WithLang("SQL", Often). WithLang("Go", Often). WithLang("Scala", Often). @@ -682,7 +682,7 @@ func initProjectsFinal() { WithSubjectMatters(smCiCd, smIAC, smBackend). finalize() supportProject = supportProject.WithStatus(statusComplete). - WithSummary("SUMMARY HERE"). // TODO: MORE! + WithSummary("Support API cluster for troubleshooting"). WithLang("SQL", Regularly). WithLang("Go", Often). WithLang("Terraform", Often). @@ -712,7 +712,7 @@ func initProjectsFinal() { WithSubjectMatters(smApi, smGeospatial, smCiCd, smContainerization, smIAC, smStatistics, smBackend). finalize() ufoProject = ufoProject.WithStatus(statusMaintaining). - WithSummary("SUMMARY HERE"). // TODO: MORE! + WithSummary("Unified Field Operations API Cluster"). WithLang("Scala", Extensively). WithLang("Go", Regularly). WithLang("Terraform", Regularly). @@ -742,10 +742,14 @@ func initProjectsFinal() { WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smClusterComputing, smDistributedComputing, smLinearAlgebra, smBackend). finalize() rasterRenderProject = rasterRenderProject. // TODO: this whole thing! + WithSummary("Tile renderer API service cluster. Later replaced by the Render Cluster"). // TODO: link to render cluster + WithLang("Scala", Extensively). + WithLang("Terraform", Regularly). + WithLang("Bash", Some). WithStatus(statusComplete). - WithSummary(fixmeLink). WithPlatforms("DroneCI", "Rally"). WithSubjectMatters(smBackend). + WithCloudProvider("AWS", "ECS", "EC2", "API Gateway", "IAM", "ELB", "ALB"). finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -754,9 +758,8 @@ func initProjectsFinal() { WithLang("Javascript", Some). WithSubjectMatters(smFrontend). WithTechnologies("Drupal"). - WithPlatforms("Confluence"). // TODO: SOURCE JAMF + WithPlatforms("Confluence"). finalize() - // TODO: sai project for JAMF mushDbProject = mushDbProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! WithLang("Go", Extensively). @@ -774,7 +777,7 @@ func initProjectsFinal() { WithInterests(string(smMycology)). finalize() cvProject = cvProject.WithStatus(statusBuilding). - WithSummary("This project! A generator which creates markdown files that can be viewed via Obsidian, or published to the web."). // TODO: MORE! + WithSummary("This project! A generator which creates markdown files that can be viewed via Obsidian, or published to the web."). WithLang("Go", Extensively). WithLang("Terraform", Often). WithLang("Bash", Some). @@ -784,7 +787,6 @@ func initProjectsFinal() { WithLang("CSS", Rarely). WithLang("SCSS", Rarely, smFrontend). WithTechnologies("Github Actions", "Obsidian", "Markdown", "Quartz", "Quartz 4"). - // TODO: add info to technologies???? WithPlatforms("Github"). WithCloudProvider("AWS", "S3", "IAM", "Cloudfront", "Cloudfront Functions", "ACM"). WithSubjectMatters(smCiCd, smFrontend, smDevOps, smServerless). @@ -811,7 +813,9 @@ func initProjectsFinal() { WithInterests(string(smMycology)). finalize() coreShufflerProject = coreShufflerProject.WithStatus(statusComplete). - WithSummary("SUMMARY HERE"). // TODO: MORE! + // TODO: MORE! LINKS! + WithSummary("Related to capstone project. Takes fuel assembly layout input, allows you to shuffle them, and outputs the CASMO and SIMULATE files necessary to run a cycle with the new layout."). + // TODO: ADD A PICTURE!!!!! WithLang("Javascript", Extensively). WithLang("Html", Extensively). WithLang("CSS", Extensively). @@ -871,7 +875,7 @@ func initProjectsFinal() { reactorAnalysisFinal = reactorAnalysisFinal. WithLang("Javascript", Extensively). WithStatus(statusComplete). - WithSummary("Final exam for the final course of my degree"). // TODO: this + WithSummary("Final exam for the final course of my Nuclear Engineering Bachelors degree"). // TODO: this! LInk! WithSubjectMatters(smNuclearEngineering, smThermodynamics, smFluidMechanics). finalize() monteCarloProject = monteCarloProject. @@ -882,14 +886,14 @@ func initProjectsFinal() { WithSubjectMatters(smParticlePhysics, smNuclearEngineering). finalize() projectLinAlgCryptography = projectLinAlgCryptography. - WithSummary("Created and analyzed the efficacy of a cryptographic algorithm utilizing basic matrix mathematics. STATS"). // TODO: this + WithSummary("Created and analyzed the efficacy of a cryptographic algorithm utilizing basic matrix mathematics."). WithMiscSkills("Linear Algebra"). WithInterests("Cryptography"). WithSubjectMatters(smCybersecurity, smCryptography). WithInterests(string(smCryptography), string(smCybersecurity)). finalize() cherenkovProject = cherenkovProject.WithStatus(statusComplete). - WithSummary("Designed and built a sensor for Cherenkov radiation, which was tested by lowering the sensor into the core of the PULSTAR reactor at "+schoolNCSU.Link()+". The sensor utilized and Arduino for signal processing."). // TODO: this + WithSummary("Designed and built a sensor for Cherenkov radiation, which was tested by lowering the sensor into the core of the PULSTAR reactor at "+schoolNCSU.Link()+". The sensor utilized and Arduino for signal processing."). WithLang("Python", Often). WithLang("Fortran", Often). WithTechnologies("Arduino"). From 7a01376d954b8afd409ae26099384c6d94b993c0 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:08:07 -0400 Subject: [PATCH 22/45] Route53 in one spot --- generator/project.go | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/generator/project.go b/generator/project.go index f24fc65..d4f0e6c 100644 --- a/generator/project.go +++ b/generator/project.go @@ -742,24 +742,24 @@ func initProjectsFinal() { WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smClusterComputing, smDistributedComputing, smLinearAlgebra, smBackend). finalize() rasterRenderProject = rasterRenderProject. // TODO: this whole thing! - WithSummary("Tile renderer API service cluster. Later replaced by the Render Cluster"). // TODO: link to render cluster - WithLang("Scala", Extensively). - WithLang("Terraform", Regularly). - WithLang("Bash", Some). - WithStatus(statusComplete). - WithPlatforms("DroneCI", "Rally"). - WithSubjectMatters(smBackend). - WithCloudProvider("AWS", "ECS", "EC2", "API Gateway", "IAM", "ELB", "ALB"). - finalize() + WithSummary("Tile renderer API service cluster. Later replaced by the Render Cluster"). // TODO: link to render cluster + WithLang("Scala", Extensively). + WithLang("Terraform", Regularly). + WithLang("Bash", Some). + WithStatus(statusComplete). + WithPlatforms("DroneCI", "Rally"). + WithSubjectMatters(smBackend). + WithCloudProvider("AWS", "ECS", "EC2", "API Gateway", "IAM", "ELB", "ALB"). + finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! - WithSummary("SUMMARY HERE"). // TODO: MORE! - WithLang("Html", Regularly). - WithLang("CSS", Regularly). - WithLang("Javascript", Some). - WithSubjectMatters(smFrontend). - WithTechnologies("Drupal"). - WithPlatforms("Confluence"). - finalize() + WithSummary("SUMMARY HERE"). // TODO: MORE! + WithLang("Html", Regularly). + WithLang("CSS", Regularly). + WithLang("Javascript", Some). + WithSubjectMatters(smFrontend). + WithTechnologies("Drupal"). + WithPlatforms("Confluence"). + finalize() mushDbProject = mushDbProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! WithLang("Go", Extensively). @@ -997,6 +997,7 @@ func initProjectsFinal() { WithTechnologies("Spring", "REST API"). WithSubjectMatters(smApi, smFullStack). WithTechnologies("Github Actions"). + WithCloudProvider("AWS", "Route53"). // TODO: add route53 everywhere needed finalize() } From d862702385f0485f3adcba09d28ccbc3d9893bc1 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:09:03 -0400 Subject: [PATCH 23/45] a little bit of cleanup --- generator/project.go | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/generator/project.go b/generator/project.go index d4f0e6c..5c05515 100644 --- a/generator/project.go +++ b/generator/project.go @@ -34,13 +34,13 @@ const ( statusBuilding projectStatus = "Building" statusMaintaining projectStatus = "Maintaining" statusNotOnTeam projectStatus = "No longer part of project" - statusShelved projectStatus = "Shelved or not actively working on this project" // TODO: change + statusShelved projectStatus = "Shelved or not actively working on this project" ) type Project struct { Name string Summary string - Status projectStatus // TODO: USE THESE!!!! + Status projectStatus TypeInfo projectTypeInfo link *string @@ -51,7 +51,7 @@ type Project struct { Technologies utils.Set[string] Platforms utils.Set[string] MiscSkills utils.Set[string] - // TODO: MORE! + RelatedInterests utils.Set[string] SubjectMatters utils.Set[SubjectMatter] Tags utils.Set[Tag] @@ -324,7 +324,6 @@ func (p *Project) WithCloudProvider(name string, services ...string) *Project { return p } func (p *Project) WithDbs(names ...string) *Project { - // TODO: Aurora, DynamoDB, DocumentDB, RDS should all point to the AWS service!!!!??? (probably not) for _, dbName := range names { if !p.Dbs.Contains(dbName) { p.Dbs.Add(dbName) @@ -742,24 +741,24 @@ func initProjectsFinal() { WithSubjectMatters(smGeospatial, smCiCd, smContainerization, smIAC, smClusterComputing, smDistributedComputing, smLinearAlgebra, smBackend). finalize() rasterRenderProject = rasterRenderProject. // TODO: this whole thing! - WithSummary("Tile renderer API service cluster. Later replaced by the Render Cluster"). // TODO: link to render cluster - WithLang("Scala", Extensively). - WithLang("Terraform", Regularly). - WithLang("Bash", Some). - WithStatus(statusComplete). - WithPlatforms("DroneCI", "Rally"). - WithSubjectMatters(smBackend). - WithCloudProvider("AWS", "ECS", "EC2", "API Gateway", "IAM", "ELB", "ALB"). - finalize() + WithSummary("Tile renderer API service cluster. Later replaced by the Render Cluster"). // TODO: link to render cluster + WithLang("Scala", Extensively). + WithLang("Terraform", Regularly). + WithLang("Bash", Some). + WithStatus(statusComplete). + WithPlatforms("DroneCI", "Rally"). + WithSubjectMatters(smBackend). + WithCloudProvider("AWS", "ECS", "EC2", "API Gateway", "IAM", "ELB", "ALB"). + finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! - WithSummary("SUMMARY HERE"). // TODO: MORE! - WithLang("Html", Regularly). - WithLang("CSS", Regularly). - WithLang("Javascript", Some). - WithSubjectMatters(smFrontend). - WithTechnologies("Drupal"). - WithPlatforms("Confluence"). - finalize() + WithSummary("SUMMARY HERE"). // TODO: MORE! + WithLang("Html", Regularly). + WithLang("CSS", Regularly). + WithLang("Javascript", Some). + WithSubjectMatters(smFrontend). + WithTechnologies("Drupal"). + WithPlatforms("Confluence"). + finalize() mushDbProject = mushDbProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! WithLang("Go", Extensively). From b8d37bbeb0d79a2d051581a544eefd71170393b7 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:09:50 -0400 Subject: [PATCH 24/45] retry cicd sm linking --- generator/subjectMatter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/subjectMatter.go b/generator/subjectMatter.go index 893f2fd..7e8fa2f 100644 --- a/generator/subjectMatter.go +++ b/generator/subjectMatter.go @@ -11,7 +11,7 @@ type SubjectMatter string func (sm SubjectMatter) Link() string { if string(sm) == "CI-CD" { - return linkFor("CI/CD", "cv", "subjectMatter", withoutSpaces(string(sm))) + return linkFor("CI\\/CD", "cv", "subjectMatter", withoutSpaces(string(sm))) } return linkFor(string(sm), "cv", "subjectMatter", withoutSpaces(string(sm))) } From 55cad5e0e6880d32a8d2d4804389b049a7e96ad1 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:21:06 -0400 Subject: [PATCH 25/45] revert CICD --- generator/subjectMatter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/subjectMatter.go b/generator/subjectMatter.go index 7e8fa2f..db0bc19 100644 --- a/generator/subjectMatter.go +++ b/generator/subjectMatter.go @@ -11,7 +11,7 @@ type SubjectMatter string func (sm SubjectMatter) Link() string { if string(sm) == "CI-CD" { - return linkFor("CI\\/CD", "cv", "subjectMatter", withoutSpaces(string(sm))) + return linkFor("CI-CD", "cv", "subjectMatter", withoutSpaces(string(sm))) // TODO; FIX SO IT SAYS CI/CD } return linkFor(string(sm), "cv", "subjectMatter", withoutSpaces(string(sm))) } From 7e94bde7758e240a10c4edf5efb176ee7eefe22f Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:22:51 -0400 Subject: [PATCH 26/45] some quick fixes --- generator/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/generator/client.go b/generator/client.go index 8250c95..f8cda5a 100644 --- a/generator/client.go +++ b/generator/client.go @@ -193,13 +193,13 @@ func initClientsAfterProjectsComplete() { WithProjects(polygonBuilderProject, ogreProject, renderProject, statsProject, billingProject, explorerProject, wqdbProject, supportProject, scudsProject, ufoProject, goweProject, tileGenProject, GhaRunnersProject).WithInfo( "Most senior consulting engineer on a high-performance, global-scale, team of 4-8 at John Deere’s Intelligent Solutions Group; responsible for architecture, implementation, testing, optimization, and support of a complex set of diverse cloud services utilizing geospatiotemporal agribusiness data", "Architected, implemented, and maintained cloud infrastructure and services for ingest, distributed processing, storage, manipulation, and retrieval of data for, datastores totalling over 50PB", - "Created multiple "+lookup("ECS").Link()+" clusters for use in, and consuming data from, John Deere AI platforms", + "Created multiple "+lookup("ECS").Link()+" clusters for use in, and consuming data from, John Deere "+lookup("AI").Link()+" platforms", "Designed "+lookup("CUDA").Link()+" "+lookup("C").Link()+"/[C++](cv/language/Cpp) Kernels used through "+lookup("CGo").Link()+" for statistics and image processing via GPU", "Improved a mission-critical image manipulation API from 65% reliability to 99.9999% success rate", "Spearheaded implementation of an "+lookup("ECS").Link()+" cluster using an advanced "+lookup("topology").Link()+" algorithm (from a PhD thesis), achieving >100x performance gains over its original implementation on TB-scale datasets", "Built a "+lookup("Go").Link()+"-based compiler that transformed nested "+lookup("JSON").Link()+" instructions into machine-executable operations across clusters of "+lookup("EC2").Link()+" instances for for retrieving and manipulating geospatial agricultural data", "__Saved \\$18M of a \\$28M budget (64%)__ in 2024, while still increasing service stability and throughput", - "Ensured maximum service uptime via careful design and rollout of [CI/CD](cv/subjectMatter/CI-CD) pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via "+lookup("Terraform").Link(), + "Ensured maximum service uptime via careful design and rollout of "+lookup("CI-CD").Link()+" pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via "+lookup("Terraform").Link(), "Setup monitoring, dashboards, alerting, traces, and profiling ("+lookup("Datadog").Link()+"/"+lookup("Grafana").Link()+"/"+lookup("Cloudwatch").Link()+")", "Responsible for educating engineers on infrastructure, codebase, domain, and best practices", "Utilized primarily "+lookup("Go").Link()+", "+lookup("Terraform").Link()+", "+lookup("Bash").Link()+", and "+lookup("Docker").Link()+" on "+lookup("AWS").Link()+", but also used "+lookup("Scala").Link()+", "+lookup("Github Actions").Link()+", "+lookup("C").Link()+"/[C++](cv/language/Cpp) with "+lookup("CUDA").Link()+", "+lookup("DroneCI").Link()+", "+lookup("Python").Link()+", "+lookup("Javascript").Link()+", "+lookup("Typescript").Link()+", "+lookup("Kotlin").Link()+", and more", @@ -239,7 +239,7 @@ func initClientsAfterProjectsComplete() { // TODO: links in text for everything below this arrowNailClient = arrowNailClient.WithProjects(ArrowNailProject). WithInfo("Used "+lookup("Serverless").Link()+" services on "+lookup("AWS").Link()+" to support a "+lookup("React").Link()+" geospatial web app, Node API via "+lookup("lambda").Link()+" functions, and an "+lookup("Aurora").Link()+" database. Provided IT and Systems Administration Support", - "Set up [CI/CD](cv/subjectMatter/CI-CD) pipeline in "+lookup("Gitlab CI").Link()+" to make future deployments seamless") + "Set up "+lookup("CI-CD").Link()+" pipeline in "+lookup("Gitlab CI").Link()+" to make future deployments seamless") wildlifeRClient = wildlifeRClient.WithProjects(WildlifeRProject). WithInfo("Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps") } From 4e1a036e2a5903fb2ca19ab9a045da0d6ae05d64 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:27:43 -0400 Subject: [PATCH 27/45] disable read times --- quartzModified/.gitignore | 1 + quartzModified/quartz.layout.ts | 6 +- .../quartz/components/ContentMeta.tsx | 58 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 quartzModified/quartz/components/ContentMeta.tsx diff --git a/quartzModified/.gitignore b/quartzModified/.gitignore index b38445e..37d04d0 100644 --- a/quartzModified/.gitignore +++ b/quartzModified/.gitignore @@ -2,6 +2,7 @@ !quartz.config.ts !quartz.layout.ts !quartz/components/Footer.tsx +!quartz/components/ContentMeta.tsx !quartz/components/scripts/darkmode.inline.ts !quartz/components/styles/darkmode.scss !quartz/components/styles/footer.scss \ No newline at end of file diff --git a/quartzModified/quartz.layout.ts b/quartzModified/quartz.layout.ts index ed0cdf2..06c4ea1 100644 --- a/quartzModified/quartz.layout.ts +++ b/quartzModified/quartz.layout.ts @@ -50,7 +50,11 @@ export const defaultContentPageLayout: PageLayout = { // components for pages that display lists of pages (e.g. tags or folders) export const defaultListPageLayout: PageLayout = { - beforeBody: [Component.Breadcrumbs(), Component.ArticleTitle(), Component.ContentMeta()], + beforeBody: [ + Component.Breadcrumbs(), + Component.ArticleTitle(), + Component.ContentMeta(), + ], left: [ Component.PageTitle(), Component.MobileOnly(Component.Spacer()), diff --git a/quartzModified/quartz/components/ContentMeta.tsx b/quartzModified/quartz/components/ContentMeta.tsx new file mode 100644 index 0000000..7ff01b9 --- /dev/null +++ b/quartzModified/quartz/components/ContentMeta.tsx @@ -0,0 +1,58 @@ +import { Date, getDate } from "./Date" +import { QuartzComponentConstructor, QuartzComponentProps } from "./types" +import readingTime from "reading-time" +import { classNames } from "../util/lang" +import { i18n } from "../i18n" +import { JSX } from "preact" +import style from "./styles/contentMeta.scss" + +interface ContentMetaOptions { + /** + * Whether to display reading time + */ + showReadingTime: boolean + showComma: boolean +} + +const defaultOptions: ContentMetaOptions = { + showReadingTime: false, // Set to true to reenable reading time ContentMetadata + showComma: true, +} + +export default ((opts?: Partial) => { + // Merge options with defaults + const options: ContentMetaOptions = { ...defaultOptions, ...opts } + + function ContentMetadata({ cfg, fileData, displayClass }: QuartzComponentProps) { + const text = fileData.text + + if (text) { + const segments: (string | JSX.Element)[] = [] + + if (fileData.dates) { + segments.push() + } + + // Display reading time if enabled + if (options.showReadingTime) { + const { minutes, words: _words } = readingTime(text) + const displayedTime = i18n(cfg.locale).components.contentMeta.readingTime({ + minutes: Math.ceil(minutes), + }) + segments.push({displayedTime}) + } + + return ( +

+ {segments} +

+ ) + } else { + return null + } + } + + ContentMetadata.css = style + + return ContentMetadata +}) satisfies QuartzComponentConstructor From 587c6a6a338cec56905b8604d1968cdf7617c3af Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:30:39 -0400 Subject: [PATCH 28/45] remove publish dates from directory views --- quartzModified/.gitignore | 1 + quartzModified/quartz/components/PageList.tsx | 114 ++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 quartzModified/quartz/components/PageList.tsx diff --git a/quartzModified/.gitignore b/quartzModified/.gitignore index 37d04d0..b2ca155 100644 --- a/quartzModified/.gitignore +++ b/quartzModified/.gitignore @@ -3,6 +3,7 @@ !quartz.layout.ts !quartz/components/Footer.tsx !quartz/components/ContentMeta.tsx +!quartz/components/PageList.tsx !quartz/components/scripts/darkmode.inline.ts !quartz/components/styles/darkmode.scss !quartz/components/styles/footer.scss \ No newline at end of file diff --git a/quartzModified/quartz/components/PageList.tsx b/quartzModified/quartz/components/PageList.tsx new file mode 100644 index 0000000..de706bc --- /dev/null +++ b/quartzModified/quartz/components/PageList.tsx @@ -0,0 +1,114 @@ +import { FullSlug, isFolderPath, resolveRelative } from "../util/path" +import { QuartzPluginData } from "../plugins/vfile" +import { Date, getDate } from "./Date" +import { QuartzComponent, QuartzComponentProps } from "./types" +import { GlobalConfiguration } from "../cfg" + +export type SortFn = (f1: QuartzPluginData, f2: QuartzPluginData) => number + +export function byDateAndAlphabetical(cfg: GlobalConfiguration): SortFn { + return (f1, f2) => { + // Sort by date/alphabetical + if (f1.dates && f2.dates) { + // sort descending + return getDate(cfg, f2)!.getTime() - getDate(cfg, f1)!.getTime() + } else if (f1.dates && !f2.dates) { + // prioritize files with dates + return -1 + } else if (!f1.dates && f2.dates) { + return 1 + } + + // otherwise, sort lexographically by title + const f1Title = f1.frontmatter?.title.toLowerCase() ?? "" + const f2Title = f2.frontmatter?.title.toLowerCase() ?? "" + return f1Title.localeCompare(f2Title) + } +} + +export function byDateAndAlphabeticalFolderFirst(cfg: GlobalConfiguration): SortFn { + return (f1, f2) => { + // Sort folders first + const f1IsFolder = isFolderPath(f1.slug ?? "") + const f2IsFolder = isFolderPath(f2.slug ?? "") + if (f1IsFolder && !f2IsFolder) return -1 + if (!f1IsFolder && f2IsFolder) return 1 + + // If both are folders or both are files, sort by date/alphabetical + if (f1.dates && f2.dates) { + // sort descending + return getDate(cfg, f2)!.getTime() - getDate(cfg, f1)!.getTime() + } else if (f1.dates && !f2.dates) { + // prioritize files with dates + return -1 + } else if (!f1.dates && f2.dates) { + return 1 + } + + // otherwise, sort lexographically by title + const f1Title = f1.frontmatter?.title.toLowerCase() ?? "" + const f2Title = f2.frontmatter?.title.toLowerCase() ?? "" + return f1Title.localeCompare(f2Title) + } +} + +type Props = { + limit?: number + sort?: SortFn +} & QuartzComponentProps + +export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit, sort }: Props) => { + const sorter = sort ?? byDateAndAlphabeticalFolderFirst(cfg) + let list = allFiles.sort(sorter) + if (limit) { + list = list.slice(0, limit) + } + + return ( +
    + {list.map((page) => { + const title = page.frontmatter?.title + const tags = page.frontmatter?.tags ?? [] + + return ( +
  • +
    + {/*

    */} + {/* {page.dates && /* TODO: maybe disable dates here????*!/*/} + {/*

    */} + + +
    +
  • + ) + })} +
+ ) +} + +PageList.css = ` +.section h3 { + margin: 0; +} + +.section > .tags { + margin: 0; +} +` From 4394ba3a6caf064973a427734946e7c8aec42695 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:31:59 -0400 Subject: [PATCH 29/45] disable dates at the top of pages --- quartzModified/quartz/components/ContentMeta.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/quartzModified/quartz/components/ContentMeta.tsx b/quartzModified/quartz/components/ContentMeta.tsx index 7ff01b9..fa262c6 100644 --- a/quartzModified/quartz/components/ContentMeta.tsx +++ b/quartzModified/quartz/components/ContentMeta.tsx @@ -29,9 +29,10 @@ export default ((opts?: Partial) => { if (text) { const segments: (string | JSX.Element)[] = [] - if (fileData.dates) { - segments.push() - } + // Disable dates at the top of pages + // if (fileData.dates) { + // segments.push() + // } // Display reading time if enabled if (options.showReadingTime) { From 38fa7978c1916bc5bcef727e4afa2ceeeca508c3 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:32:27 -0400 Subject: [PATCH 30/45] Fix an import --- quartzModified/quartz/components/PageList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartzModified/quartz/components/PageList.tsx b/quartzModified/quartz/components/PageList.tsx index de706bc..ada32b4 100644 --- a/quartzModified/quartz/components/PageList.tsx +++ b/quartzModified/quartz/components/PageList.tsx @@ -1,6 +1,6 @@ import { FullSlug, isFolderPath, resolveRelative } from "../util/path" import { QuartzPluginData } from "../plugins/vfile" -import { Date, getDate } from "./Date" +import { getDate } from "./Date" import { QuartzComponent, QuartzComponentProps } from "./types" import { GlobalConfiguration } from "../cfg" From a7f3c0817b8a3d416e2a5fc2d20c822df1987bce Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:32:50 -0400 Subject: [PATCH 31/45] leave a comment on what was removed --- quartzModified/quartz/components/PageList.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/quartzModified/quartz/components/PageList.tsx b/quartzModified/quartz/components/PageList.tsx index ada32b4..2a0429d 100644 --- a/quartzModified/quartz/components/PageList.tsx +++ b/quartzModified/quartz/components/PageList.tsx @@ -1,5 +1,6 @@ import { FullSlug, isFolderPath, resolveRelative } from "../util/path" import { QuartzPluginData } from "../plugins/vfile" +//import { Date, getDate } from "./Date" import { getDate } from "./Date" import { QuartzComponent, QuartzComponentProps } from "./types" import { GlobalConfiguration } from "../cfg" From 71ce522a3c14226c78522afe22136967386ba37e Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:36:47 -0400 Subject: [PATCH 32/45] say file in date area to keep formatting stable --- quartzModified/quartz/components/PageList.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/quartzModified/quartz/components/PageList.tsx b/quartzModified/quartz/components/PageList.tsx index 2a0429d..819c8a5 100644 --- a/quartzModified/quartz/components/PageList.tsx +++ b/quartzModified/quartz/components/PageList.tsx @@ -74,9 +74,9 @@ export const PageList: QuartzComponent = ({ cfg, fileData, allFiles, limit, sort return (
  • - {/*

    */} - {/* {page.dates && /* TODO: maybe disable dates here????*!/*/} - {/*

    */} +

    + {"file"/*page.dates && TODO: maybe disable dates here????*/} +

    From 58caaab5843b336b9bf5e5f13325dac64f1bfcad Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:42:44 -0400 Subject: [PATCH 33/45] project tags --- generator/project.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/generator/project.go b/generator/project.go index 5c05515..5a80e8d 100644 --- a/generator/project.go +++ b/generator/project.go @@ -129,16 +129,29 @@ func (pg *Project) EntryType() string { func (pg *Project) Bytes() []byte { builder := strings.Builder{} - builder.WriteString(frontmatterFor(pg.Name, "Project")) + pgTags := []string{"Project"} + t := pg.TypeInfo.Type() + switch t { + case projectTypeSchool: + pgTags = append(pgTags, "Project/Coursework Related") + builder.WriteString("Coursework-related Project\n\n") + case projectTypeProfessional: + pgTags = append(pgTags, "Project/Professional") + case projectTypePersonal: + pgTags = append(pgTags, "Project/Personal") + default: + panic("unknown project type: " + string(t)) + } + builder.WriteString(frontmatterFor(pg.Name, pgTags...)) // TODO; do these tags need both Project and Project/*? // PERSONAL/Professional t := pg.TypeInfo.Type() switch t { case projectTypeSchool: - builder.WriteString("Coursework-related Project\n") + builder.WriteString("Coursework-related Project\n\n") case projectTypeProfessional: - builder.WriteString("Professional Project\n") + builder.WriteString("Professional Project\n\n") case projectTypePersonal: - builder.WriteString("Personal Project\n") + builder.WriteString("Personal Project\n\n") default: panic("unknown project type: " + string(t)) } From ecacb8bdb40e0d4e83c9a15cf54aa2722adc9c50 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:43:12 -0400 Subject: [PATCH 34/45] fix metadata import --- quartzModified/quartz/components/ContentMeta.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartzModified/quartz/components/ContentMeta.tsx b/quartzModified/quartz/components/ContentMeta.tsx index fa262c6..d013ba5 100644 --- a/quartzModified/quartz/components/ContentMeta.tsx +++ b/quartzModified/quartz/components/ContentMeta.tsx @@ -1,4 +1,4 @@ -import { Date, getDate } from "./Date" +//import { Date, getDate } from "./Date" import { QuartzComponentConstructor, QuartzComponentProps } from "./types" import readingTime from "reading-time" import { classNames } from "../util/lang" From 881718d6fb669a84d6d62771d0131e5f4246c1b2 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:45:59 -0400 Subject: [PATCH 35/45] More links in footer --- quartzModified/quartz.layout.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/quartzModified/quartz.layout.ts b/quartzModified/quartz.layout.ts index 06c4ea1..a6211a7 100644 --- a/quartzModified/quartz.layout.ts +++ b/quartzModified/quartz.layout.ts @@ -8,9 +8,13 @@ export const sharedPageComponents: SharedLayout = { afterBody: [], footer: Component.Footer({ links: { + "Home": "https://reece.appli.ng", + "CV": "https://reece.appli.ng/cv", + "Blog": "https://reece.appli.ng/Blog", + "My Links": "https://links.reece.appli.ng", GitHub: "https://github.com/reeceappling", "Linkedin": "https://linkedin.com/reeceappling", - "Other Links": "https://links.reece.appli.ng" + "Notes": "https://reece.appli.ng/Notes", }, }), } From 8ebaf6b923117800af4444b831f403f399003515 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:53:14 -0400 Subject: [PATCH 36/45] fix --- generator/main.go | 1 + generator/project.go | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/main.go b/generator/main.go index d4e5ea2..ee0230e 100644 --- a/generator/main.go +++ b/generator/main.go @@ -18,6 +18,7 @@ import ( // TODO: sort projects on projects page? (maybe chronological?) (alphabetical?) // TODO: backlinks only on the subject matter pages???? // TODO: PUT TAGS ALL OVER PROJECTS???!!!!!! +// TODO: Folder page should alphabetize contents on list func main() { for _, dir := range []string{"cv", "blog", "note"} { diff --git a/generator/project.go b/generator/project.go index 5a80e8d..10fb2e4 100644 --- a/generator/project.go +++ b/generator/project.go @@ -144,7 +144,6 @@ func (pg *Project) Bytes() []byte { } builder.WriteString(frontmatterFor(pg.Name, pgTags...)) // TODO; do these tags need both Project and Project/*? // PERSONAL/Professional - t := pg.TypeInfo.Type() switch t { case projectTypeSchool: builder.WriteString("Coursework-related Project\n\n") From e09b8e7f024b2fd66a1aa33c5b205155a7d9e00f Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:57:21 -0400 Subject: [PATCH 37/45] fix project tags --- generator/project.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/generator/project.go b/generator/project.go index 10fb2e4..b9ec752 100644 --- a/generator/project.go +++ b/generator/project.go @@ -133,12 +133,11 @@ func (pg *Project) Bytes() []byte { t := pg.TypeInfo.Type() switch t { case projectTypeSchool: - pgTags = append(pgTags, "Project/Coursework Related") - builder.WriteString("Coursework-related Project\n\n") + pgTags = append(pgTags, "Coursework-Related Project") case projectTypeProfessional: - pgTags = append(pgTags, "Project/Professional") + pgTags = append(pgTags, "Professional Project") case projectTypePersonal: - pgTags = append(pgTags, "Project/Personal") + pgTags = append(pgTags, "Personal Project") default: panic("unknown project type: " + string(t)) } @@ -154,8 +153,8 @@ func (pg *Project) Bytes() []byte { default: panic("unknown project type: " + string(t)) } - // Client/company/school + // Client/company/school if cli := pg.TypeInfo.getClient(); cli != nil { builder.WriteString(fmt.Sprintf("Client: %s\n", cli.Link())) if comp := cli.company; comp != nil { From 0d15850528f2ce757e11ed83a3e04362e789d212 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 18:59:27 -0400 Subject: [PATCH 38/45] fix formatting on client-company area of projects --- generator/project.go | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/generator/project.go b/generator/project.go index b9ec752..2c67f71 100644 --- a/generator/project.go +++ b/generator/project.go @@ -142,21 +142,10 @@ func (pg *Project) Bytes() []byte { panic("unknown project type: " + string(t)) } builder.WriteString(frontmatterFor(pg.Name, pgTags...)) // TODO; do these tags need both Project and Project/*? - // PERSONAL/Professional - switch t { - case projectTypeSchool: - builder.WriteString("Coursework-related Project\n\n") - case projectTypeProfessional: - builder.WriteString("Professional Project\n\n") - case projectTypePersonal: - builder.WriteString("Personal Project\n\n") - default: - panic("unknown project type: " + string(t)) - } // Client/company/school if cli := pg.TypeInfo.getClient(); cli != nil { - builder.WriteString(fmt.Sprintf("Client: %s\n", cli.Link())) + builder.WriteString(fmt.Sprintf("Client: %s\n\n", cli.Link())) if comp := cli.company; comp != nil { builder.WriteString(fmt.Sprintf("Company: %s\n", comp.Link())) } From 6d91fc0e693a2f6dce346f0444879b7fd04cb1f6 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 19:05:18 -0400 Subject: [PATCH 39/45] add projects to positions --- generator/company.go | 2 +- generator/position.go | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/generator/company.go b/generator/company.go index 5e2f7db..078d948 100644 --- a/generator/company.go +++ b/generator/company.go @@ -217,7 +217,7 @@ func (pg *CompanyPage) GetAllLowest() (outCaches map[string]*CachePage, outDbs m func initCompaniesAfterPositions() { _ = NewCompany("Source Allies", 5, 2022, nil, nil). - WithPositions(sai1, sai2, sai3) + WithPositions(sai1, sai2, sai3) // TODO: SAI not showing SAI as a client _ = NewCompany("Freelance", 1, 2012, utils.Pointer(6), utils.Pointer(2022)). // TODO: RENAME WithPositions(freelancePosition) _ = NewCompany("TEI", 1, 2019, utils.Pointer(3), utils.Pointer(2020)). // TODO: ENSURE DATES ARE RIGHT diff --git a/generator/position.go b/generator/position.go index ffe27e6..b75eadb 100644 --- a/generator/position.go +++ b/generator/position.go @@ -221,29 +221,30 @@ var ( sai1, sai2, sai3, freelancePosition, positionTEI, positionTAE, positionSeniorLifeguard, positionLifeguard *Position ) +// TODO; ensure all professional projects are added to positions func initPositionsAfterProjects() { // TODO: ANY MISC SKILLS positionLifeguard = NewPosition("Lifeguard", 1, 2013, utils.Pointer(6), utils.Pointer(2014)). // TODO: ensure dates are right - WithSubjectMatters(smFirstAid) + WithSubjectMatters(smFirstAid) positionSeniorLifeguard = NewPosition("Senior Lifeguard", 6, 2014, utils.Pointer(8), utils.Pointer(2016)). // TODO: ensure dates are right - WithSubjectMatters(smFirstAid) + WithSubjectMatters(smFirstAid) positionTAE = NewPosition("Civil Structural Engineer and Tower Climber", 5, 2017, utils.Pointer(3), utils.Pointer(2018)). // TODO: ensure dates are right - WithMiscSkills("Excel", "Climbing", "AutoDesk Inventor", "AutoCAD", "Autodesk Revit", "Drafting"). - WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics) + WithMiscSkills("Excel", "Climbing", "AutoDesk Inventor", "AutoCAD", "Autodesk Revit", "Drafting"). + WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics) positionTEI = NewPosition("Cell Tower Inspector and Tower Climber", 1, 2019, utils.Pointer(3), utils.Pointer(2020)). // TODO: ensure dates are right - WithMiscSkills("Climbing", "Drafting"). - WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics) + WithMiscSkills("Climbing", "Drafting"). + WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics) freelancePosition = NewPosition("Software Engineer", 1, 2012, utils.Pointer(5), utils.Pointer(2022)). WithSubjectMatters(smFullStack). - WithProjects(WellAwareProject, CharityProject, CritColaProject, MastersDataAnalysisProject) + WithProjects(WellAwareProject, CharityProject, CritColaProject, MastersDataAnalysisProject, WildlifeRProject, ArrowNailProject) sai1 = NewPosition("Software Engineer", 5, 2022, utils.Pointer(6), utils.Pointer(2023)). WithSubjectMatters(smFullStack). - WithProjects(tileGenProject, renderProject, statsProject, explorerProject, wqdbProject, supportProject, scudsProject, ufoProject, goweProject, simpsonUnivProject) // TODO: MOVE PROJECTS AROUND + WithProjects(rasterRenderProject, explorerProject, supportProject, scudsProject, simpsonUnivProject, jamfProject, smallImprovementsProject, internalResumeGeneratorProject) // TODO: MOVE PROJECTS AROUND sai2 = NewPosition("Senior Software Engineer", 6, 2023, utils.Pointer(2), utils.Pointer(2025)). WithSubjectMatters(smBackend). - WithProjects(polygonBuilderProject, GhaRunnersProject) + WithProjects(tileGenProject, polygonBuilderProject, GhaRunnersProject, statsProject, ufoProject, renderProject) sai3 = NewPosition("Senior Software Engineer and Tech Lead", 2, 2025, nil, nil). WithSubjectMatters(smBackend). - WithProjects(ogreProject, billingProject) + WithProjects(ogreProject, billingProject, goweProject, wqdbProject) } From 62888b91eb5fcca95b9b0c43ba937ad7a413b504 Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sat, 14 Mar 2026 19:06:09 -0400 Subject: [PATCH 40/45] TODOS on positions --- generator/position.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/generator/position.go b/generator/position.go index b75eadb..b9b6fa6 100644 --- a/generator/position.go +++ b/generator/position.go @@ -225,15 +225,19 @@ var ( func initPositionsAfterProjects() { // TODO: ANY MISC SKILLS positionLifeguard = NewPosition("Lifeguard", 1, 2013, utils.Pointer(6), utils.Pointer(2014)). // TODO: ensure dates are right - WithSubjectMatters(smFirstAid) + WithSubjectMatters(smFirstAid). + WithProjects() // TODO: THIS! positionSeniorLifeguard = NewPosition("Senior Lifeguard", 6, 2014, utils.Pointer(8), utils.Pointer(2016)). // TODO: ensure dates are right - WithSubjectMatters(smFirstAid) + WithSubjectMatters(smFirstAid). + WithProjects() // TODO: THIS! positionTAE = NewPosition("Civil Structural Engineer and Tower Climber", 5, 2017, utils.Pointer(3), utils.Pointer(2018)). // TODO: ensure dates are right WithMiscSkills("Excel", "Climbing", "AutoDesk Inventor", "AutoCAD", "Autodesk Revit", "Drafting"). - WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics) + WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics). + WithProjects() // TODO: THIS! positionTEI = NewPosition("Cell Tower Inspector and Tower Climber", 1, 2019, utils.Pointer(3), utils.Pointer(2020)). // TODO: ensure dates are right WithMiscSkills("Climbing", "Drafting"). - WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics) + WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics). + WithProjects() // TODO: THIS! freelancePosition = NewPosition("Software Engineer", 1, 2012, utils.Pointer(5), utils.Pointer(2022)). WithSubjectMatters(smFullStack). WithProjects(WellAwareProject, CharityProject, CritColaProject, MastersDataAnalysisProject, WildlifeRProject, ArrowNailProject) From e8577452e840a763fee8e896b8109bed5e5f31e1 Mon Sep 17 00:00:00 2001 From: Reece Appling <19520870+reeceappling@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:03:47 -0400 Subject: [PATCH 41/45] Add resume file --- Resume_Reece_Appling_2026_03_14.pdf | Bin 0 -> 126650 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Resume_Reece_Appling_2026_03_14.pdf diff --git a/Resume_Reece_Appling_2026_03_14.pdf b/Resume_Reece_Appling_2026_03_14.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b9829102ac1f32ed08f6a8ea1c60da7f93ccc557 GIT binary patch literal 126650 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ⅇ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

    +$6` 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 uD)#m+4{ Date: Sun, 15 Mar 2026 19:05:00 -0400 Subject: [PATCH 42/45] Rename resume file and move to static --- .../Resume_Reece_Appling.pdf | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename Resume_Reece_Appling_2026_03_14.pdf => static/Resume_Reece_Appling.pdf (100%) diff --git a/Resume_Reece_Appling_2026_03_14.pdf b/static/Resume_Reece_Appling.pdf similarity index 100% rename from Resume_Reece_Appling_2026_03_14.pdf rename to static/Resume_Reece_Appling.pdf From 58c18957b591d3f859751aa57af0be8365c91575 Mon Sep 17 00:00:00 2001 From: Reece Appling <19520870+reeceappling@users.noreply.github.com> Date: Sun, 15 Mar 2026 19:09:44 -0400 Subject: [PATCH 43/45] Update resume link to point to new PDF location --- generator/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/main.go b/generator/main.go index ee0230e..3088f27 100644 --- a/generator/main.go +++ b/generator/main.go @@ -105,7 +105,7 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? // TODO: HEADER AREA FOR LINKS TO CV, RESUME, BLOG, NOTES? b.WriteString("Welcome to my CV! It is a living document that is updated occasionally.\n\n") // Why does this need 2 newlines? - b.WriteString("Looking for a resume instead? [Download it here](Resume.pdf)ENSURE WORKING\n\n") // TODO: ENSURE OK + b.WriteString("Looking for a resume instead? [Download it here](static/Resume_Reece_Appling.pdf)ENSURE WORKING\n\n") // TODO: ENSURE OK b.WriteString("Feel free to check out the [source code](https://github.com/reeceappling/CV) for this website\n") b.WriteString("# About\n") From 49505c63ce730b57b1e3df9a4f5422000509bc1f Mon Sep 17 00:00:00 2001 From: Reece Appling <19520870+reeceappling@users.noreply.github.com> Date: Sun, 15 Mar 2026 20:58:53 -0400 Subject: [PATCH 44/45] Add tags to several technology entries Added tags to OpenAI API spec, REST API, OpenApi, and Swagger technologies. --- generator/technology.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/generator/technology.go b/generator/technology.go index 015fd09..0525f61 100644 --- a/generator/technology.go +++ b/generator/technology.go @@ -60,10 +60,10 @@ func setupTechSubjectMatters() { NewTechnology("LLM", smAI) NewTechnology("AI", smAI) NewTechnology("AI Agents", smAI) - NewTechnology("OpenAI API spec", smAI, smBackend) + NewTechnology("OpenAI API spec", smAI, smBackend).WithTags("API") NewTechnology("CUDA", smGPU, smGraphics) NewTechnology("CGo", smBackend) - NewTechnology("REST API", smBackend, smApi) // TODO: use this everywhere necessary... + NewTechnology("REST API", smBackend, smApi).WithTags("API") // TODO: use this everywhere necessary... NewTechnology("Avro", smBackend).WithTags("DataFormat") NewTechnology("Parquet", smBackend).WithTags("DataFormat") NewTechnology("JSON", smFullStack).WithTags("DataFormat") @@ -103,8 +103,8 @@ func setupTechSubjectMatters() { NewTechnology("Arduino", smEmbeddedSystems, smElectronics, smRobotics) NewTechnology("PWM", smEmbeddedSystems, smElectronics, smRobotics) NewTechnology("G and M codes", smElectronics, smRobotics) - NewTechnology("OpenApi", smDocumentation) // TODO: USE - NewTechnology("Swagger", smDocumentation) // TODO: USE + NewTechnology("OpenApi", smDocumentation).WithTags("API") // TODO: USE + NewTechnology("Swagger", smDocumentation).WithTags("API") // TODO: USE NewTechnology("Spring", smBackend) } From 494b3100f1d07de88bee061fc668c32172f0bb2b Mon Sep 17 00:00:00 2001 From: reeceappling Date: Sun, 22 Mar 2026 14:07:49 -0400 Subject: [PATCH 45/45] lost of changes. Probably wont deploy properly --- .github/workflows/build.yml | 39 +++++- generate.sh | 21 ++- generator/aliases.go | 36 +++++ generator/blogPost.go | 3 + generator/cache.go | 18 ++- generator/client.go | 93 ++++++++----- generator/common.go | 67 +++++++--- generator/company.go | 23 ++-- generator/db.go | 10 +- generator/education.go | 32 +++-- generator/interests.go | 11 +- generator/language.go | 18 ++- generator/main.go | 73 +++++----- generator/miscSkills.go | 10 +- generator/note.go | 24 +++- generator/platform.go | 30 +++-- generator/position.go | 75 +++++++---- generator/project.go | 222 ++++++++++++++++++------------- generator/provider.go | 23 ++-- generator/service.go | 81 +++++++++-- generator/subjectMatter.go | 19 ++- generator/tags.go | 53 -------- generator/tags/tags.go | 72 ++++++++++ generator/technology.go | 21 ++- scripts/changeTimestamps/main.go | 61 +++++++++ 25 files changed, 776 insertions(+), 359 deletions(-) create mode 100644 generator/aliases.go delete mode 100644 generator/tags.go create mode 100644 generator/tags/tags.go create mode 100644 scripts/changeTimestamps/main.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 760ba38..687c86c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,6 @@ name: Build and Deploy run-name: ${{ github.ref_name }} - "${{ github.event.head_commit.message }}" on: - # TODO: other environments? pull_request: paths: - 'generator/**' @@ -83,6 +82,7 @@ jobs: with: sparse-checkout: | quartzModified + scripts - name: Clone Quartz 4 run: git clone https://github.com/jackyzha0/quartz.git - uses: actions/setup-node@v4 @@ -126,7 +126,37 @@ jobs: compression-level: 6 overwrite: true include-hidden-files: false - +# clean-bucket: +# needs: [build-quartz, generate-markdown] +# if: | +# contains(github.event.pull_request.labels.*.name, 'clean bucket') +# && always() && !cancelled() +# && (needs.generate-markdown.result == 'success') +# && needs.build-quartz.result == 'success' +# runs-on: ubuntu-latest +# permissions: +# id-token: write +# contents: read +# actions: write +# steps: +# # - uses: actions/checkout@v4 +# # with: +# # sparse-checkout: | +# # terraform +# # static +# # - name: Setup Terraform +# # uses: hashicorp/setup-terraform@v3 +# # with: +# # terraform_version: "1.14.6" +# - name: Configure AWS Credentials +# uses: aws-actions/configure-aws-credentials@v6.0.0 +# with: +# role-to-assume: ${{ secrets.AWS_RUNNER_ROLE }} +# aws-region: ${{ env.AWS_REGION }} +# role-session-name: reeceSubdomainBucketCleanup +# # TODO: ensure this does not mess up the terraform! +# - name: clean out s3 bucket # Do not do this often because it is more expensive +# run: aws s3 rm s3://${{ env.TF_VAR_subdomain }}.${{ env.TF_VAR_domain }} --recursive deploy: needs: [build-quartz, generate-markdown] if: | @@ -134,6 +164,7 @@ jobs: && always() && !cancelled() && (needs.generate-markdown.result == 'success') && needs.build-quartz.result == 'success' +# && (needs.clean-bucket.result == 'success' || needs.clean-bucket.result == 'skipped') runs-on: ubuntu-latest permissions: id-token: write @@ -168,13 +199,15 @@ jobs: mv -f static/* terraform/public/static/ fi fi - # TODO: ONLY REPLACE FILES THAT HAVE CHANGED! + - name: Change file timestamps + run: go run scripts/changeTimestamps/... -path=quartz/public -date=03-22-2026 # TODO: ensure works correctly - name: initialize terraform run: terraform init -backend-config='bucket=${{ vars.TERRAFORM_STATE_BUCKET }}' -backend-config='region=${{ env.AWS_REGION }}' working-directory: terraform - name: plan terraform run: terraform plan -var-file='deploy.tfvars' -var 'cloudflare_api_token=${{secrets.CLOUDFLARE_API_TOKEN}}' -var 'cloudflare_zone_id=${{secrets.CLOUDFLARE_ZONE_ID}}' -var 'aws_region=${{env.AWS_REGION}}' -out=plan.tf working-directory: terraform + # TODO: ensure changed fileNames actually delete the old files! (should but unsure with terraform resources on s3 objects specifically) - name: apply terraform run: terraform apply -auto-approve plan.tf working-directory: terraform diff --git a/generate.sh b/generate.sh index 701b2f8..02290b6 100755 --- a/generate.sh +++ b/generate.sh @@ -1,2 +1,19 @@ -chmod -R 777 ./quartz -go run ./generator # TODO: delete file if unused \ No newline at end of file +# Execute commands in a subshell so that the final directory is the initial +( + current_dir_name=$(basename "$PWD") + case "$current_dir_name" in + "CV") + echo "working from CV directory" + ;; + "scripts") + echo "working from scripts directory" + cd ../ || exit 1 + ;; + *) + echo "unhandled working directory: $current_dir_name" + exit 1 + ;; + esac + chmod -R 777 ./quartz + go run ./generator +) diff --git a/generator/aliases.go b/generator/aliases.go new file mode 100644 index 0000000..5f048cd --- /dev/null +++ b/generator/aliases.go @@ -0,0 +1,36 @@ +package main + +import "strings" + +var aliases = map[string]linkableAlias{} // Map of link to link + +func initAliases() { + newLinkableAlias("IaC", smIAC) // TODO: ADD TAGS? + newLinkableAlias("CiCd", smCiCd) + newLinkableAlias("CI/CD", smCiCd) // TODO: NOT WORKING + newLinkableAlias("k8s", techs["Kubernetes"]) // TODO: ok? + newLinkableAlias("KMS", cloudServices["Key Management Service"]) // TODO: ok? + newLinkableAlias("ASM", langs["Assembly"]) // TODO: ok? + newLinkableAlias("WebAssembly", langs["WASM"]) // TODO: ok? +} +func newLinkableAlias(name string, linkTo Linkable) { + out := linkableAlias{name: name, finalLink: linkTo} + aliases[name] = out + addLinkable(strings.ToLower(name), out) +} + +type linkableAlias struct { + name string + finalLink Linkable +} + +func (pg linkableAlias) Title() string { + return pg.name +} + +func (pg linkableAlias) Dst() string { // TODO: ptr? + return pg.finalLink.Dst() +} +func (pg linkableAlias) EntryType() string { + return pg.name +} diff --git a/generator/blogPost.go b/generator/blogPost.go index fb8d4ad..111195e 100644 --- a/generator/blogPost.go +++ b/generator/blogPost.go @@ -54,6 +54,9 @@ func (post BlogPost) Bytes() []byte { func (post BlogPost) Link() string { return fmt.Sprintf(`[%s](blog/%s)`, post.Title, withoutSpaces(post.Title)) } +func (pg BlogPost) Dst() string { + return dstFor("blog", withoutSpaces(pg.Title)) +} const blogPostDir = "blogPosts/" diff --git a/generator/cache.go b/generator/cache.go index defdb76..da38d7b 100644 --- a/generator/cache.go +++ b/generator/cache.go @@ -9,11 +9,25 @@ type CachePage struct { *tracked } -func (pg *CachePage) Link() string { +func Link(linkable Linkable) string { + return linkFor(linkable.Title(), linkable.Dst()) +} + +func (pg *CachePage) Dst() string { if pg == nil { return "NO_LINK" } - return linkFor(pg.Name, "cv", "cache", withoutSpaces(pg.Name)) + return dstFor("cv", "cache", withoutSpaces(pg.Name)) +} + +func (pg *CachePage) Title() string { + if pg == nil { + return "NO_TITLE" + } + return pg.Name +} +func dstFor(elems ...string) string { + return strings.Join(elems, "/") } func (pg *CachePage) EntryType() string { return "Cache" diff --git a/generator/client.go b/generator/client.go index f8cda5a..10b5085 100644 --- a/generator/client.go +++ b/generator/client.go @@ -1,6 +1,7 @@ package main import ( + "appli.ng/cv/generator/tags" "appli.ng/cv/generator/utils" "fmt" "maps" @@ -13,12 +14,25 @@ var clientsOrder = []string{} type Client struct { Name string + Description string // TODO: ADD THIS? Info []string // Information points on a client. // TODO: ADD INFO TO ALL CLIENTS AND DISPLAY Projects []*Project projectsSet utils.Set[string] //Languages []string // Calculated later // Parent company *CompanyPage + Tags tags.Field +} + +func (pg *Client) Title() string { + if pg == nil { + return "NO_NAME" + } + return pg.Name +} +func (pg *Client) WithTags(tags ...tags.Tag) *Client { + pg.Tags = append(pg.Tags, tags...) + return pg } func (pg *Client) NameValue() string { @@ -29,11 +43,13 @@ func (pg *Client) ProjectTypeInfo() projectTypeInfo { return professionalProjectTypeInfo{client: pg} } -func (pg *Client) Link() string { +const noLinkText = "NO_LINK" + +func (pg *Client) Dst() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "client", withoutSpaces(pg.Name)) + return dstFor("cv", "client", withoutSpaces(pg.Name)) } func (pg *Client) EntryType() string { return "Client" @@ -67,14 +83,13 @@ func (pg *Client) Bytes() []byte { if pg.Projects != nil && len(pg.Projects) > 0 { builder.WriteString("# Projects\n") for _, proj := range pg.Projects { - builder.WriteString(fmt.Sprintf("- %s\n", proj.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(proj))) } } ls := languagesFor(pg.Projects) if len(ls) > 0 { builder.WriteString("# Languages\n") builder.WriteString("Language | Usage Frequency[[" + withoutSpaces(pg.Name) + "#^usageFrequencies\\|*]]\n") - // TODO: remove frequency??? builder.WriteString(":-- | :--\n") freqs := make(map[Frequency][]string, 6) for name, freq := range ls { @@ -86,7 +101,7 @@ func (pg *Client) Bytes() []byte { } for f := 5; f >= 0; f-- { for _, name := range freqs[Frequency(f)] { - builder.WriteString(fmt.Sprintf("%s | %s\n", langs[name].Link(), Frequency(f).String())) + builder.WriteString(fmt.Sprintf("%s | %s\n", Link(langs[name]), Frequency(f).String())) } } } @@ -125,7 +140,7 @@ func (c *Client) WithProjects(projs ...*Project) *Client { techs[l].AddClient(c) } for l := range maps.Keys(proj.CloudProviders) { - providers[l].AddClient(c) // TODO: cloud services? + providers[l].AddClient(c) } for l := range maps.Keys(proj.Platforms) { platforms[l].AddClient(c) @@ -193,53 +208,61 @@ func initClientsAfterProjectsComplete() { WithProjects(polygonBuilderProject, ogreProject, renderProject, statsProject, billingProject, explorerProject, wqdbProject, supportProject, scudsProject, ufoProject, goweProject, tileGenProject, GhaRunnersProject).WithInfo( "Most senior consulting engineer on a high-performance, global-scale, team of 4-8 at John Deere’s Intelligent Solutions Group; responsible for architecture, implementation, testing, optimization, and support of a complex set of diverse cloud services utilizing geospatiotemporal agribusiness data", "Architected, implemented, and maintained cloud infrastructure and services for ingest, distributed processing, storage, manipulation, and retrieval of data for, datastores totalling over 50PB", - "Created multiple "+lookup("ECS").Link()+" clusters for use in, and consuming data from, John Deere "+lookup("AI").Link()+" platforms", - "Designed "+lookup("CUDA").Link()+" "+lookup("C").Link()+"/[C++](cv/language/Cpp) Kernels used through "+lookup("CGo").Link()+" for statistics and image processing via GPU", + "Created multiple "+Link(lookup("ECS"))+" clusters for use in, and consuming data from, John Deere "+Link(lookup("AI"))+" platforms", + "Designed "+Link(lookup("CUDA"))+" "+Link(lookup("C"))+"/[C++](cv/language/Cpp) Kernels used through "+Link(lookup("CGo"))+" for statistics and image processing via GPU", "Improved a mission-critical image manipulation API from 65% reliability to 99.9999% success rate", - "Spearheaded implementation of an "+lookup("ECS").Link()+" cluster using an advanced "+lookup("topology").Link()+" algorithm (from a PhD thesis), achieving >100x performance gains over its original implementation on TB-scale datasets", - "Built a "+lookup("Go").Link()+"-based compiler that transformed nested "+lookup("JSON").Link()+" instructions into machine-executable operations across clusters of "+lookup("EC2").Link()+" instances for for retrieving and manipulating geospatial agricultural data", + "Spearheaded implementation of an "+Link(lookup("ECS"))+" cluster using an advanced "+Link(lookup("topology"))+" algorithm (from a PhD thesis), achieving >100x performance gains over its original implementation on TB-scale datasets", + "Built a "+Link(lookup("Go"))+"-based compiler that transformed nested "+Link(lookup("JSON"))+" instructions into machine-executable operations across clusters of "+Link(lookup("EC2"))+" instances for for retrieving and manipulating geospatial agricultural data", "__Saved \\$18M of a \\$28M budget (64%)__ in 2024, while still increasing service stability and throughput", - "Ensured maximum service uptime via careful design and rollout of "+lookup("CI-CD").Link()+" pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via "+lookup("Terraform").Link(), - "Setup monitoring, dashboards, alerting, traces, and profiling ("+lookup("Datadog").Link()+"/"+lookup("Grafana").Link()+"/"+lookup("Cloudwatch").Link()+")", + "Ensured maximum service uptime via careful design and rollout of "+Link(lookup("CI-CD"))+" pipelines operated via self-hosted GitHub Actions runners, in conjunction with Infrastructure as Code via "+Link(lookup("Terraform")), + "Setup monitoring, dashboards, alerting, traces, and profiling ("+Link(lookup("Datadog"))+"/"+Link(lookup("Grafana"))+"/"+Link(lookup("Cloudwatch"))+")", "Responsible for educating engineers on infrastructure, codebase, domain, and best practices", - "Utilized primarily "+lookup("Go").Link()+", "+lookup("Terraform").Link()+", "+lookup("Bash").Link()+", and "+lookup("Docker").Link()+" on "+lookup("AWS").Link()+", but also used "+lookup("Scala").Link()+", "+lookup("Github Actions").Link()+", "+lookup("C").Link()+"/[C++](cv/language/Cpp) with "+lookup("CUDA").Link()+", "+lookup("DroneCI").Link()+", "+lookup("Python").Link()+", "+lookup("Javascript").Link()+", "+lookup("Typescript").Link()+", "+lookup("Kotlin").Link()+", and more", - "Platforms utilized: AWS (>30 separate services), "+lookup("Datadog").Link()+", "+lookup("LogCentral").Link()+", "+lookup("Rally").Link()+", "+lookup("Azure DevOps").Link()+", "+lookup("Github").Link()+", "+lookup("DroneCI").Link()+", "+lookup("Grafana").Link()+", "+lookup("Prometheus").Link()+", "+lookup("Confluence").Link()+", and more") + "Utilized primarily "+Link(lookup("Go"))+", "+Link(lookup("Terraform"))+", "+Link(lookup("Bash"))+", and "+Link(lookup("Docker"))+" on "+Link(lookup("AWS"))+", but also used "+Link(lookup("Scala"))+", "+Link(lookup("Github Actions"))+", "+Link(lookup("C"))+"/[C++](cv/language/Cpp) with "+Link(lookup("CUDA"))+", "+Link(lookup("DroneCI"))+", "+Link(lookup("Python"))+", "+Link(lookup("Javascript"))+", "+Link(lookup("Typescript"))+", "+Link(lookup("Kotlin"))+", and more", + "Platforms utilized: AWS (>30 separate services), "+Link(lookup("Datadog"))+", "+Link(lookup("LogCentral"))+", "+Link(lookup("Rally"))+", "+Link(lookup("Azure DevOps"))+", "+Link(lookup("Github"))+", "+Link(lookup("DroneCI"))+", "+Link(lookup("Grafana"))+", "+Link(lookup("Prometheus"))+", "+Link(lookup("Confluence"))+", and more"). + WithTags(tags.AgTech) sourceAlliesClient = sourceAlliesClient. WithProjects(jamfProject, smallImprovementsProject, internalResumeGeneratorProject). // TODO: USE OTHERS WithInfo( "Source Allies internal projects", - "Upgraded company internal payment gateway to a newer version of "+lookup("Java").Link()+" "+lookup("Spring").Link(), - "Secured all company machines via "+lookup("JAMF").Link()+" to ensure the protection of company and client data", - "Designed and created a "+lookup("Slack").Link()+" bot integration with "+lookup("Small Improvements").Link()+" to automate monthly announcements, and employee creation and completion of personal and professional goals", - "Redesigned "+lookup("Jira").Link()+" workflows streamlining the hiring process and onboarding systems for remote coworkers", - ) + "Upgraded company internal payment gateway to a newer version of "+Link(lookup("Java"))+" "+Link(lookup("Spring")), + "Secured all company machines via "+Link(lookup("JAMF"))+" to ensure the protection of company and client data", + "Designed and created a "+Link(lookup("Slack"))+" bot integration with "+Link(lookup("Small Improvements"))+" to automate monthly announcements, and employee creation and completion of personal and professional goals", + "Redesigned "+Link(lookup("Jira"))+" workflows streamlining the hiring process and onboarding systems for remote coworkers", + ). + WithTags(tags.Consulting) simpsonUniversityClient = simpsonUniversityClient. WithProjects(simpsonUnivProject). - WithInfo("Upgraded the University’s payment and donation gateway. Remediated resulting bugs") - // TODO: SIMPSON COLLEGE // TODO: USE! + WithInfo("Upgraded the University’s payment and donation gateway. Remediated resulting bugs"). + WithTags(tags.Education) critColaClient = critColaClient. WithProjects(CritColaProject). WithInfo( - // TODO; link to discord - "Consulted on hosting game servers and "+lookup("Discord").Link()+" bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing "+lookup("Gitlab CI").Link()+", "+lookup("Terraform").Link()+", "+lookup("Cloudflare").Link()+", and "+lookup("AWS").Link()+" ("+lookup("EC2").Link()+", "+lookup("EBS").Link()+", "+lookup("IAM").Link()+")", + "Consulted on hosting game servers and "+Link(lookup("Discord"))+" bots for a large online community such that they could be deployed or destroyed, on short notice with persistent game data utilizing "+Link(lookup("Gitlab CI"))+", "+Link(lookup("Terraform"))+", "+Link(lookup("Cloudflare"))+", and "+Link(lookup("AWS"))+" ("+Link(lookup("EC2"))+", "+Link(lookup("EBS"))+", "+Link(lookup("IAM"))+")", "Created a final product with a spin-up time of approximately 3 minutes", - ) + ).WithTags(tags.Entertainment) wellAwareClient = wellAwareClient.WithProjects(WellAwareProject). - WithInfo("Designed, created, and hosted a website for Well Aware NC, a University of North Carolina Chapel Hill affiliated nonprofit focused on the testing of well water contaminants within North Carolina") + WithInfo("Designed, created, and hosted a website for Well Aware NC, a University of North Carolina Chapel Hill affiliated nonprofit focused on the testing of well water contaminants within North Carolina"). + WithTags(tags.PublicHealth, tags.Education) clarkClient = clarkClient.WithProjects(MastersDataAnalysisProject). - WithInfo("Created programs for a student doing research for his Masters Degree in Public Health. Provided data on E.Coli samples from different waterways, the programs checked the statistical validity on different E.Coli indicating kits") + WithInfo("Created programs for a student doing research for his Masters Degree in Public Health. Provided data on E.Coli samples from different waterways, the programs checked the statistical validity on different E.Coli indicating kits"). + WithTags(tags.PublicHealth, tags.Education) charityClient = charityClient.WithProjects(CharityProject). - WithInfo("Designed, created, and hosted a website for an undisclosed local charity") + WithInfo("Designed, created, and hosted a website for an undisclosed local charity"). + WithTags(tags.Charity) teiClient = teiClient.WithProjects(teiProjects). - WithInfo("Internal company work for TEI") // TODO: WEBSITE? // TODO: add projects (like NM, TX, IA, NC?) + WithInfo("Internal company work for TEI"). // TODO: WEBSITE? // TODO: add projects (like NM, TX, IA, NC?) + WithTags(tags.Telecom) taeClient = taeClient.WithProjects(taePhotoImporter). - WithInfo("Internal company work for Talley Associates of Engineering") + WithInfo("Internal company work for Talley Associates of Engineering"). + WithTags(tags.Telecom) mafcClient = mafcClient.WithProjects(mafcProjects). - WithInfo("Lifeguarding work, both at the indoor and outdoor pools of the fitness center") // TODO: Indoor and outdoor pool? - // TODO: links in text for everything below this + WithInfo("Lifeguarding work, both at the indoor and outdoor pools of the fitness center"). // TODO: Indoor and outdoor pool? + WithTags(tags.FirstAid) arrowNailClient = arrowNailClient.WithProjects(ArrowNailProject). - WithInfo("Used "+lookup("Serverless").Link()+" services on "+lookup("AWS").Link()+" to support a "+lookup("React").Link()+" geospatial web app, Node API via "+lookup("lambda").Link()+" functions, and an "+lookup("Aurora").Link()+" database. Provided IT and Systems Administration Support", - "Set up "+lookup("CI-CD").Link()+" pipeline in "+lookup("Gitlab CI").Link()+" to make future deployments seamless") + WithInfo("Used "+Link(lookup("Serverless"))+" services on "+Link(lookup("AWS"))+" to support a "+Link(lookup("React"))+" geospatial web app, Node API via "+Link(lookup("lambda"))+" functions, and an "+Link(lookup("Aurora"))+" database. Provided IT and Systems Administration Support", + "Set up "+Link(lookup("CI-CD"))+" pipeline in "+Link(lookup("Gitlab CI"))+" to make future deployments seamless"). + WithTags(tags.Construction) wildlifeRClient = wildlifeRClient.WithProjects(WildlifeRProject). - WithInfo("Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps") + WithInfo("Utilized spatiotemporal data for wildlife in a specified area over a specified date range in order to produce population density maps"). + WithTags(tags.Ecology) } diff --git a/generator/common.go b/generator/common.go index f67313e..9e6ff36 100644 --- a/generator/common.go +++ b/generator/common.go @@ -75,32 +75,32 @@ func bytesForAll(item showAll, showFrequencies bool) string { } for f := 5; f >= 0; f-- { for _, name := range freqs[Frequency(f)] { - b.WriteString(fmt.Sprintf("%s | %s\n", langs[name].Link(), Frequency(f).String())) + b.WriteString(fmt.Sprintf("%s | %s\n", Link(langs[name]), Frequency(f).String())) } } } if len(tempC) > 0 { b.WriteString("# Caches\n") for name, _ := range tempC { - b.WriteString(fmt.Sprintf("%s\n", caches[name].Link())) + b.WriteString(fmt.Sprintf("%s\n", Link(caches[name]))) } } if len(tempD) > 0 { b.WriteString("# Databases\n") for name, _ := range tempD { - b.WriteString(fmt.Sprintf("%s\n", dbs[name].Link())) + b.WriteString(fmt.Sprintf("%s\n", Link(dbs[name]))) } } if len(tempP) > 0 { b.WriteString("# Platforms\n") for name, _ := range tempP { - b.WriteString(fmt.Sprintf("%s\n", platforms[name].Link())) + b.WriteString(fmt.Sprintf("%s\n", Link(platforms[name]))) } } if len(tempT) > 0 { b.WriteString("# Technologies\n") for name, _ := range tempT { - b.WriteString(fmt.Sprintf("%s\n", techs[name].Link())) + b.WriteString(fmt.Sprintf("%s\n", Link(techs[name]))) } } if len(tempS) > 0 { @@ -108,9 +108,9 @@ func bytesForAll(item showAll, showFrequencies bool) string { for name, svcs := range tempS { tempSvcs := make([]string, len(svcs)) for i, svc := range slices.Collect(maps.Keys(svcs)) { - tempSvcs[i] = fmt.Sprintf("%s\n", cloudServices[svc].Link()) + tempSvcs[i] = fmt.Sprintf("%s\n", Link(cloudServices[svc])) } - b.WriteString(fmt.Sprintf("%s: %s\n", providers[name].Link(), strings.Join(tempSvcs, ", "))) + b.WriteString(fmt.Sprintf("%s: %s\n", Link(providers[name]), strings.Join(tempSvcs, ", "))) } } if showFrequencies { @@ -148,7 +148,7 @@ func newTracked() *tracked { Tags: utils.Set[string]{}, } } -func (pg *tracked) Bytes(title string, tags ...string) []byte { +func (pg *tracked) Bytes(title string, sm *SubjectMatter, tags ...string) []byte { builder := strings.Builder{} if title != "" { builder.WriteString(frontmatterFor(title, tags...)) @@ -157,19 +157,19 @@ func (pg *tracked) Bytes(title string, tags ...string) []byte { if pg.Companies != nil && len(pg.Companies) > 0 { builder.WriteString("# Companies\n") for _, com := range pg.Companies { - builder.WriteString(fmt.Sprintf("- %s\n", com.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(com))) } } if pg.Positions != nil && len(pg.Positions) > 0 { builder.WriteString("# Positions\n") for _, pos := range pg.Positions { - builder.WriteString(fmt.Sprintf("- %s\n", pos.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(pos))) } } if pg.Clients != nil && len(pg.Clients) > 0 { builder.WriteString("# Clients\n") for _, cli := range pg.Clients { - builder.WriteString(fmt.Sprintf("- %s\n", cli.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(cli))) } } if pg.Projects != nil && len(pg.Projects) > 0 { @@ -177,9 +177,13 @@ func (pg *tracked) Bytes(title string, tags ...string) []byte { //builder.WriteString("Project | Usage Frequency | Project Type\n") //builder.WriteString(":-- | :--: | :--\n") for _, proj := range pg.Projects { - builder.WriteString(fmt.Sprintf("- %s\n", proj.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(proj))) } } + if sm != nil { + builder.WriteString("\n\nSubject Matter: (HIDE THIS STRING)" + sm.Dst() + "\n") + sm.Dst() + } return []byte(builder.String()) } @@ -208,25 +212,25 @@ func (tr *tracked) String() string { if tr.Projects != nil { b.WriteString("# Projects\n") for _, proj := range tr.Projects { - b.WriteString(fmt.Sprintf("- %s\n", proj.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(proj))) } } if tr.Positions != nil { b.WriteString("# Positions\n") for _, pos := range tr.Positions { // TODO: ENSURE THESE ARE IN ORDER - b.WriteString(fmt.Sprintf("- %s at %s\n", pos.Link(), pos.company.Link())) + b.WriteString(fmt.Sprintf("- %s at %s\n", Link(pos), Link(pos.company))) } } if tr.Companies != nil { b.WriteString("# Companies\n") for _, item := range tr.Companies { - b.WriteString(fmt.Sprintf("- %s\n", item.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(item))) } } if tr.Clients != nil { b.WriteString("# Clients\n") for _, client := range tr.Clients { - b.WriteString(fmt.Sprintf("- %s\n", client.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(client))) } } return b.String() @@ -305,14 +309,43 @@ func FrequencyFromString(s string) Frequency { } } +var ( + _ Linkable = &CachePage{} + _ Linkable = &Client{} + _ Linkable = NoLinkable{} + _ Linkable = &CompanyPage{} + _ Linkable = &DbPage{} + _ Linkable = &SchoolPage{} + _ Linkable = &Interest{} // TODO: ptr ok? + _ Linkable = &LanguagePage{} + _ Linkable = &MiscSkill{} + _ Linkable = &PlatformPage{} + _ Linkable = &Position{} + _ Linkable = &Project{} + _ Linkable = &CloudProviderPage{} + _ Linkable = &CloudServicePage{} + _ Linkable = SubjectMatter("") + _ Linkable = &TechologyPage{} +) + type Linkable interface { // TODO: use??? EntryType() string - Link() string + // Link() string + Dst() string // TODO: impl + Title() string } var linkables = map[string]Linkable{} // TODO: USE THIS type NoLinkable struct{} +func (n NoLinkable) Dst() string { + return "error.md" // TODO: ok? +} + +func (n NoLinkable) Title() string { + return "FIX_ME" +} + func (n NoLinkable) EntryType() string { panic("nilEntryType") } diff --git a/generator/company.go b/generator/company.go index 078d948..06dea7f 100644 --- a/generator/company.go +++ b/generator/company.go @@ -41,16 +41,21 @@ type CompanyPage struct { //Languages []string // Calculated later } -func (pg *CompanyPage) NameValue() string { - return pg.Name +func (pg *CompanyPage) Dst() string { + return dstFor("cv", "company", withoutSpaces(pg.Name)) } -func (pg *CompanyPage) Link() string { +func (pg *CompanyPage) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "company", withoutSpaces(pg.Name)) + return pg.Name +} + +func (pg *CompanyPage) NameValue() string { + return pg.Name } + func (pg *CompanyPage) EntryType() string { return "Company" } @@ -89,7 +94,7 @@ func (pg *CompanyPage) Bytes() []byte { // TODO: NOT PROPERLY SORTED // TODO: ORDERING? // TODO: POS NOT WORKING ON DEERE CLIENT PAGE - builder.WriteString(fmt.Sprintf("- %s\n", pos.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(pos))) } } clis := pg.Clients() @@ -97,7 +102,7 @@ func (pg *CompanyPage) Bytes() []byte { if clis != nil && len(clis) > 0 { builder.WriteString("# Clients\n") for _, client := range clis { - builder.WriteString(fmt.Sprintf("- %s\n", client.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(client))) } } // resolve projects @@ -105,7 +110,7 @@ func (pg *CompanyPage) Bytes() []byte { if len(ps) > 0 { builder.WriteString("# Projects\n") for _, proj := range ps { - builder.WriteString(fmt.Sprintf("- %s\n", proj.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(proj))) } } // Resolve languages @@ -123,7 +128,7 @@ func (pg *CompanyPage) Bytes() []byte { } for f := 5; f >= 0; f-- { for _, name := range freqs[Frequency(f)] { - builder.WriteString(fmt.Sprintf("- %s\n", langs[name].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(langs[name]))) } } } diff --git a/generator/db.go b/generator/db.go index 2c2d9c3..92267f8 100644 --- a/generator/db.go +++ b/generator/db.go @@ -9,11 +9,15 @@ type DbPage struct { *tracked } -func (pg *DbPage) Link() string { +func (pg *DbPage) Dst() string { + return dstFor("cv", "db", withoutSpaces(pg.Name)) +} + +func (pg *DbPage) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "db", withoutSpaces(pg.Name)) + return pg.Name } func (pg *DbPage) EntryType() string { diff --git a/generator/education.go b/generator/education.go index 1aadac0..1845958 100644 --- a/generator/education.go +++ b/generator/education.go @@ -52,11 +52,15 @@ func (pg *SchoolPage) ProjectTypeInfo() projectTypeInfo { return schoolProjectTypeInfo{school: pg} } -func (pg *SchoolPage) Link() string { +func (pg *SchoolPage) Dst() string { + return dstFor("cv", "school", withoutSpaces(pg.Name)) +} + +func (pg *SchoolPage) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "school", withoutSpaces(pg.Name)) + return pg.Name } func (pg *SchoolPage) EntryType() string { @@ -78,7 +82,7 @@ func (sp *SchoolPage) Bytes() []byte { // Write all projects b.WriteString("# Projects\n") for _, proj := range sp.Projects { - b.WriteString(fmt.Sprintf("- %s\n", proj.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(proj))) } // Write all extracurriculars b.WriteString("# Extracurriculars and positions held\n") @@ -97,7 +101,7 @@ func (sp *SchoolPage) Bytes() []byte { if len(sp.SubjectMatters) > 0 { b.WriteString("# Related Subject Matters\n") for sm, _ := range sp.SubjectMatters { - b.WriteString(fmt.Sprintf("- %s\n", sm.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(sm))) } } @@ -166,10 +170,10 @@ func NewEcPosition(title, notes string) EcPosition { var ( schoolCata = NewSchool("Central Academy of Technology and Arts"). - WithSummary("Magnet High School, Engineering"). - WithSubjectMatters(smEducation, smStatics, smElectronics). - WithDegree(DegHS). - WithExtracurriculars( + WithSummary("Magnet High School, Engineering"). + WithSubjectMatters(smEducation, smStatics, smElectronics). + WithDegree(DegHS). + WithExtracurriculars( NewExtracurricular("Soccer", fixmeLink, NewEcPosition("Varsity", "Sophomore-Senior year"), NewEcPosition("Junior Varsity", "Freshman year"), @@ -191,11 +195,11 @@ var ( NewExtracurricular("Beta club", fixmeLink), ) schoolNCSU = NewSchool("North Carolina State University"). - WithSummary("Undergraduate studies"). - WithDegree(DegNE). - WithDegree(DegMath). - WithSubjectMatters(smNuclearEngineering, smParticlePhysics, smFluidMechanics, smThermodynamics, smEducation, smStatics, smElectronics). - WithExtracurriculars( + WithSummary("Undergraduate studies"). + WithDegree(DegNE). + WithDegree(DegMath). + WithSubjectMatters(smNuclearEngineering, smParticlePhysics, smFluidMechanics, smThermodynamics, smEducation, smStatics, smElectronics). + WithExtracurriculars( NewExtracurricular("American Nuclear Society", fixmeLink), NewExtracurricular("88.1 WKNC FM HD1 Raleigh", fixmeLink, NewEcPosition("DJ", "Sophomore-Junior years"), diff --git a/generator/interests.go b/generator/interests.go index 83a0931..ef428bb 100644 --- a/generator/interests.go +++ b/generator/interests.go @@ -23,8 +23,15 @@ func (pg *Interest) EntryType() string { return "Interest" } -func (i *Interest) Link() string { - return linkForInterest(i.Name) +func (pg *Interest) Dst() string { + return dstFor("cv", "interest", withoutSpaces(pg.Name)) +} + +func (pg *Interest) Title() string { + if pg == nil { + return noLinkText + } + return pg.Name } func linkForInterest(name string) string { diff --git a/generator/language.go b/generator/language.go index 88f78b8..e9451f2 100644 --- a/generator/language.go +++ b/generator/language.go @@ -30,14 +30,18 @@ func (pg *LanguagePage) WithSubjectMatters(sms ...SubjectMatter) *LanguagePage { return pg } -func (pg *LanguagePage) Link() string { +func (pg *LanguagePage) Dst() string { + return dstFor("cv", "language", withoutSpaces(pg.Name)) +} + +func (pg *LanguagePage) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } if pg.Name == "Cpp" { - return linkFor("C++", "cv", "language", withoutSpaces(pg.Name)) // TODO; will this need escaping on the plusses? + return "C++" // TODO: escape plusses? } - return linkFor(pg.Name, "cv", "language", withoutSpaces(pg.Name)) + return pg.Name } func (pg *LanguagePage) Bytes() []byte { @@ -47,13 +51,13 @@ func (pg *LanguagePage) Bytes() []byte { builder.WriteString("# Companies\n") for companyName := range pg.Companies { - builder.WriteString(fmt.Sprintf("- %s\n", companies[companyName].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(companies[companyName]))) } } if pg.Clients != nil && len(pg.Clients) > 0 { builder.WriteString("# Clients\n") for name := range pg.Clients { - builder.WriteString(fmt.Sprintf("- %s\n", clients[name].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(clients[name]))) } } if pg.Projects != nil && len(pg.Projects) > 0 { @@ -83,7 +87,7 @@ func (pg *LanguagePage) Bytes() []byte { panic("unknown project type: " + v) } - builder.WriteString(fmt.Sprintf("%s | %s | %s\n", projects[name].Link(), Frequency(f).String(), projTypeStr)) + builder.WriteString(fmt.Sprintf("%s | %s | %s\n", Link(projects[name]), Frequency(f).String(), projTypeStr)) } } } diff --git a/generator/main.go b/generator/main.go index 3088f27..17d84b3 100644 --- a/generator/main.go +++ b/generator/main.go @@ -1,6 +1,7 @@ package main import ( + "appli.ng/cv/generator/utils" "fmt" "maps" "os" @@ -20,6 +21,8 @@ import ( // TODO: PUT TAGS ALL OVER PROJECTS???!!!!!! // TODO: Folder page should alphabetize contents on list +// TODO: add TDD? OOP? functional? + func main() { for _, dir := range []string{"cv", "blog", "note"} { if err := os.MkdirAll("./quartz/content/"+dir, 777); err != nil { @@ -44,6 +47,7 @@ func main() { initPositionsAfterProjects() initCompaniesAfterPositions() // Must be done after positions and project setup, but before projects pages. What about clients? // TODO: POPULATE SUBJECT MATTERS ON TECHS, SERVICES, MISCSKILLS? + initAliases() // TODO: ok ???? // Start creating actual pages createLanguagesPages() @@ -104,7 +108,7 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? b.WriteString(frontmatterFor("CV")) // TODO: HEADER AREA FOR LINKS TO CV, RESUME, BLOG, NOTES? - b.WriteString("Welcome to my CV! It is a living document that is updated occasionally.\n\n") // Why does this need 2 newlines? + b.WriteString("Welcome to my CV! It is a living document that is updated occasionally.\n\n") // Why does this need 2 newlines? b.WriteString("Looking for a resume instead? [Download it here](static/Resume_Reece_Appling.pdf)ENSURE WORKING\n\n") // TODO: ENSURE OK b.WriteString("Feel free to check out the [source code](https://github.com/reeceappling/CV) for this website\n") @@ -120,7 +124,7 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? if endDate == "current" { endDate = "" } - b.WriteString(fmt.Sprintf("%s | %s | %s | %s\n", company.Positions[0].Name, company.Link(), company.Positions[len(company.Positions)-1].Start.String(), endDate)) + b.WriteString(fmt.Sprintf("%s | %s | %s | %s\n", company.Positions[0].Name, Link(company), company.Positions[len(company.Positions)-1].Start.String(), endDate)) } b.WriteString("# Education\n") @@ -133,7 +137,7 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? for i, d := range school.Degrees { temp[i] = d.StringShort() } - b.WriteString(fmt.Sprintf("- %s %s\n", school.Link(), strings.Join(temp, ", "))) + b.WriteString(fmt.Sprintf("- %s %s\n", Link(school), strings.Join(temp, ", "))) } b.WriteString("# Certifications\n") @@ -155,7 +159,7 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? b.WriteString("[see all languages LINK BROKEN](language/)\n") // TODO: delete? b.WriteString("## Preferred (in order)\n") for _, name := range []string{"Go", "Typescript", "Terraform", "Bash"} { - b.WriteString(fmt.Sprintf("- %s\n", langs[name].Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(langs[name]))) } b.WriteString("## All\n") b.WriteString(alphabetizedLinksCompressed(langs, "language")) @@ -169,7 +173,7 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? b.WriteString("# Cloud Providers\n[Full Page](providers.md)\n") b.WriteString("## Preferred\n") for _, name := range []string{"AWS"} { - b.WriteString(fmt.Sprintf("- %s\n", providers[name].Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(providers[name]))) } b.WriteString("## All\n") b.WriteString(alphabetizedLinksCompressed(providers, "provider")) @@ -230,7 +234,7 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? alphabetizedSkills := slices.Collect(maps.Keys(miscSkills)) sort.Strings(alphabetizedSkills) for _, name := range alphabetizedSkills { - b.WriteString(fmt.Sprintf("- %s\n", miscSkills[name].Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(miscSkills[name]))) } b.WriteString("# Interests\n") // TODO: move???? alphabetizedInterests := slices.Collect(maps.Keys(interests)) @@ -244,7 +248,7 @@ func createMainCVPage() { // TODO: TAGS EVERYWHERE???? return string(alphabetizedSms[i]) < string(alphabetizedSms[j]) }) for _, name := range alphabetizedSms { - b.WriteString(fmt.Sprintf("- %s\n", name.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(name))) } WriteFile("cv.md", b.String()) } @@ -262,7 +266,7 @@ func createLanguagesPages() { b.WriteString("# Preferred (in order)\n") for _, name := range []string{"Go", "Typescript", "Terraform", "Bash"} { l := langs[name] - b.WriteString(fmt.Sprintf("- %s\n", l.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(l))) } b.WriteString("# All\n") b.WriteString("[see all languages](language/)\n") // TODO: delete? @@ -298,21 +302,21 @@ func createProjectsPages() { for _, proj := range projects { switch proj.TypeInfo.Type() { case projectTypePersonal: - bPersonal.WriteString(fmt.Sprintf("- %s\n", proj.Link())) + bPersonal.WriteString(fmt.Sprintf("- %s\n", Link(proj))) case projectTypeProfessional: - pr := proj.Link() + pr := Link(proj) var co, cl = "none", "none" client := proj.TypeInfo.getClient() if client != nil { - cl = client.Link() + cl = Link(client) if client.company != nil { - co = client.company.Link() + co = Link(client.company) } } bProfessional.WriteString(fmt.Sprintf("%s | %s | %s\n", pr, co, cl)) case projectTypeSchool: - bSchool.WriteString(fmt.Sprintf("- %s\n", proj.Link())) // TODO: school link??? + bSchool.WriteString(fmt.Sprintf("- %s\n", Link(proj))) // TODO: school link??? default: panic("unknown project type") @@ -339,7 +343,7 @@ func createSchoolsPages() { b := strings.Builder{} b.WriteString(frontmatterFor("Schools")) for _, school := range schools { - b.WriteString(fmt.Sprintf("# %s\n", school.Link())) + b.WriteString(fmt.Sprintf("# %s\n", Link(school))) // Write all degrees b.WriteString("- Degrees:") for i, deg := range school.Degrees { @@ -352,7 +356,7 @@ func createSchoolsPages() { // Write all projects b.WriteString("- Projects:\n") for _, proj := range school.Projects { - b.WriteString(fmt.Sprintf("- - %s\n", proj.Link())) + b.WriteString(fmt.Sprintf("- - %s\n", Link(proj))) } b.WriteString("- Extracurriculars:\n") // Write all extracurriculars @@ -402,7 +406,7 @@ func createPositionsPages() { if end == "current" { end = "" } - b.WriteString(fmt.Sprintf(" %s | %s | %s | %s\n", pos.Link(), pos.company.Name, pos.Start.String(), end)) + b.WriteString(fmt.Sprintf(" %s | %s | %s | %s\n", Link(pos), pos.company.Name, pos.Start.String(), end)) } b.WriteString("Earliest") @@ -429,9 +433,9 @@ func createClientsPages() { client := clients[clientName] compLink := linkFor(client.Name, "cv", "company", withoutSpaces(client.Name)) if client.company != nil { - compLink = client.company.Link() + compLink = Link(client.company) } - b.WriteString(fmt.Sprintf("%s | %s \n", client.Link(), compLink)) + b.WriteString(fmt.Sprintf("%s | %s \n", Link(client), compLink)) } err := os.WriteFile(root+"clients.md", []byte(b.String()), 777) if err != nil { @@ -457,7 +461,7 @@ func createCompaniesPages() { for _, companyName := range companiesOrder { // TODO: Consider linking every position from here, but on separate lines company := companies[companyName] - b.WriteString(fmt.Sprintf("%s | %s | %s | %s\n", company.Positions[0].Name, company.Link(), company.Start.String(), company.End.String())) + b.WriteString(fmt.Sprintf("%s | %s | %s | %s\n", company.Positions[0].Name, Link(company), company.Start.String(), company.End.String())) } err := os.WriteFile(root+"companies.md", []byte(b.String()), 777) if err != nil { @@ -481,7 +485,7 @@ func createPlatformsPages() { sort.Strings(alphabetizedPlatforms) for _, platName := range alphabetizedPlatforms { platform := platforms[platName] - b.WriteString(fmt.Sprintf("- %s\n", platform.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(platform))) } err := os.WriteFile(root+"platforms.md", []byte(b.String()), 777) if err != nil { @@ -496,7 +500,7 @@ func createPlatformsPages() { if len(platform.SubjectMatters) == 0 { panic("no subject matter on platform " + name) } - WriteCVFile("platform/"+withoutSpaces(name)+".md", string(platform.Bytes(name, "Platform"))) + WriteCVFile("platform/"+withoutSpaces(name)+".md", string(platform.Bytes(name, nil, "Platform"))) } } @@ -508,7 +512,7 @@ func createDbsPages() { sort.Strings(alphabetized) for _, name := range alphabetized { db := dbs[name] - b.WriteString(fmt.Sprintf("- %s\n", db.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(db))) } err := os.WriteFile(root+"dbs.md", []byte(b.String()), 777) if err != nil { @@ -520,7 +524,8 @@ func createDbsPages() { } } for name, db := range dbs { - WriteCVFile("db/"+withoutSpaces(name)+".md", string(db.Bytes(name, "Database"))) + + WriteCVFile("db/"+withoutSpaces(name)+".md", string(db.Bytes(name, utils.Pointer(smDatabase), "Database"))) } } func createCachesPages() { @@ -531,7 +536,7 @@ func createCachesPages() { sort.Strings(alphabetized) for _, name := range alphabetized { cache := caches[name] - b.WriteString(fmt.Sprintf("- %s\n", cache.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(cache))) } err := os.WriteFile(root+"caches.md", []byte(b.String()), 777) if err != nil { @@ -543,7 +548,7 @@ func createCachesPages() { } } for name, cache := range caches { - WriteCVFile("cache/"+withoutSpaces(name)+".md", string(cache.Bytes(name, "Cache"))) + WriteCVFile("cache/"+withoutSpaces(name)+".md", string(cache.Bytes(name, utils.Pointer(smCaching), "Cache"))) } } @@ -555,7 +560,7 @@ func createProvidersPages() { b.WriteString("# Preferred (in order)\n") for _, name := range []string{"AWS"} { provider := providers[name] - b.WriteString(fmt.Sprintf("- %s\n", provider.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(provider))) // TODO: SUB-SERVICES!!!!! } b.WriteString("# All\n") @@ -581,7 +586,7 @@ func createServicesPages() { b.WriteString("Service | Provider\n") b.WriteString(":-- | :--\n") for _, service := range cloudServices { // TODO: alphabetize?!!!!! - b.WriteString(fmt.Sprintf("%s | %s\n", service.Link(), service.provider)) + b.WriteString(fmt.Sprintf("%s | %s\n", Link(service), service.provider)) } err := os.WriteFile(root+"services.md", []byte(b.String()), 777) if err != nil { @@ -593,7 +598,7 @@ func createServicesPages() { } } for name, service := range cloudServices { - WriteCVFile("service/"+withoutSpaces(name)+".md", string(service.Bytes(name, "Cloud Service"))) + WriteCVFile("service/"+withoutSpaces(name)+".md", string(service.Bytes(name, nil, "Cloud Service"))) } } @@ -605,7 +610,7 @@ func createTechnologiesPages() { sort.Strings(alphabetized) for _, name := range alphabetized { tech := techs[name] - b.WriteString(fmt.Sprintf("- %s\n", tech.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(tech))) } err := os.WriteFile(root+"technologies.md", []byte(b.String()), 777) if err != nil { @@ -621,7 +626,7 @@ func createTechnologiesPages() { panic("no subject matter on technology " + name) } typePlusTags := append([]string{"Technology"}, tech.Tags.AsSlice()...) - WriteCVFile("technology/"+withoutSpaces(name)+".md", string(tech.Bytes(name, typePlusTags...))) + WriteCVFile("technology/"+withoutSpaces(name)+".md", string(tech.Bytes(name, nil, typePlusTags...))) } } @@ -633,7 +638,7 @@ func createMiscSkillsPages() { sort.Strings(alphabetized) for _, name := range alphabetized { skill := miscSkills[name] - b.WriteString(fmt.Sprintf("- %s\n", skill.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(skill))) } err := os.WriteFile(root+"miscSkills.md", []byte(b.String()), 777) if err != nil { @@ -645,7 +650,7 @@ func createMiscSkillsPages() { } } for name, skill := range miscSkills { - WriteCVFile("miscSkill/"+withoutSpaces(name)+".md", string(skill.Bytes(name, "Misc Skill"))) + WriteCVFile("miscSkill/"+withoutSpaces(name)+".md", string(skill.Bytes(name, nil, "Misc Skill"))) } } func createInterestsPages() { @@ -666,7 +671,7 @@ func createInterestsPages() { } } for name, interest := range interests { - WriteCVFile("interest/"+withoutSpaces(name)+".md", string(interest.Bytes(name, "Interest"))) + WriteCVFile("interest/"+withoutSpaces(name)+".md", string(interest.Bytes(name, nil, "Interest"))) } } func createSubjectMatterPages() { @@ -678,7 +683,7 @@ func createSubjectMatterPages() { return string(alphabetizedSms[i]) < string(alphabetizedSms[j]) }) for _, name := range alphabetizedSms { - b.WriteString(fmt.Sprintf("- %s\n", name.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(name))) } err := os.WriteFile(root+"subjectMatters.md", []byte(b.String()), 777) if err != nil { diff --git a/generator/miscSkills.go b/generator/miscSkills.go index b1eb67e..6a05553 100644 --- a/generator/miscSkills.go +++ b/generator/miscSkills.go @@ -7,11 +7,15 @@ type MiscSkill struct { *tracked } -func (pg *MiscSkill) Link() string { +func (pg *MiscSkill) Dst() string { + return dstFor("cv", "miscSkill", withoutSpaces(pg.Name)) +} + +func (pg *MiscSkill) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "miscSkill", withoutSpaces(pg.Name)) // TODO: plural?? + return pg.Name } func (pg *MiscSkill) EntryType() string { diff --git a/generator/note.go b/generator/note.go index 2b54cd7..e80875a 100644 --- a/generator/note.go +++ b/generator/note.go @@ -18,22 +18,22 @@ func createNotesPages() { WriteFile("Notes.md", b.String()) // create all note pages for _, note := range notes { - WriteFile(fmt.Sprintf(`note/%s.md`, withoutSpaces(note.Title)), string(note.Bytes())) + WriteFile(fmt.Sprintf(`note/%s.md`, withoutSpaces(note.TitleText)), string(note.Bytes())) } } type Note struct { - Title string + TitleText string CreationDate dayMonthYr ModifiedDate *dayMonthYr ContentFile string Tags []string } -func (note Note) Bytes() []byte { +func (note *Note) Bytes() []byte { b := strings.Builder{} b.WriteString("---\n") - b.WriteString("title: " + note.Title + "\n") + b.WriteString("title: " + note.TitleText + "\n") b.WriteString("draft: false\n") if len(note.Tags) > 0 { b.WriteString("tags:\n") @@ -51,8 +51,18 @@ func (note Note) Bytes() []byte { return []byte(b.String()) } -func (note Note) Link() string { - return fmt.Sprintf(`[%s](note/%s)`, note.Title, withoutSpaces(note.Title)) +func (note *Note) Link() string { + return fmt.Sprintf(`[%s](note/%s)`, note.Title, withoutSpaces(note.TitleText)) +} +func (pg *Note) Dst() string { + return dstFor("note", withoutSpaces(pg.TitleText)) +} + +func (pg *Note) Title() string { + if pg == nil { + return noLinkText + } + return pg.TitleText } const notesDir = "notes/" @@ -65,7 +75,7 @@ func newNote(title string, creationDate dayMonthYr, modifiedDate *dayMonthYr, co panic("note " + contentFileName + " already exists") } notes = append(notes, Note{ - Title: title, + TitleText: title, CreationDate: creationDate, ModifiedDate: modifiedDate, ContentFile: contentFileName, diff --git a/generator/platform.go b/generator/platform.go index d48aff1..ddbfc8d 100644 --- a/generator/platform.go +++ b/generator/platform.go @@ -14,11 +14,15 @@ func (pg *PlatformPage) EntryType() string { return "Platform" } -func (pg *PlatformPage) Link() string { +func (pg *PlatformPage) Dst() string { + return dstFor("cv", "platform", withoutSpaces(pg.Name)) +} + +func (pg *PlatformPage) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "platform", withoutSpaces(pg.Name)) + return pg.Name } func (pg *PlatformPage) WithSubjectMatters(sms ...SubjectMatter) *PlatformPage { @@ -61,16 +65,16 @@ func setupPlatformSubjectMatters() { WithSubjectMatters(smBackend, smNetworking) NewPlatform("Gitlab"). WithSubjectMatters(smCiCd) - NewPlatform("Azure DevOps"). // TODO: USE - WithSubjectMatters(smDevOps) - NewPlatform("Jira"). // TODO: USE - WithSubjectMatters(smDevOps) - NewPlatform("Confluence"). // TODO: USE - WithSubjectMatters(smDocumentation) - NewPlatform("Slack"). // TODO: USE - WithSubjectMatters(smCommunication) - NewPlatform("Small Improvements"). // TODO: USE - WithSubjectMatters(smObservability) + NewPlatform("Azure DevOps"). + WithSubjectMatters(smDevSecOps) + NewPlatform("Jira"). + WithSubjectMatters(smDevSecOps) + NewPlatform("Confluence"). + WithSubjectMatters(smDocumentation) + NewPlatform("Slack"). + WithSubjectMatters(smCommunication) + NewPlatform("Small Improvements"). + WithSubjectMatters(smObservability) NewPlatform("Teams"). WithSubjectMatters(smCommunication) NewPlatform("Jamf"). diff --git a/generator/position.go b/generator/position.go index b9b6fa6..6140759 100644 --- a/generator/position.go +++ b/generator/position.go @@ -1,6 +1,7 @@ package main import ( + "appli.ng/cv/generator/tags" "appli.ng/cv/generator/utils" "fmt" "strings" @@ -9,30 +10,34 @@ import ( var positionsMap = map[string]*Position{} // Map of companyName+positionName to position type Position struct { - Name string - Start monthYr - End *monthYr // None == current - //Clients []*Client // clients may contain projects which are outside of this position + Name string + Start monthYr + End *monthYr // None == current Projects map[string]*Project // Parent company *CompanyPage - miscSkills utils.Set[string] // TODO: MISC SKILLS?????? + miscSkills utils.Set[string] // TODO: DISPLAY???? SubjectMatters utils.Set[SubjectMatter] // TODO: DISPLAY THIS??? // TODO: maybe dont have this on here + Tags tags.Field } func (pg *Position) NameValue() string { // The name to be used on the link. NOT the URL - return pg.Name // TODO: or mapName? + return pg.Name } func (pg *Position) EntryType() string { return "Position" } -func (pg *Position) Link() string { +func (pg *Position) Dst() string { + return dstFor("cv", "position", withoutSpaces(pg.Name)) +} + +func (pg *Position) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "position", withoutSpaces(pg.MapName())) + return pg.Name } func (pg *Position) WithMiscSkills(skills ...string) *Position { if pg == nil { @@ -87,16 +92,16 @@ func NewPosition(name string, startMo, startYr int, endMo, endYr *int) *Position func (pg *Position) Bytes() []byte { builder := strings.Builder{} - builder.WriteString(frontmatterFor(pg.Name)) + builder.WriteString(frontmatterFor(pg.Name, pg.Tags.AsStrings()...)) // TODO: Start/end // Company and clients - builder.WriteString(fmt.Sprintf("Company: %s\n", pg.company.Link())) + builder.WriteString(fmt.Sprintf("Company: %s\n", Link(pg.company))) if len(pg.Projects) > 0 { builder.WriteString("# Clients and Projects\n") builder.WriteString("Client | Project\n") builder.WriteString(":-- | :--\n") for _, pr := range pg.Projects { - builder.WriteString(fmt.Sprintf("%s | %s\n", pr.TypeInfo.getClient().Link(), pr.Link())) + builder.WriteString(fmt.Sprintf("%s | %s\n", Link(pr.TypeInfo.getClient()), Link(pr))) } } else { builder.WriteString("NO CLIENTS FOUND. FIXME\n ") @@ -105,14 +110,14 @@ func (pg *Position) Bytes() []byte { if len(pg.miscSkills) > 0 { builder.WriteString("# Misc Skills\n") for skill, _ := range pg.miscSkills { - builder.WriteString(fmt.Sprintf("- %s\n", miscSkills[skill].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(miscSkills[skill]))) } } // SubjectMatters if len(pg.SubjectMatters) > 0 { builder.WriteString("# Related Subject Matters\n") for sm, _ := range pg.SubjectMatters { - builder.WriteString(fmt.Sprintf("- %s\n", sm.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(sm))) } } @@ -129,6 +134,11 @@ func (pg *Position) MapName() string { return pg.company.Name + " " + pg.Name } +func (pg *Position) WithTags(tags ...tags.Tag) *Position { + pg.Tags = append(pg.Tags, tags...) + return pg +} + func (pg *Position) WithProjects(projs ...*Project) *Position { pg.Projects = map[string]*Project{} for _, proj := range projs { @@ -225,30 +235,37 @@ var ( func initPositionsAfterProjects() { // TODO: ANY MISC SKILLS positionLifeguard = NewPosition("Lifeguard", 1, 2013, utils.Pointer(6), utils.Pointer(2014)). // TODO: ensure dates are right - WithSubjectMatters(smFirstAid). - WithProjects() // TODO: THIS! + WithSubjectMatters(smFirstAid). + WithProjects(mafcProjects). + WithTags(tags.FirstAid) positionSeniorLifeguard = NewPosition("Senior Lifeguard", 6, 2014, utils.Pointer(8), utils.Pointer(2016)). // TODO: ensure dates are right - WithSubjectMatters(smFirstAid). - WithProjects() // TODO: THIS! + WithSubjectMatters(smFirstAid). + WithProjects(mafcProjects). + WithTags(tags.FirstAid) positionTAE = NewPosition("Civil Structural Engineer and Tower Climber", 5, 2017, utils.Pointer(3), utils.Pointer(2018)). // TODO: ensure dates are right - WithMiscSkills("Excel", "Climbing", "AutoDesk Inventor", "AutoCAD", "Autodesk Revit", "Drafting"). - WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics). - WithProjects() // TODO: THIS! + WithMiscSkills("Excel", "Climbing", "AutoDesk Inventor", "AutoCAD", "Autodesk Revit", "Drafting"). + WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics). + WithProjects(taePhotoImporter). + WithTags(tags.CivilEngineering, tags.StructuralEngineering, tags.Telecom) positionTEI = NewPosition("Cell Tower Inspector and Tower Climber", 1, 2019, utils.Pointer(3), utils.Pointer(2020)). // TODO: ensure dates are right - WithMiscSkills("Climbing", "Drafting"). - WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics). - WithProjects() // TODO: THIS! + WithMiscSkills("Climbing", "Drafting"). + WithSubjectMatters(smCivilEngineering, smStructuralEngineering, smStatics). + WithProjects(teiProjects). + WithTags(tags.CivilEngineering, tags.StructuralEngineering, tags.Telecom) freelancePosition = NewPosition("Software Engineer", 1, 2012, utils.Pointer(5), utils.Pointer(2022)). WithSubjectMatters(smFullStack). - WithProjects(WellAwareProject, CharityProject, CritColaProject, MastersDataAnalysisProject, WildlifeRProject, ArrowNailProject) + WithProjects(WellAwareProject, CharityProject, CritColaProject, MastersDataAnalysisProject, WildlifeRProject, ArrowNailProject). + WithTags(tags.SWE) sai1 = NewPosition("Software Engineer", 5, 2022, utils.Pointer(6), utils.Pointer(2023)). WithSubjectMatters(smFullStack). - WithProjects(rasterRenderProject, explorerProject, supportProject, scudsProject, simpsonUnivProject, jamfProject, smallImprovementsProject, internalResumeGeneratorProject) // TODO: MOVE PROJECTS AROUND + WithProjects(explorerProject, supportProject, scudsProject, simpsonUnivProject, jamfProject, smallImprovementsProject, internalResumeGeneratorProject). + WithTags(tags.SWE) // TODO: can the same project be on multiple positions? sai2 = NewPosition("Senior Software Engineer", 6, 2023, utils.Pointer(2), utils.Pointer(2025)). WithSubjectMatters(smBackend). - WithProjects(tileGenProject, polygonBuilderProject, GhaRunnersProject, statsProject, ufoProject, renderProject) + WithProjects(tileGenProject, rasterRenderProject, polygonBuilderProject, GhaRunnersProject, statsProject, ufoProject, renderProject). + WithTags(tags.SWE) sai3 = NewPosition("Senior Software Engineer and Tech Lead", 2, 2025, nil, nil). WithSubjectMatters(smBackend). - WithProjects(ogreProject, billingProject, goweProject, wqdbProject) - + WithProjects(ogreProject, billingProject, goweProject, wqdbProject). + WithTags(tags.SWE) } diff --git a/generator/project.go b/generator/project.go index 2c67f71..94a1d96 100644 --- a/generator/project.go +++ b/generator/project.go @@ -1,6 +1,7 @@ package main import ( + "appli.ng/cv/generator/tags" "appli.ng/cv/generator/utils" "fmt" "maps" @@ -54,18 +55,31 @@ type Project struct { RelatedInterests utils.Set[string] SubjectMatters utils.Set[SubjectMatter] - Tags utils.Set[Tag] + Tags utils.Set[tags.Tag] + size projectSize // TODO: PROJECT SIZE. Maybe get rid of? } +type projectSize string + +const ( + projectSizeS projectSize = "small" + projectSizeM projectSize = "medium" + projectSizeL projectSize = "large" +) + func (pg *Project) NameValue() string { return pg.Name } -func (pg *Project) Link() string { +func (pg *Project) Dst() string { + return dstFor("cv", "project", withoutSpaces(pg.Name)) +} + +func (pg *Project) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "project", withoutSpaces(pg.Name)) + return pg.Name } func (pg *Project) WithStatus(status projectStatus) *Project { @@ -145,12 +159,12 @@ func (pg *Project) Bytes() []byte { // Client/company/school if cli := pg.TypeInfo.getClient(); cli != nil { - builder.WriteString(fmt.Sprintf("Client: %s\n\n", cli.Link())) + builder.WriteString(fmt.Sprintf("Client: %s\n\n", Link(cli))) if comp := cli.company; comp != nil { - builder.WriteString(fmt.Sprintf("Company: %s\n", comp.Link())) + builder.WriteString(fmt.Sprintf("Company: %s\n", Link(comp))) } } else if sch := pg.TypeInfo.getSchool(); sch != nil { - builder.WriteString(fmt.Sprintf("School: %s\n", sch.Link())) + builder.WriteString(fmt.Sprintf("School: %s\n", Link(sch))) } // SUMMARY @@ -178,7 +192,7 @@ func (pg *Project) Bytes() []byte { } for f := 5; f >= 0; f-- { for _, name := range freqs[Frequency(f)] { - builder.WriteString(fmt.Sprintf("%s | %s\n", langs[name].Link(), Frequency(f).String())) + builder.WriteString(fmt.Sprintf("%s | %s\n", Link(langs[name]), Frequency(f).String())) } } } @@ -186,14 +200,14 @@ func (pg *Project) Bytes() []byte { if len(pg.Dbs) > 0 { builder.WriteString("# Databases\n") for db, _ := range pg.Dbs { - builder.WriteString(fmt.Sprintf("- %s\n", dbs[db].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(dbs[db]))) } } // CACHES if len(pg.Caches) > 0 { builder.WriteString("# Caches\n") for name, _ := range pg.Caches { - builder.WriteString(fmt.Sprintf("- %s\n", caches[name].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(caches[name]))) } } // Cloud Providers! @@ -202,9 +216,9 @@ func (pg *Project) Bytes() []byte { for provName, services := range pg.CloudProviders { svcStrings := make([]string, len(services)) for i, svc := range slices.Collect(maps.Keys(services)) { - svcStrings[i] = cloudServices[svc].Link() + svcStrings[i] = Link(cloudServices[svc]) } - builder.WriteString(fmt.Sprintf("- %s\n", providers[provName].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(providers[provName]))) builder.WriteString(strings.Join(svcStrings, ", ") + "\n") } } @@ -212,35 +226,35 @@ func (pg *Project) Bytes() []byte { if len(pg.Technologies) > 0 { builder.WriteString("# Technologies\n") for name, _ := range pg.Technologies { - builder.WriteString(fmt.Sprintf("- %s\n", techs[name].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(techs[name]))) } } // Platforms if len(pg.Platforms) > 0 { builder.WriteString("# Platforms\n") for name, _ := range pg.Platforms { - builder.WriteString(fmt.Sprintf("- %s\n", platforms[name].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(platforms[name]))) } } // Misc skills if len(pg.MiscSkills) > 0 { builder.WriteString("# Misc Skills\n") for skill, _ := range pg.MiscSkills { - builder.WriteString(fmt.Sprintf("- %s\n", miscSkills[skill].Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(miscSkills[skill]))) } } // SubjectMatters if len(pg.SubjectMatters) > 0 { builder.WriteString("# Related Subject Matters\n") for sm, _ := range pg.SubjectMatters { - builder.WriteString(fmt.Sprintf("- %s\n", sm.Link())) + builder.WriteString(fmt.Sprintf("- %s\n", Link(sm))) } } // Related Interests if len(pg.RelatedInterests) > 0 { builder.WriteString("# Related Interests\n") for interest, _ := range pg.RelatedInterests { - builder.WriteString(fmt.Sprintf("- %s\n", linkForInterest(interest))) + builder.WriteString(fmt.Sprintf("- %s\n", linkForInterest(interest))) // TODO: remove interests because of overlap with subject matters??? } } builder.WriteString(definitionsArea()) @@ -362,7 +376,7 @@ func (p *Project) WithTechnologies(names ...string) *Project { } return p } -func (p *Project) WithTags(tags ...Tag) *Project { +func (p *Project) WithTags(tags ...tags.Tag) *Project { p.Tags.Add(tags...) return p } @@ -416,23 +430,23 @@ func (p *Project) GetAllLowest() (outCaches map[string]*CachePage, outDbs map[st return } -func NewPersonalProject(name string, summary string, link *string) *Project { - return newProject(name, summary, personalProjectTypeInfo{}, link) +func NewPersonalProject(name string, summary string, link *string, size projectSize) *Project { + return newProject(name, summary, personalProjectTypeInfo{}, link, size) } -func NewProfessionalProject(name string, summary string, client *Client, link *string) *Project { - out := newProject(name, summary, client.ProjectTypeInfo(), link) +func NewProfessionalProject(name string, summary string, client *Client, link *string, size projectSize) *Project { + out := newProject(name, summary, client.ProjectTypeInfo(), link, size) client.WithProjects(out) return out } -func NewSchoolProject(name string, summary string, school *SchoolPage, link *string) *Project { - out := newProject(name, summary, school.ProjectTypeInfo(), link) +func NewSchoolProject(name string, summary string, school *SchoolPage, link *string, size projectSize) *Project { + out := newProject(name, summary, school.ProjectTypeInfo(), link, size) school.withProjects(out) return out } -func newProject(name string, summary string, info projectTypeInfo, link *string) *Project { +func newProject(name string, summary string, info projectTypeInfo, link *string, size projectSize) *Project { if strings.Contains(name, ".") || strings.Contains(name, "%") { panic("invalid character in project " + name) } @@ -449,7 +463,8 @@ func newProject(name string, summary string, info projectTypeInfo, link *string) Platforms: utils.Set[string]{}, SubjectMatters: map[SubjectMatter]struct{}{}, RelatedInterests: map[string]struct{}{}, - Tags: utils.Set[Tag]{}, + Tags: utils.Set[tags.Tag]{}, + size: size, } if _, exists := projects[name]; exists { panic("project already exists") @@ -471,55 +486,55 @@ var nfcScannerUrl = "github.com/reeceappling/nfcScanner" // TODO: ensure ok var coreShufflerUrl = "github.com/reeceappling/coreShuffler" // TODO: ensure ok var ( - projectAgentSwarm = NewPersonalProject("AI Agent Swarm", fixmeLink, nil) - polygonBuilderProject = NewProfessionalProject("Polygon Builder", fixmeLink, jdClient, nil) - tileGenProject = NewProfessionalProject("Tile Generator", fixmeLink, jdClient, nil) - GhaRunnersProject = NewProfessionalProject("Github Actions GPU Runners", "FIX SUMMARY", jdClient, nil) - ogreProject = NewProfessionalProject("Organizational Geospatial Rollup Engine", fixmeLink, jdClient, nil) - renderProject = NewProfessionalProject("Render Cluster", fixmeLink, jdClient, nil) // TODO: MORE! - statsProject = NewProfessionalProject("Statistics Cluster", fixmeLink, jdClient, nil) - billingProject = NewProfessionalProject("Billing Cluster", fixmeLink, jdClient, nil) - explorerProject = NewProfessionalProject("Transform Explorer", fixmeLink, jdClient, nil) - wqdbProject = NewProfessionalProject("Work Queue Database", fixmeLink, jdClient, nil) - supportProject = NewProfessionalProject("Support Cluster", fixmeLink, jdClient, nil) - scudsProject = NewProfessionalProject("Scuds API", fixmeLink, jdClient, nil) - ufoProject = NewProfessionalProject("UFO API", fixmeLink, jdClient, nil) - goweProject = NewProfessionalProject("Gowe Builder", fixmeLink, jdClient, nil) - rasterRenderProject = NewProfessionalProject("Raster Render", fixmeLink, jdClient, nil) - simpsonUnivProject = NewProfessionalProject("Simpson University", fixmeLink, simpsonUniversityClient, nil) // TODO: MORE! - jamfProject = NewProfessionalProject("JAMF companywide setup", fixmeLink, sourceAlliesClient, nil) - smallImprovementsProject = NewProfessionalProject("Small Improvements Bot", fixmeLink, sourceAlliesClient, nil) - internalResumeGeneratorProject = NewProfessionalProject("Source Allies Internal Consultant Resume Generator", fixmeLink, sourceAlliesClient, nil) - mushDbProject = NewPersonalProject("MushDb", fixmeLink, &mushDbUrl) - cvProject = NewPersonalProject("Personal Site and CV", "This project! A generator which creates markdown files that can be viewed via Obsidian, or published to the web.", &cvUrl) // TODO: make multiple strings an available option for summary - linksPage = NewPersonalProject("Personal Links Page", "A page to put all my links", nil) // TODO: LINKS PAGE - measurementsProject = NewPersonalProject("Measurements", fixmeLink, &measurementsUrl) - nfcScannerProject = NewPersonalProject("Nfc Scanner", fixmeLink, &nfcScannerUrl) - teiProjects = NewProfessionalProject("Tei Projects", fixmeLink, teiClient, nil) // TODO: maybe add an actual project - taePhotoImporter = NewProfessionalProject("Tae Field Photograph Importer", fixmeLink, taeClient, nil) - mafcProjects = NewProfessionalProject("Mafc Projects", fixmeLink, mafcClient, nil) // TODO: maybe add actual projects? - coreShufflerProject = NewPersonalProject("Simulate Core Shuffler", fixmeLink, &coreShufflerUrl) - CharityProject = NewProfessionalProject("Charity Site", fixmeLink, charityClient, nil) - WellAwareProject = NewProfessionalProject("Well Aware NC", fixmeLink, clarkClient, nil) // TODO: change client to the lab??? - CritColaProject = NewProfessionalProject("CritCola", fixmeLink, critColaClient, nil) // TODO: ADD OTHER CRITCOLA PROJECTS - ArrowNailProject = NewProfessionalProject("Hail History Tracker", fixmeLink, arrowNailClient, nil) - WildlifeRProject = NewProfessionalProject("Wildlife R Data Analysis", fixmeLink, wildlifeRClient, nil) - MastersDataAnalysisProject = NewProfessionalProject("Masters Data Analysis", fixmeLink, clarkClient, nil) - capstoneProject = NewSchoolProject("Capstone Project-Uranium Silicide Accident Tolerant Fuel cycle design for Duke Energy Catawba Nuclear Plant", "FIX M_E", schoolNCSU, &coreShufflerUrl) - reactorAnalysisFinal = NewSchoolProject("Reactor Analysis Exam", "Reactor analysis final exam code", schoolNCSU, utils.Pointer(fixmeLink)) // TODO: ADD REAL LINK! - monteCarloProject = NewSchoolProject("MonteCarlo scattering and decay project", "FIX_ME", schoolNCSU, utils.Pointer(fixmeLink)) // TODO: ADD REAL LINK! - // TODO; FORTRAN SCHOOL PROJECT - projectLinAlgCryptography = NewSchoolProject("Linear algebra cryptography algorithm", "FIX M_E", schoolNCSU, nil) - cherenkovProject = NewSchoolProject("Cherenkov radiation sensor", fixmeLink, schoolNCSU, nil) - roboticsTeamProject = NewSchoolProject("Robotics team 3720", fixmeLink, schoolCata, nil) - cncLaserCutterProject = NewSchoolProject("CNC Laser Cutter", fixmeLink, schoolCata, nil) - aerospaceFinalProject = NewSchoolProject("Aerospace senior design course", "Designed, created, and tested a rocket from scratch", schoolCata, nil) - miscSmallPersonalProjects = NewPersonalProject("Misc small personal projects", "A conglomeration of personal projects which did not each deserve their own entry", nil) + projectAgentSwarm = NewPersonalProject("AI Agent Swarm", fixmeLink, nil, projectSizeS) + polygonBuilderProject = NewProfessionalProject("Polygon Builder", fixmeLink, jdClient, nil, projectSizeL) + tileGenProject = NewProfessionalProject("Tile Generator", fixmeLink, jdClient, nil, projectSizeL) + GhaRunnersProject = NewProfessionalProject("Github Actions GPU Runners", "FIX SUMMARY", jdClient, nil, projectSizeM) + ogreProject = NewProfessionalProject("Organizational Geospatial Rollup Engine", fixmeLink, jdClient, nil, projectSizeL) + renderProject = NewProfessionalProject("Render Cluster", fixmeLink, jdClient, nil, projectSizeL) // TODO: MORE! + statsProject = NewProfessionalProject("Statistics Cluster", fixmeLink, jdClient, nil, projectSizeL) + billingProject = NewProfessionalProject("Billing Cluster", fixmeLink, jdClient, nil, projectSizeL) + explorerProject = NewProfessionalProject("Transform Explorer", fixmeLink, jdClient, nil, projectSizeM) + wqdbProject = NewProfessionalProject("Work Queue Database", fixmeLink, jdClient, nil, projectSizeM) + supportProject = NewProfessionalProject("Support Cluster", fixmeLink, jdClient, nil, projectSizeM) + scudsProject = NewProfessionalProject("Scuds API", fixmeLink, jdClient, nil, projectSizeL) + ufoProject = NewProfessionalProject("UFO API", fixmeLink, jdClient, nil, projectSizeL) + goweProject = NewProfessionalProject("Gowe Builder", fixmeLink, jdClient, nil, projectSizeL) + rasterRenderProject = NewProfessionalProject("Raster Render", fixmeLink, jdClient, nil, projectSizeL) + simpsonUnivProject = NewProfessionalProject("Simpson University", fixmeLink, simpsonUniversityClient, nil, projectSizeS) // TODO: MORE! + jamfProject = NewProfessionalProject("JAMF companywide setup", fixmeLink, sourceAlliesClient, nil, projectSizeM) + smallImprovementsProject = NewProfessionalProject("Small Improvements Bot", fixmeLink, sourceAlliesClient, nil, projectSizeS) + internalResumeGeneratorProject = NewProfessionalProject("Source Allies Internal Consultant Resume Generator", fixmeLink, sourceAlliesClient, nil, projectSizeM) + mushDbProject = NewPersonalProject("MushDb", fixmeLink, &mushDbUrl, projectSizeL) + cvProject = NewPersonalProject("Personal Site and CV", "This project! A generator which creates markdown files that can be viewed via Obsidian, or published to the web.", &cvUrl, projectSizeM) // TODO: make multiple strings an available option for summary + linksPage = NewPersonalProject("Personal Links Page", "A page to put all my links", nil, projectSizeS) // TODO: LINKS PAGE + measurementsProject = NewPersonalProject("Measurements", fixmeLink, &measurementsUrl, projectSizeM) + nfcScannerProject = NewPersonalProject("Nfc Scanner", fixmeLink, &nfcScannerUrl, projectSizeS) + teiProjects = NewProfessionalProject("Tei Projects", fixmeLink, teiClient, nil, projectSizeS) // TODO: maybe add an actual project + taePhotoImporter = NewProfessionalProject("Tae Field Photograph Importer", fixmeLink, taeClient, nil, projectSizeS) + mafcProjects = NewProfessionalProject("Mafc Projects", fixmeLink, mafcClient, nil, projectSizeS) // TODO: maybe add actual projects? + coreShufflerProject = NewPersonalProject("Simulate Core Shuffler", fixmeLink, &coreShufflerUrl, projectSizeM) + CharityProject = NewProfessionalProject("Charity Site", fixmeLink, charityClient, nil, projectSizeM) + WellAwareProject = NewProfessionalProject("Well Aware NC", fixmeLink, clarkClient, nil, projectSizeM) // TODO: change client to the lab??? + CritColaProject = NewProfessionalProject("CritCola", fixmeLink, critColaClient, nil, projectSizeM) // TODO: ADD OTHER CRITCOLA PROJECTS + ArrowNailProject = NewProfessionalProject("Hail History Tracker", fixmeLink, arrowNailClient, nil, projectSizeM) + WildlifeRProject = NewProfessionalProject("Wildlife R Data Analysis", fixmeLink, wildlifeRClient, nil, projectSizeM) + MastersDataAnalysisProject = NewProfessionalProject("Masters Data Analysis", fixmeLink, clarkClient, nil, projectSizeM) + capstoneProject = NewSchoolProject("Capstone Project-Uranium Silicide Accident Tolerant Fuel cycle design for Duke Energy Catawba Nuclear Plant", "FIX M_E", schoolNCSU, &coreShufflerUrl, projectSizeL) + reactorAnalysisFinal = NewSchoolProject("Reactor Analysis Exam", "Reactor analysis final exam code", schoolNCSU, utils.Pointer(fixmeLink), projectSizeM) // TODO: ADD REAL LINK! + monteCarloProject = NewSchoolProject("MonteCarlo nuclear scattering and decay project", "FIX_ME", schoolNCSU, utils.Pointer(fixmeLink), projectSizeM) // TODO: ADD REAL LINK! + // TODO; FORTRAN SCHOOL PROJECT FOR ATM + projectLinAlgCryptography = NewSchoolProject("Linear algebra cryptography algorithm", "FIX M_E", schoolNCSU, nil, projectSizeM) + cherenkovProject = NewSchoolProject("Cherenkov radiation sensor", fixmeLink, schoolNCSU, nil, projectSizeM) + roboticsTeamProject = NewSchoolProject("Robotics team 3720", fixmeLink, schoolCata, nil, projectSizeM) + cncLaserCutterProject = NewSchoolProject("CNC Laser Cutter", fixmeLink, schoolCata, nil, projectSizeM) + aerospaceFinalProject = NewSchoolProject("Aerospace senior design course", "Designed, created, and tested a rocket from scratch", schoolCata, nil, projectSizeM) + miscSmallPersonalProjects = NewPersonalProject("Misc small personal projects", "A conglomeration of personal projects which did not each deserve their own entry", nil, projectSizeS) ) func initProjectsFinal() { projectAgentSwarm = projectAgentSwarm.WithStatus(statusMaintaining). - WithSummary("SUMMARY HERE"). // TODO: MORE! + WithSummary("Create an AI coding agent swarm with "+Link(lookup("Langgraph"))+" and "+Link(lookup("Poolside AI"))). WithLang("Go", Extensively, smBackend). WithInterests("Agentic AI", "Agent Swarms"). // TODO: any more? WithPlatforms("Poolside AI"). @@ -528,7 +543,7 @@ func initProjectsFinal() { //WithTags(tagAI, tagBackend). finalize() polygonBuilderProject = polygonBuilderProject.WithStatus(statusMaintaining). - WithSummary("SUMMARY HERE"). // TODO: MORE! + WithSummary("Service that utilizes a modified AlphaShapes algorithm to take inputs of millions of datapoints of geospatiotemporal agribusiness data, and output multipolygons representing field operations and subsets thereof."). WithLang("Go", Extensively, smBackend, smScripting). WithLang("Scala", Often, smBackend). WithLang("Terraform", Regularly, smIAC). @@ -550,6 +565,7 @@ func initProjectsFinal() { WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "Azure DevOps", "Jira", "Confluence"). WithSubjectMatters(smApi, smGeospatial, smBackend, smTopology, smLinearAlgebra, smClusterComputing, smDistributedComputing, smContainerization, smIAC, smCiCd). //WithTags(tagBackend, tagTopology, tagLinearAlgebra, tagClusterComputing, tagDistributedComputing, tagContainerization, tagIAC, tagCiCd) + WithMiscSkills("GIS", "QGIS"). finalize() tileGenProject = tileGenProject.WithStatus(statusMaintaining). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -577,7 +593,7 @@ func initProjectsFinal() { ). WithTechnologies("Github Actions"). WithPlatforms("Datadog", "Logcentral", "Github", "Azure DevOps", "Jira", "Confluence", "Teams"). - WithSubjectMatters(smBackend, smCiCd, smDevOps, smContainerization, smIAC, smCiCd). + WithSubjectMatters(smBackend, smCiCd, smDevSecOps, smContainerization, smIAC, smCiCd). finalize() ogreProject = ogreProject.WithStatus(statusBuilding). WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -625,7 +641,7 @@ func initProjectsFinal() { WithLang("Rust", Minimal). WithDbs("Aurora", "Postgres"). WithCaches("Redis", "memcached"). - WithTechnologies("CGo", "CUDA", "Github Actions", "Parquet", "Avro", "GraphQL", "SIMD", "REST API"). + WithTechnologies("CGo", "Swagger", "OpenAPI", "CUDA", "Github Actions", "Parquet", "Avro", "GraphQL", "SIMD", "REST API"). WithPlatforms("Datadog", "Logcentral", "Github", "ServiceNow", "GraphQL Apollo", "Azure DevOps", "Jira", "Confluence"). WithCloudProvider("AWS", "ECS", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", @@ -732,7 +748,7 @@ func initProjectsFinal() { WithLang("Bash", Some). WithLang("Docker", Regularly). WithCloudProvider("AWS", - "ECS", "Fargate", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", + "ECS", "Fargate", "VPC", "VPN", "S3", "EC2", "IAM", "SecretsManager", "DynamoDB", "DAX", "Cloudwatch", "Lambda", "ECR", "Route53", "Kinesis", "SQS", "SNS", "Kinesis", ). WithDbs("DuckDB", "Aurora", "Postgres"). WithCaches("Redis"). @@ -748,7 +764,7 @@ func initProjectsFinal() { WithStatus(statusComplete). WithPlatforms("DroneCI", "Rally"). WithSubjectMatters(smBackend). - WithCloudProvider("AWS", "ECS", "EC2", "API Gateway", "IAM", "ELB", "ALB"). + WithCloudProvider("AWS", "ECS", "EC2", "API Gateway", "IAM", "ELB", "ALB", "Corretto"). finalize() simpsonUnivProject = simpsonUnivProject.WithStatus(statusComplete). // TODO: MORE! WithSummary("SUMMARY HERE"). // TODO: MORE! @@ -788,7 +804,7 @@ func initProjectsFinal() { WithTechnologies("Github Actions", "Obsidian", "Markdown", "Quartz", "Quartz 4"). WithPlatforms("Github"). WithCloudProvider("AWS", "S3", "IAM", "Cloudfront", "Cloudfront Functions", "ACM"). - WithSubjectMatters(smCiCd, smFrontend, smDevOps, smServerless). + WithSubjectMatters(smCiCd, smFrontend, smDevSecOps, smServerless). finalize() linksPage = linksPage. WithStatus(statusComplete). @@ -838,7 +854,7 @@ func initProjectsFinal() { WithCloudProvider("AWS", "S3", "IAM", "Route53"). WithTechnologies("Gitlab CI", "React", "NodeJS"). - WithSubjectMatters(smCiCd, smDevOps, smFrontend). + WithSubjectMatters(smCiCd, smDevSecOps, smFrontend). finalize() CritColaProject = CritColaProject.WithStatus(statusComplete). WithSummary("SUMMARY HERE"). // TODO: this @@ -851,7 +867,7 @@ func initProjectsFinal() { WithPlatforms("Gitlab", "Discord"). WithCloudProvider("AWS", "S3", "EC2", "IAM", "SecretsManager", "ECR", "Route53"). - WithSubjectMatters(smBackend, smCiCd, smDevOps, smScripting). + WithSubjectMatters(smBackend, smCiCd, smDevSecOps, smScripting). WithInterests("Gaming"). finalize() MastersDataAnalysisProject = MastersDataAnalysisProject.WithStatus(statusComplete). @@ -892,7 +908,7 @@ func initProjectsFinal() { WithInterests(string(smCryptography), string(smCybersecurity)). finalize() cherenkovProject = cherenkovProject.WithStatus(statusComplete). - WithSummary("Designed and built a sensor for Cherenkov radiation, which was tested by lowering the sensor into the core of the PULSTAR reactor at "+schoolNCSU.Link()+". The sensor utilized and Arduino for signal processing."). + WithSummary("Designed and built a sensor for Cherenkov radiation, which was tested by lowering the sensor into the core of the PULSTAR reactor at "+Link(schoolNCSU)+". The sensor utilized and Arduino for signal processing."). WithLang("Python", Often). WithLang("Fortran", Often). WithTechnologies("Arduino"). @@ -924,20 +940,6 @@ func initProjectsFinal() { WithLang("Javascript", Minimal). WithSubjectMatters(smFluidMechanics, smElectronics, smChemistry). finalize() - miscSmallPersonalProjects = miscSmallPersonalProjects. - WithSummary("A catch-all for my miscellaneous personal projects I didn't want to add entire pages for"). - WithStatus(statusShelved). - WithLang("Python", Regularly, smScripting). - WithLang("Fortran", Some, smScripting, smThermodynamics, smFluidMechanics, smParticlePhysics, smNuclearEngineering, smEducation, smEmbeddedSystems, smStatistics, smLinearAlgebra, smStatics). - WithLang("Solidity", Some, smCryptocurrency). - WithLang("Go", Extensively, smThermodynamics). - WithLang("Java", Some, smBackend). - WithLang("Javascript", Often, smFrontend). - WithLang("Typescript", Often, smFullStack). - WithLang("Kotlin", Rarely, smBackend). - WithTechnologies("Kubernetes"). - WithSubjectMatters(smApi, smCryptocurrency). - finalize() teiProjects = teiProjects. WithSummary("A catch-all for all projects at my position at TEI."). WithStatus(statusComplete). @@ -980,7 +982,7 @@ func initProjectsFinal() { WithLang("Javascript", Often). WithSummary("Created a Small Improvements Slack bot for Source Allies to track goal creation and achievement"). WithCloudProvider("AWS", "Lambda", "DynamoDB", "SAM", "Cloudformation"). // TODO: AWS - WithPlatforms("Slack", "Small Improvements", "Github"). + WithPlatforms("Slack", "Small Improvements", "Github", "Jira", "Confluence", "Slack"). WithTechnologies("Github Actions", "REST API"). WithStatus(statusComplete). WithSubjectMatters(smApi, smBackend, smServerless). @@ -998,6 +1000,36 @@ func initProjectsFinal() { WithTechnologies("Github Actions"). WithCloudProvider("AWS", "Route53"). // TODO: add route53 everywhere needed finalize() + // TODO: root-finding algorithms project + miscSmallPersonalProjects = miscSmallPersonalProjects. + WithSummary("A catch-all for my miscellaneous personal projects I didn't want to add entire pages for"). + WithStatus(statusShelved). + WithLang("Python", Regularly, smScripting). + WithLang("Fortran", Some, smScripting, smThermodynamics, smFluidMechanics, smParticlePhysics, smNuclearEngineering, smEducation, smEmbeddedSystems, smStatistics, smLinearAlgebra, smStatics). + WithLang("Solidity", Some, smCryptocurrency). + WithLang("Go", Extensively, smThermodynamics, smScripting, smBackend). + WithLang("Java", Some, smBackend). + WithLang("Javascript", Often, smFrontend, smScripting). + WithLang("Typescript", Often, smFullStack, smScripting). + WithLang("Kotlin", Rarely, smBackend). + WithLang("Rust", Rarely, smBackend). + WithLang("Lua", Rarely, smScripting). + WithLang("Batch", Rarely, smScripting). + WithLang("Assembly", Rarely, smEmbeddedSystems). + WithLang("WASM", Rarely, smFrontend). + WithLang("ARM Assembly", Rarely, smEmbeddedSystems). + WithLang("ARM Assembly", Rarely, smEmbeddedSystems). + WithLang("Zig", Rarely, smBackend). + WithLang("Cobol", Rarely, smBackend). + WithLang("LISP", Rarely, smBackend). + WithLang("LABVIEW", Rarely, smScripting). // TODO: ok or put elsewhere? + WithLang("MATLAB", Rarely, smScripting). // TODO: ok or put elsewhere? + WithLang("Perl", Rarely, smScripting). + WithLang("PowerShell", Rarely, smScripting). + WithLang("Ruby", Rarely, smScripting). + WithTechnologies("Kubernetes", "Kafka", "ORC"). + WithSubjectMatters(smApi, smCryptocurrency). + finalize() } type projectTypeInfo interface { diff --git a/generator/provider.go b/generator/provider.go index de0f167..c58a2dc 100644 --- a/generator/provider.go +++ b/generator/provider.go @@ -37,21 +37,25 @@ func (pg *CloudProviderPage) Bytes() []byte { b.WriteString(frontmatterFor(pg.Name, "Cloud Provider")) b.WriteString("# Services\n") for _, svc := range pg.Services { - b.WriteString(fmt.Sprintf("- %s\n", svc.Link())) + b.WriteString(fmt.Sprintf("- %s\n", Link(svc))) } - b.WriteString("\n" + string(pg.tracked.Bytes("", ""))) + b.WriteString("\n" + string(pg.tracked.Bytes("", nil, ""))) // TODO: ok? return []byte(b.String()) } -func (pg *CloudProviderPage) Link() string { - if pg == nil { - return "NO_LINK" - } - return linkFor(pg.Name, "cv", "provider", withoutSpaces(pg.Name)) -} func (pg *CloudProviderPage) EntryType() string { return "Cloud Provider" } +func (pg *CloudProviderPage) Dst() string { + return dstFor("cv", "provider", withoutSpaces(pg.Name)) +} + +func (pg *CloudProviderPage) Title() string { + if pg == nil { + return noLinkText + } + return pg.Name +} func NewCloudProvider(name string, services ...string) *CloudProviderPage { prov, exists := providers[name] @@ -60,9 +64,6 @@ func NewCloudProvider(name string, services ...string) *CloudProviderPage { } svcs := make(map[string]*CloudServicePage, len(services)) for _, svc := range services { - if svc == "Fargate" { - println("fargate found") - } serv, existing := cloudServices[svc] if !existing || serv == nil { serv = NewService(svc, name) diff --git a/generator/service.go b/generator/service.go index 892a3a6..62bf1a1 100644 --- a/generator/service.go +++ b/generator/service.go @@ -26,11 +26,15 @@ func (pg *CloudServicePage) WithSubjectMatters(sms ...SubjectMatter) *CloudServi return pg } -func (pg *CloudServicePage) Link() string { +func (pg *CloudServicePage) Dst() string { + return dstFor("cv", "service", withoutSpaces(pg.Name)) +} + +func (pg *CloudServicePage) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "service", withoutSpaces(pg.Name)) + return pg.Name } func NewService(name string, provider string) *CloudServicePage { @@ -52,16 +56,77 @@ func setupServiceSubjectMatters() { NewService("Cloudwatch", "AWS"). WithSubjectMatters(smObservability) NewService("Lambda", "AWS"). - WithSubjectMatters(smServerless) + WithSubjectMatters(smServerless, smCloudComputing) NewService("Fargate", "AWS"). - WithSubjectMatters(smServerless) + WithSubjectMatters(smServerless, smCloudComputing) NewService("Aurora", "AWS"). - WithSubjectMatters(smServerless) + WithSubjectMatters(smServerless, smDatabase) + //WithTags("Database") // TODO: ok??? NewService("DynamoDB", "AWS"). + WithSubjectMatters(smServerless, smDatabase) + NewService("RDS", "AWS"). + WithSubjectMatters(smDatabase) + NewService("DocumentDB", "AWS"). + WithSubjectMatters(smDatabase) + NewService("DAX", "AWS"). + WithSubjectMatters(smCaching) + NewService("Elasticache", "AWS"). // TODO: use! + WithSubjectMatters(smCaching) + //NewService("DocumentDB", "AWS"). // TODO: use if used + // WithSubjectMatters(smServerless) + NewService("IAM", "AWS"). + WithSubjectMatters(smCybersecurity) + NewService("ECS", "AWS"). + WithSubjectMatters(smCloudComputing, smClusterComputing) + // TODO: ASGs? CapacityProviders? + // TODO: Glue? + NewService("EC2", "AWS"). + WithSubjectMatters(smCloudComputing) + NewService("Secrets Manager", "AWS"). + WithSubjectMatters(smCybersecurity) + NewService("Key Management Service", "AWS"). + WithSubjectMatters(smCybersecurity) + NewService("Cloudfront", "AWS"). + WithSubjectMatters(smFullStack) // TODO: OK? + NewService("ECS", "AWS"). + WithSubjectMatters(smCloudComputing, smClusterComputing) + NewService("ECR", "AWS"). + WithSubjectMatters(smCloudComputing, smClusterComputing, smContainerization) + NewService("SAM", "AWS"). WithSubjectMatters(smServerless) - NewService("SAM", "AWS"). // TODO: use somewhere - WithSubjectMatters(smServerless) + NewService("EBS", "AWS"). + WithSubjectMatters(smBackend) + //NewService("EFS", "AWS"). // TODO: use somewhere when used + // WithSubjectMatters(smBackend) + //NewService("EKS", "AWS"). // TODO: use somewhere when used + // WithSubjectMatters(smBackend, smClusterComputing) + NewService("Kinesis", "AWS"). // TODO: use somewhere when used + WithSubjectMatters(smBackend, smClusterComputing) // TODO: event driven??? NewService("Cloudformation", "AWS"). // TODO: use somewhere WithSubjectMatters(smIAC) + NewService("Chime", "AWS"). // TODO: use somewhere + WithSubjectMatters(smCommunication) + NewService("SQS", "AWS"). // TODO: use somewhere + WithSubjectMatters(smBackend) // TODO: event driven??? + NewService("SNS", "AWS"). // TODO: use somewhere + WithSubjectMatters(smBackend) // TODO: event driven??? + //NewService("SES", "AWS"). // TODO: use somewhere when used + // WithSubjectMatters(smBackend) + NewService("VPC", "AWS"). // TODO: use somewhere when used + WithSubjectMatters(smBackend, smNetworking) + NewService("VPN", "AWS"). // TODO: use somewhere when used + WithSubjectMatters(smBackend, smNetworking) + //NewService("MQ", "AWS"). // TODO: use somewhere + // WithSubjectMatters(smBackend) // TODO: event driven??? + //NewService("Cognito", "AWS"). // TODO: use somewhere when used + // WithSubjectMatters(smCommunication) + //NewService("Athena", "AWS"). // TODO: use somewhere when used + // WithSubjectMatters(smBackend) + //NewService("Bedrock", "AWS"). // TODO: use somewhere when used + // WithSubjectMatters(smBackend, smAI) + NewService("Corretto", "AWS"). // TODO: use somewhere when used + WithSubjectMatters(smBackend) + NewService("S3", "AWS"). + WithSubjectMatters(smBackend) } diff --git a/generator/subjectMatter.go b/generator/subjectMatter.go index db0bc19..eee48c8 100644 --- a/generator/subjectMatter.go +++ b/generator/subjectMatter.go @@ -9,11 +9,16 @@ var subjectMatters = map[SubjectMatter]map[string]utils.Set[string]{} // Map of type SubjectMatter string -func (sm SubjectMatter) Link() string { +func (sm SubjectMatter) Dst() string { if string(sm) == "CI-CD" { + dstFor("cv", "subjectMatter", withoutSpaces(string(sm))) return linkFor("CI-CD", "cv", "subjectMatter", withoutSpaces(string(sm))) // TODO; FIX SO IT SAYS CI/CD } - return linkFor(string(sm), "cv", "subjectMatter", withoutSpaces(string(sm))) + return dstFor("cv", "subjectMatter", withoutSpaces(string(sm))) +} + +func (sm SubjectMatter) Title() string { + return string(sm) } func (pg SubjectMatter) EntryType() string { return "Subject Matter" @@ -52,15 +57,15 @@ func withSubjectMatters(pg Linkable, setIn utils.Set[SubjectMatter], sms ...Subj if current, exists := subjectMatters[sm]; exists { newInternalMap := current if currentSet, setExists := current[pg.EntryType()]; setExists { - currentSet.Add(pg.Link()) + currentSet.Add(Link(pg)) newInternalMap[pg.EntryType()] = currentSet // TODO: use new set? } else { - newInternalMap[pg.EntryType()] = utils.SetFrom(pg.Link()) + newInternalMap[pg.EntryType()] = utils.SetFrom(Link(pg)) } subjectMatters[sm] = newInternalMap } else { subjectMatters[sm] = map[string]utils.Set[string]{ - pg.EntryType(): utils.SetFrom(pg.Link()), + pg.EntryType(): utils.SetFrom(Link(pg)), } } } @@ -75,7 +80,7 @@ var ( smDistributedComputing = NewSubjectMatter("Distributed Computing") smContainerization = NewSubjectMatter("Containerization") smIAC = NewSubjectMatter("Infrastructure As Code") - smDevOps = NewSubjectMatter("DevOps") + smDevSecOps = NewSubjectMatter("DevSecOps") smCiCd = NewSubjectMatter("CI-CD") smStatistics = NewSubjectMatter("Statistics") smTopology = NewSubjectMatter("Topology") @@ -101,6 +106,7 @@ var ( smCloudComputing = NewSubjectMatter("Cloud Computing") smObservability = NewSubjectMatter("Observability") smServerless = NewSubjectMatter("Serverless") + smDatabase = NewSubjectMatter("Serverless") smLogging = NewSubjectMatter("Logging") smFirstAid = NewSubjectMatter("First Aid") smScripting = NewSubjectMatter("Scripting") @@ -111,6 +117,7 @@ var ( smDocumentation = NewSubjectMatter("Documentation") smGeospatial = NewSubjectMatter("Geospatial") smCommunication = NewSubjectMatter("Communication") + smCaching = NewSubjectMatter("Caching") ) func setupSubjectMatters() { diff --git a/generator/tags.go b/generator/tags.go deleted file mode 100644 index 63763ca..0000000 --- a/generator/tags.go +++ /dev/null @@ -1,53 +0,0 @@ -package main - -import "appli.ng/cv/generator/utils" - -type Tag string - -var tags = utils.Set[Tag]{} - -func NewTag(name string) Tag { - out := Tag(name) - tags.Add(out) - return out -} - -var ( - tagRobotics = NewTag("Robotics") - tagCryptocurrency = NewTag("Cryptocurrency") // TODO: USE SOMEWHERE - tagClusterComputing = NewTag("Cluster Computing") - tagDistributedComputing = NewTag("Distributed Computing") - tagContainerization = NewTag("Containerization") - tagIAC = NewTag("Infrastructure As Code") - tagDevOps = NewTag("DevOps") - tagCiCd = NewTag("CI-CD") - tagStatistics = NewTag("Statistics") - tagTopology = NewTag("Topology") - tagParticlePhysics = NewTag("Particle Physics") - tagNuclearEngineering = NewTag("Nuclear Engineering") - tagCivilEngineering = NewTag("Civil Engineering") - tagStructuralEngineering = NewTag("Structural Engineering") - tagThermodynamics = NewTag("Thermodynamics") - tagFluidMechanics = NewTag("Fluid Mechanics") - tagCybersecurity = NewTag("Cybersecurity") - tagCryptography = NewTag("Cryptography") - tagEmbeddedSystems = NewTag("Embedded Systems") - tagElectronics = NewTag("Electronics") - tagAI = NewTag("AI") - tagLinearAlgebra = NewTag("Linear Algebra") - tagGraphics = NewTag("Graphics") - tagGPU = NewTag("GPU") - tagMycology = NewTag("Mycology") - tagFrontend = NewTag("Frontend") - tagBackend = NewTag("Backend") - tagFullStack = NewTag("Full Stack") - tagCloudComputing = NewTag("Cloud Computing") - tagObservability = NewTag("Observability") - tagServerless = NewTag("Serverless") - tagLogging = NewTag("Logging") - tagFirstAid = NewTag("First Aid") - tagScripting = NewTag("Scripting") - tagEducation = NewTag("Education") - tagStatics = NewTag("Statics") - tagNetworking = NewTag("Networking") // TODO: maybe get rid of -) diff --git a/generator/tags/tags.go b/generator/tags/tags.go new file mode 100644 index 0000000..8e1ab94 --- /dev/null +++ b/generator/tags/tags.go @@ -0,0 +1,72 @@ +package tags + +import "appli.ng/cv/generator/utils" + +type Tag string + +var tags = utils.Set[Tag]{} + +func NewTag(name string) Tag { + out := Tag(name) + tags.Add(out) + return out +} + +type Field []Tag + +func (tags Field) AsStrings() []string { + out := make([]string, len(tags)) + for i, tag := range tags { + out[i] = string(tag) + } + return out +} + +var ( + AgTech = NewTag("AgTech") + Consulting = NewTag("Consulting") + Robotics = NewTag("Robotics") + Cryptocurrency = NewTag("Cryptocurrency") // TODO: USE SOMEWHERE + ClusterComputing = NewTag("Cluster Computing") + DistributedComputing = NewTag("Distributed Computing") + Containerization = NewTag("Containerization") + IAC = NewTag("Infrastructure As Code") + DevOps = NewTag("DevOps") + CiCd = NewTag("CI-CD") + Statistics = NewTag("Statistics") + Topology = NewTag("Topology") + ParticlePhysics = NewTag("Particle Physics") + NuclearEngineering = NewTag("Nuclear Engineering") + CivilEngineering = NewTag("Civil Engineering") + StructuralEngineering = NewTag("Structural Engineering") + Telecom = NewTag("Telecommunications") + SWE = NewTag("Software Engineering") + Thermodynamics = NewTag("Thermodynamics") + FluidMechanics = NewTag("Fluid Mechanics") + Cybersecurity = NewTag("Cybersecurity") + Cryptography = NewTag("Cryptography") + EmbeddedSystems = NewTag("Embedded Systems") + Electronics = NewTag("Electronics") + AI = NewTag("AI") + LinearAlgebra = NewTag("Linear Algebra") + Graphics = NewTag("Graphics") + GPU = NewTag("GPU") + Mycology = NewTag("Mycology") + Frontend = NewTag("Frontend") + Backend = NewTag("Backend") + FullStack = NewTag("Full Stack") + CloudComputing = NewTag("Cloud Computing") + Observability = NewTag("Observability") + Serverless = NewTag("Serverless") + Logging = NewTag("Logging") + FirstAid = NewTag("First Aid") + Scripting = NewTag("Scripting") + Statics = NewTag("Statics") + Networking = NewTag("Networking") // TODO: maybe get rid of + Charity = NewTag("Charity") + Entertainment = NewTag("Entertainment") + Education = NewTag("Education") + Construction = NewTag("Construction") + PublicHealth = NewTag("Public Health") + Ecology = NewTag("Ecology") +) diff --git a/generator/technology.go b/generator/technology.go index 0525f61..2d53c55 100644 --- a/generator/technology.go +++ b/generator/technology.go @@ -13,11 +13,15 @@ type TechologyPage struct { // React, Github actions, etc SubjectMatters utils.Set[SubjectMatter] } -func (pg *TechologyPage) Link() string { +func (pg *TechologyPage) Dst() string { + return dstFor("cv", "technology", withoutSpaces(pg.Name)) +} + +func (pg *TechologyPage) Title() string { if pg == nil { - return "NO_LINK" + return noLinkText } - return linkFor(pg.Name, "cv", "technology", withoutSpaces(pg.Name)) + return pg.Name } func (pg *TechologyPage) EntryType() string { @@ -97,15 +101,20 @@ func setupTechSubjectMatters() { NewTechnology("SIMULATE3", smNuclearEngineering, smParticlePhysics) NewTechnology("CASMO4e", smNuclearEngineering, smParticlePhysics) NewTechnology("Pub-Sub", smBackend, smNetworking) - NewTechnology("Git", smFullStack, smDevOps) + NewTechnology("Git", smFullStack, smDevSecOps) NewTechnology("Webhooks", smFullStack, smNetworking) NewTechnology("JQuery", smFrontend) NewTechnology("Arduino", smEmbeddedSystems, smElectronics, smRobotics) NewTechnology("PWM", smEmbeddedSystems, smElectronics, smRobotics) NewTechnology("G and M codes", smElectronics, smRobotics) - NewTechnology("OpenApi", smDocumentation).WithTags("API") // TODO: USE - NewTechnology("Swagger", smDocumentation).WithTags("API") // TODO: USE + NewTechnology("OpenApi", smDocumentation).WithTags("API") + NewTechnology("Swagger", smDocumentation).WithTags("API") NewTechnology("Spring", smBackend) + //NewTechnology("Spark", smBackend) // TODO: apache arrow + //NewTechnology("Arrow", smBackend) // TODO: apache arrow + //NewTechnology("ORC", smBackend) // TODO: apache ORC + NewTechnology("Kafka", smBackend) // TODO: event driven? + // } // TODO: list all backlinks???? diff --git a/scripts/changeTimestamps/main.go b/scripts/changeTimestamps/main.go new file mode 100644 index 0000000..5c5dd50 --- /dev/null +++ b/scripts/changeTimestamps/main.go @@ -0,0 +1,61 @@ +package changeTimestamps + +import ( + "flag" + "fmt" + "io/fs" + "log" + "os" + "path/filepath" + "strconv" + "strings" + "time" +) + +func main() { + endPath := flag.String("path", "", "directory path to change timestamps within") + dateStr := flag.String("date", "03-21-2026", "date to use for all files") + flag.Parse() + if endPath == nil || *endPath == "" { + panic("path is required") + } + dateStrs := strings.Split(*dateStr, "-") + if len(dateStrs) != 3 { + panic("date format is invalid. Must be MM-DD-YYYY") + } + mo, err := strconv.ParseInt(dateStrs[0], 10, 8) + if err != nil { + panic("month must be an integer") + } + dy, err := strconv.ParseInt(dateStrs[1], 10, 8) + if err != nil { + panic("day must be an integer") + } + yr, err := strconv.ParseInt(dateStrs[2], 10, 64) + if err != nil { + panic("year must be an integer") + } + modificationTime := time.Date(int(yr), time.Month(mo), int(dy), 0, 0, 0, 0, time.UTC) + + wd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + root := wd + "/" + *endPath // TODO: ensure ok + err = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { + if err != nil { + fmt.Printf("Error accessing path %s: %v\n", path, err) + return err + } + + if d.IsDir() { + return nil + } + // Process the file. Change the access and modification times + return os.Chtimes(path, modificationTime, modificationTime) // TODO: ensure this also changes creation time + }) + if err != nil { + log.Fatalf("Error walking the path %s: %v\n", root, err) + } + +}