resume/generate.scm

183 lines
7.4 KiB
Scheme
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

;;; generate.scm - Walk resume data and produce HTML via SXML
;;; Run with: guile generate.scm > resume.html
(use-modules (sxml simple))
(load "resume.scm")
;; ─── Utility ──────────────────────────────────────────────────────────────────
(define (bullets items)
`(ul ,@(map (lambda (s) `(li ,s)) items)))
(define (date-range start end)
`(span (@ (class "date-range")) ,start " " ,end))
(define (tech-list techs)
`(ul (@ (class "tech-list"))
,@(map (lambda (t) `(li (@ (class "tech-tag")) ,t)) techs)))
;; ─── Individual cards (all become flat children of .columns) ──────────────────
;; Project card — detail bullets revealed on hover (desktop) or click (mobile)
(define (render-project proj)
`(div (@ (class "project-card")
(onclick "this.classList.toggle('open')"))
(div (@ (class "project-header"))
(span (@ (class "project-name")) ,(field proj 'name))
,(date-range (field proj 'date-start) (field proj 'date-end)))
(div (@ (class "project-summary"))
,(bullets (field proj 'summary-bullets)))
(div (@ (class "project-details"))
,(bullets (field proj 'detail-bullets)))
,(tech-list (field proj 'technologies))))
;; Title card: blurb only, no projects nested inside.
;; Projects are emitted as siblings after the title card.
(define (render-title-card tc)
`(div (@ (class "title-card"))
(div (@ (class "title-header"))
(span (@ (class "title-name")) ,(field tc 'title))
,(date-range (field tc 'date-start) (field tc 'date-end)))
(div (@ (class "title-meta"))
,(field tc 'company) " • " ,(field tc 'location))
(div (@ (class "title-blurb"))
,(bullets (field tc 'blurb)))))
;; Emit a title card followed by its project cards as a flat list of siblings
(define (render-title-flat tc)
(append
(list (render-title-card tc))
(map render-project (field tc 'projects))))
(define (render-education edu)
`(div (@ (class "education-card")
(onclick "this.classList.toggle('open')"))
(div (@ (class "edu-header"))
(span (@ (class "edu-degree")) ,(field edu 'degree))
,(date-range (field edu 'date-start) (field edu 'date-end)))
(div (@ (class "edu-meta"))
,(field edu 'institution) " • " ,(field edu 'location))
(div (@ (class "edu-bullets"))
,(bullets (field edu 'bullets)))
(div (@ (class "edu-extras"))
,(bullets (field edu 'extracurriculars)))))
(define (render-interest item)
`(div (@ (class "interest-card"))
(span (@ (class "interest-title")) ,(field item 'title))
(span (@ (class "interest-description")) ,(field item 'description))))
(define (render-interests sec)
`(div (@ (class "interests"))
,@(map render-interest (field sec 'items))))
(define (render-favorite-software sec)
`(div (@ (class "favorite-software"))
,@(map (lambda (s) `(span (@ (class "sw-item")) ,s))
(field sec 'items))))
;; ─── Section → flat list of column children ───────────────────────────────────
(define (section->items sec)
(case (car sec)
((job)
(apply append (map render-title-flat (field sec 'titles))))
((education)
(list (render-education sec)))
((hobby-projects)
(map render-project (field sec 'projects)))
((interests)
(list (render-interests sec)))
((favorite-software)
(list (render-favorite-software sec)))
(else '())))
(define (section-label sec)
(case (car sec)
((job) "Work Experience")
((education) "Education")
((hobby-projects) "Projects")
((interests) "Interests")
((favorite-software) "Favorite Software")
(else "")))
;; Group consecutive sections of same type, emit one heading per group
(define (sections->flat-items sections)
(if (null? sections) '()
(let loop ((rest (cdr sections))
(cur-type (caar sections))
(cur-label (section-label (car sections)))
(cur-items (section->items (car sections)))
(acc '()))
(cond
((null? rest)
;; flush the last group — acc is already in forward order
(append acc
(list `(h2 (@ (class "sec-heading")) ,cur-label))
cur-items))
((eq? (caar rest) cur-type)
;; same section type: merge items under one heading
(loop (cdr rest) cur-type cur-label
(append cur-items (section->items (car rest)))
acc))
(else
;; new section type: flush current group into acc, start new
(loop (cdr rest)
(caar rest)
(section-label (car rest))
(section->items (car rest))
(append acc
(list `(h2 (@ (class "sec-heading")) ,cur-label))
cur-items)))))))
;; ─── Front matter ─────────────────────────────────────────────────────────────
(define (render-front-matter fm)
`(header (@ (class "front-matter"))
(h1 (@ (class "fm-name")) ,(field fm 'name))
(div (@ (class "fm-contact"))
(span ,(field fm 'location))
(span ,(field fm 'email)))))
;; ─── Document ─────────────────────────────────────────────────────────────────
(define (render-resume res)
(let* ((fm (field res 'front-matter))
(items (sections->flat-items (field res 'sections))))
`(*TOP*
(html (@ (lang "en"))
(head
(meta (@ (charset "UTF-8")))
(meta (@ (name "viewport") (content "width=device-width, initial-scale=1")))
(title ,(field fm 'name))
(link (@ (rel "stylesheet") (href "style.css")))
;; Measure the front-matter height after every resize/zoom and
;; set .columns height to exactly fill the remaining space.
(script "
function setColHeight() {
var fm = document.querySelector('.front-matter');
var col = document.querySelector('.columns');
if (!fm || !col) return;
var used = fm.getBoundingClientRect().height
+ parseInt(getComputedStyle(document.body).paddingTop) * 2;
col.style.height = (window.innerHeight - used) + 'px';
}
window.addEventListener('load', setColHeight);
window.addEventListener('resize', setColHeight);
// zoom via ctrl+/- fires resize in most browsers, but also poll as fallback
var _lastW = window.innerWidth, _lastH = window.innerHeight;
setInterval(function() {
if (window.innerWidth !== _lastW || window.innerHeight !== _lastH) {
_lastW = window.innerWidth; _lastH = window.innerHeight;
setColHeight();
}
}, 200);
"))
(body
,(render-front-matter fm)
(main (@ (class "columns"))
,@items))))))
(display "<!DOCTYPE html>\n")
(sxml->xml (render-resume resume))
(newline)