git.fiddlerwoaroof.com
Raw Blame History
(in-package :cl-user)

(defpackage :resume-dl
  (:use :cl :anaphora :alexandria :serapeum :fw.lu))
(in-package :resume-dl)

(defclass section ()
  ((subsections :initarg :subsections :reader subsections)))
(defclass subsection ()
  ((items :initarg :items :reader items)))

(defclass address ()
  ((street :initarg :street :reader street)
   (city :initarg :city :reader city)
   (state :initarg :state :reader state)
   (zipcode :initarg :zipcode :reader zipcode)))

(defun make-address (&rest r &key street city state zipcode)
  (declare (ignore street city state zipcode))
  (apply #'make-instance 'address r))

(defclass person ()
  ((name :initarg :name :reader name)
   (email :initarg :email :reader email)
   (phone :initarg :phone :reader phone)
   (address :initarg :address :reader address)))

(defun make-person (&rest r &key name email phone address)
  (declare (ignore name email phone))
  (apply #'make-instance 'person (list* :address (apply #'make-address address)
					r)))

(defclass skill-section (section)
  ())

(defclass skill ()
  ((description :initarg :description :reader description)))

(defmacro make-skills (&body r)
  `(make-instance 'skill-section
		  :subsections (list ,@(loop for (key value) in r
					  when (eq key :skill)
					  collect `(make-instance 'skill :description ,value)))))

(defclass technology-section (section)
  ())

(defclass technology ()
  ((name :initarg :name :reader name)
   (subordinates :initarg :subordinates :reader subordinates)))

(defmacro make-technologies (&rest r)
  `(labels ((:technology (name &rest subordinates)
	      (make-instance 'technology :name name :subordinates (copy-seq subordinates)))
	    (:group (&rest technologies)
	      (make-instance 'subsection
			     :items (copy-seq technologies))))
     (make-instance 'technology-section
		    :subsections (list ,@(loop for (key . value) in r
					    collect `(,key ,@value))))))

(defclass employment-section (section)
  ())

(defclass employer ()
  ((name :initarg :name :reader name)
   (location :initarg :location :reader location)
   (dates :initarg :dates :reader dates)
   (title :initarg :title :reader title)
   (description :initarg :description :reader description)))

(defmacro make-employment (&rest r)
  `(labels ((:employer (name &rest r &key location dates title description)
	      (declare (ignore location dates title description))
	      (apply #'make-instance 'employer :name name r)))
     (make-instance 'employment-section
		    :subsections (list ,@(loop for (key . value) in r
					    collect `(,key ,@value))))))

(defclass education-section (section)
  ())

(defclass school ()
  ((name :initarg :name :reader name)
   (location :initarg :location :reader location)
   (degree :initarg :degree :reader degree)
   (graduation-date :initarg :graduation-date :reader graduation-date)
   (gpa :initarg :gpa :reader gpa)))

(defmacro make-education (&rest r)
  `(labels ((:school (name &rest r &key location degree graduation-date gpa)
	      (declare (ignore location degree graduation-date gpa))
	      (apply #'make-instance 'school :name name r)))
     (make-instance 'education-section
		    :subsections (list ,@(loop for (key . value) in r
					    collect `(,key ,@value))))))

(defclass resume ()
  ((person :initarg :person :reader person)
   (sections :initarg :sections :reader sections)
   (skills :initarg :skills :reader skills)
   (technologies :initarg :technologies :reader technologies)
   (employment :initarg :employment :reader employment)
   (education :initarg :education :reader education)))

(defun make-resume (&rest r &key person skills technologies employment education)
  (declare (ignore person skills technologies employment education))
  (apply #'make-instance 'resume r))

(defmacro define-resume (place &body body)
  (let ((person (assoc :person body))
	(skills (assoc :skills body))
	(technologies (assoc :technologies body))
	(employment (assoc :employment body))
	(education (assoc :education body)))
    (let ((definer (etypecase place
		     (symbol 'defparameter)
		     (list 'setf))))
      `(,definer ,place
	   (make-resume ,@(when person
			    `(:person (make-person ,@(cdr person))))
			,@(when skills
			    `(:skills (make-skills ,@(cdr skills))))
			,@(when technologies
			    `(:technologies (make-technologies ,@(cdr technologies))))
			,@(when employment
			    `(:employment (make-employment ,@(cdr employment))))
			,@(when education
			    `(:education (make-education ,@(cdr education)))))))))



(defgeneric format-object (object)
  (:documentation "Format an object"))

(defgeneric format-section (root section)
  (:documentation "Format a section"))

(defparameter *output-stream* (make-synonym-stream '*standard-output*))
(defgeneric format-subsection (root section subsection)
  (:documentation "Format a subsection"))

(defgeneric format-item (root section subsection item)
  (:documentation "Format an item"))

(defclass font ()
  ((basename :initarg :basename :reader basename :initform (error "need a basename"))
   (role :initarg :role :reader font-role :initform "main")
   (extension :initarg :extension :reader extension :initform "otf")
   (formats :initarg :formats :reader formats :initform '(:bold :italic :bold-italic :small-cap))
   (regular :initarg :regular :reader regular)
   (bold :initarg :bold :reader bold)
   (italic :initarg :italic :reader italic)
   (bold-italic :initarg :bold-italic :reader bold-italic)
   (small-cap :initarg :small-cap :reader small-cap)))

(defmethod initialize-instance :after ((instance font)
				       &key
					 (regular (string-join (list (basename instance)
								  "-Regular")))
					 (bold (string-join (list (basename instance)
								  "-Bold")))
					 (italic (string-join (list (basename instance)
								    "-Italic")))
					 (bold-italic (string-join (list (basename instance)
									"-BoldItalic")))
					 (small-cap (string-join (list (basename instance)
								       "-SmallCaps"))))
  (setf (slot-value instance 'bold) bold
	(slot-value instance 'regular) regular
	(slot-value instance 'italic) italic
	(slot-value instance 'bold-italic) bold-italic
	(slot-value instance 'small-cap) small-cap))

(defgeneric get-font-spec (font kind)
  (:method :around ((font font) kind)
    (format nil "~a.~a"
	    (call-next-method)
	    (extension font)))
  (:method ((font font) (kind (eql :regular)))
    (regular font))
  (:method ((font font) (kind (eql :bold)))
    (bold font))
  (:method ((font font) (kind (eql :italic)))
    (italic font))
  (:method ((font font) (kind (eql :bold-italic)))
    (bold-italic font))
  (:method ((font font) (kind (eql :small-cap)))
    (small-cap font)))

(defmethod format-object ((font font))
  (format *output-stream* "~&\\set~afont{~a}~@[[~%~{~4t~{~a~^=~}~^,~%~}~%]~]~%"
	  (font-role font)
	  (get-font-spec font :regular)
	  (loop for format in (formats font)
	     collect (list (ecase format
			     (:bold "BoldFont")
			     (:italic "ItalicFont")
			     (:bold-italic "BoldItalicFont")
			     (:small-cap "SmallCapsFont"))
			   (get-font-spec font format)))))


(defclass latex-doc ()
  ((body :initarg :body :reader body)))

(defgeneric document-class (latex-doc)
  (:method ((latex-doc latex-doc))
    (values "res"
	    (list "margin"))))

(defgeneric packages (latex-doc)
  (:method ((latex-doc latex-doc))
    (list "polyglossia"
	  "fontspec"
	  "url"
	  "hyperref"
	  "hyphenat")))

(defgeneric main-font (doc)
  (:method ((doc latex-doc))
    (make-instance 'font
		   :basename "Alegreya"
		   :small-cap "AlegreyaSC-Bold")))

(defgeneric sans-font (doc)
  (:method ((doc latex-doc))
    (make-instance 'font
		   :role "sans"
		   :basename "AlegreyaSans"
		   :small-cap "AlegreyaSansSC-Bold")))

(defgeneric mono-font (doc)
  (:method ((doc latex-doc))
    (make-instance 'font
		   :role "mono"
		   :formats '()
		   :basename "SourceCodePro"
		   :small-cap "AlegreyaSansSC-Bold")))

(defgeneric document-language (doc)
  (:method ((doc latex-doc))
    "english"))

(defgeneric header (doc)
  (:method ((doc latex-doc))
    "\\textheight=700pt
\\parskip=8pt"))

(defmethod format-object ((doc latex-doc))
  (multiple-value-bind (doc-class class-options) (document-class doc)
    (format *output-stream* "~&\\documentclass[~{~a~^,~}]{~a}~2&~{\\usepackage{~a}~%~}~2&\\setdefaultlanguage{~a}"
	    class-options doc-class
	    (packages doc)
	    (document-language doc)))
  (format-object (main-font doc))
  (format-object (sans-font doc))
  (format-object (mono-font doc))
  (format *output-stream* "~2&~a~2&\\begin{document}~%"
	  (header doc))
  (format-object (body doc))
  (format *output-stream* "~&\\end{document}~%"))

(defmethod format-object ((object resume))
  (fresh-line *output-stream*)
  (format-section object (person object))
  (fresh-line *output-stream*)
  (format *output-stream* "\\begin{resume}~%")
  (terpri *output-stream*)
  (format-section object (skills object))
  (fresh-line *output-stream*)
  (terpri *output-stream*)
  (format-section object (technologies object))
  (fresh-line *output-stream*)
  (terpri *output-stream*)
  (format-section object (employment object))
  (fresh-line *output-stream*)
  (terpri *output-stream*)
  (format-section object (education object))
  (fresh-line *output-stream*)
  (format *output-stream* "\\end{resume}~%"))

(defmethod format-section :after ((object resume) section)
  (format *output-stream* "\\bigskip{}"))

(defmethod format-section :before ((object resume) (section education-section))
  (format *output-stream* "\\vfill{}"))

(defmethod format-section ((object resume) (person person))
  (format *output-stream* "~&\\name{\\Huge{\\textsc{~a}}}
\\address{\\textbf{Email:} ~a \\\\ \\textbf{Phone:} ~a}~%"
	  (name person)
	  (email person)
	  (phone person))
  (format-subsection object person (address person))
  (fresh-line *output-stream*))

(defmethod format-subsection ((object resume) (person person) (address address))
  (let ((street (street address)))
    (format *output-stream* "~&\\address{~a ~a ~a \\\\ ~a, ~a ~a}~%"
	    (getf street :number)
	    (getf street :street)
	    (string-replace-all "#" "\\#" (getf street :apartment))
	    (city address)
	    (state address)
	    (zipcode address))))

(defmethod format-section ((object resume) (section skill-section))
  (format *output-stream* "~&\\section{\\textbf{\\textsc{Skills}}}~2%")
  (mapcar (op (format-subsection object section _)
	      (fresh-line *output-stream*)
	      (terpri *output-stream*))
	  (subsections section)))

(defmethod format-subsection ((object resume) (section skill-section) (subsection skill))
  (format *output-stream* "~&\\par ~a~%" (description subsection)))


(defmethod format-section ((object resume) (section technology-section))
  (format *output-stream* "\\section{\\textbf{\\textsc{Technologies}}}~2%")
  (mapcar (op (format-subsection object section _))
	  (subsections section)))

(defmethod format-subsection ((object resume) (section technology-section) (subsection subsection))
  (declare (optimize (debug 3)))
  (format *output-stream* "~&\\par ")
  (loop for (technology . rest) on (items subsection)
     do (format-item object section subsection technology)
     when rest
     do (princ ", " *output-stream*))
  (fresh-line *output-stream*)
  (terpri *output-stream*))

(defmethod format-item ((object resume) (section technology-section) subsection (item technology))
  (princ (name item) *output-stream*)
  (when (subordinates item)
    (format *output-stream* " (~{~a~^, ~})" (subordinates item))))

(defmethod format-section ((object resume) (section employment-section))
  (format *output-stream* "~&\\section{\\textbf{\\textsc{Experience}}}~2%")
  (loop for subsection in (subsections section)
       do (format-subsection object section subsection)))

(defmethod format-subsection ((object resume) (section employment-section) (subsection employer))
  (flet ((:formatter (format &rest args) (apply #'format *output-stream* format args)))
    (with-accessors #.(mapcar (op (list _1 _1)) '(name location dates title description)) subsection
      (:formatter "~&\\employer{~a}~%" name)
      ;(:formatter "~&\\location{~a}~%" location)
      (typecase dates
	(cons (:formatter "~&\\dates{~a (~a~@[--~a~])}~%" location (car dates)
			  (case (cdr dates)
			    (:now "Present")
			    (t (cdr dates)))))
	(integer (:formatter "~&\\dates{~a, (~a)}~%" location dates)))
      ;(:formatter "~&\\title{\\textbf{~a}}~%" title)
      (:formatter "~&\\begin{position}~%~a~&\\end{position}~%" description))))

(defmethod format-section ((object resume) (section education-section))
  (format *output-stream* "\\section{\\textbf{\\textsc{Education}}}~2%")
  (loop for subsection in (subsections section)
       do (format-subsection object section subsection)))

(defmethod format-subsection ((object resume) (section education-section) (subsection school))
  (with-accessors* (name location degree graduation-date) subsection
    (format *output-stream* "~a, ~a\\hfill{\\sl ~a}, ~a, ~a\\\\~%"
	    name location
	    (car degree)
	    (cdr degree)
	    graduation-date)))

(define-resume *resume*
  (:person :name "Edward Langley"
	   :email "edward@elangley.org"
	   :phone "(832) 585-2660"
	   :address '(:street (:number "3090" :street "Channel Dr." :apartment "#210")
		      :city "Ventura"
		      :state "CA"
		      :zipcode "93003"))
  (:skills (:skill "Designing and implementing applications using a variety of programming paradigms and languages.")
	   (:skill "Developing web applications using a variety of server-side and client-side frameworks")
	   (:skill "Deploying and maintaining web applications on nginx using FastCGI")
	   (:skill "Administering and configuring Linux, FreeBSD and Solaris"))
  (:technologies (:group (:technology "Haskell")
			 (:technology "Clojure")
			 (:technology "SQL")
			 (:technology "Python")
			 (:technology "Common Lisp")
			 (:technology "Javascript")
			 (:technology "PHP")
			 (:technology "Postscript"))
		 (:group (:technology "HTML")
			 (:technology "CSS"
				      "Sass"
				      "Bootstrap"
				      "Susy grids")
			 (:technology "VueJS")
			 (:technology "Apache")
			 (:technology "Nginx")
			 (:technology "PHP-FPM")
			 (:technology "uWSGI"))
		 (:group (:technology "MySQL")
			 (:technology "PostgreSQL")
			 (:technology "Redis")
			 (:technology "BerkeleyDB"))
		 (:group (:technology "Linux")
			 (:technology "FreeBSD")
			 (:technology "Solaris")
			 (:technology "Mac OSX")
			 (:technology "AWS" "EC2" "S3" "RDS" "Route53")))
  (:employment (:employer "Independent Consultant"
			  :location "Ventura, CA"
			  :dates '(2014 . :now)
			  :title "Programmer"
			  :description "Developed a web interface for managing a router including interfaces for
			  various system services such as the bro intrusion detection system.

		          Themed an e-commerce site, developed a deployment strategy and managed updates to the site.
                          Implemented and themed a new e-commerce platform.

                          Developed a tool to track my time and automatically upload it to my invoicing service.")
	       (:employer "NCMI, Baylor College of Medicine"
			  :location "Houston, TX"
			  :dates '(2007 . 2012)
			  :title "Python Programmer"
			  :description "Built a web framework for EMEN2, a document-oriented database for recording
		       scientific experiments.

		       Improved EMEN2's reliability and performance by debugging and fixing memory leaks and taking
		       advantage of Berkeley DB's transactional storage.

		       Managed the migration of another lab's data from EMEN1 to EMEN2 and built a conversion tool
                       that allowed the user to add new rules for correcting invalid data without restarting the
                       conversion process.")
	       (:employer "Desktop Assistance, L.P."
			  :location "Houston, TX"
			  :dates 2004
			  :title "IT Assistant"
			  :description "Installed and configured Linux desktops and serves. Build a Fedora derivative
		       for internal use. Researched, installed and configured a variety of services including:
		       databases, Apache, Squid, Samba and NFS."))
  (:education (:school "Thomas Aquinas College"
		       :location "Santa Paula, CA"
		       :degree '("B.A." . "Liberal Arts")
		       :graduation-date "2012"
		       :gpa 3.96)
	      (:school "The Catholic University of America"
		       :location "Washington, DC"
		       :degree '("Ph.D student" . "Philosophy")
		       :graduation-date "2015"
		       :gpa 3.84)))