Repository: williamsmj/resume.md
Branch: main
Commit: 92afc0159ffa
Files: 10
Total size: 25.5 KB
Directory structure:
gitextract_zx4oaoa_/
├── .github/
│ └── workflows/
│ ├── build.yml
│ └── release.yml
├── LICENSE
├── README.md
├── example/
│ └── resume.html
├── pyproject.toml
└── src/
└── resume_markdown/
├── __init__.py
├── __main__.py
├── resume.css
└── resume.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/build.yml
================================================
name: build
on:
workflow_dispatch:
pull_request:
push:
branches: [main]
paths-ignore:
- 'example/**'
jobs:
build:
if: github.repository == 'mikepqr/resume-markdown'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
# os: [ubuntu-latest, macos-latest, windows-latest]
os: [ubuntu-latest, windows-latest]
steps:
- name: Check out repo
uses: actions/checkout@v4
- name: Set up uv
uses: astral-sh/setup-uv@v5
- name: Install package
run: uv tool install .
- name: Initialize resume templates
run: resume-markdown init
- name: Install mscorefonts (Linux)
if: runner.os == 'Linux'
run: |
echo "ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true" | sudo debconf-set-selections
sudo apt-get update
sudo apt-get install -y --no-install-recommends ttf-mscorefonts-installer fontconfig
sudo fc-cache -f -v
- name: Make resume
run: resume-markdown --debug build
- name: Rename output (Unix)
if: runner.os != 'Windows'
shell: bash
run: |
mv resume.pdf resume_${{ runner.os }}.pdf
mv resume.html resume_${{ runner.os }}.html
- name: Rename output (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
Move-Item resume.pdf resume_Windows.pdf
Move-Item resume.html resume_Windows.html
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: resume-${{ runner.os }}
path: |
*.pdf
*.html
update-examples:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
needs: build
steps:
- name: Check out repo
uses: actions/checkout@v4
- name: Install ImageMagick
run: sudo apt-get update && sudo apt-get install -y imagemagick
- name: Download Windows artifacts
uses: actions/download-artifact@v4
with:
name: resume-Windows
path: artifacts
- name: Update example files
run: |
mv artifacts/resume_Windows.pdf example/resume.pdf
mv artifacts/resume_Windows.html example/resume.html
convert -density 300 example/resume.pdf[0] -background white -alpha remove \
-crop 2550x1176+0+0 +repage \
\( -size 2550x196 gradient:none-white \) \
-gravity South -composite \
-resize 1300x600 example/resume.png
- name: Commit changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add example/resume.pdf example/resume.html example/resume.png
git diff --staged --quiet || git commit -m "Update example outputs"
git push
================================================
FILE: .github/workflows/release.yml
================================================
name: Publish to PyPI
on:
release:
types: [published]
jobs:
build:
name: Build distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"
- name: Install build dependencies
run: python -m pip install --upgrade build
- name: Build package
run: python -m build
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: release-dists
path: dist/
publish:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: build
environment:
name: pypi
url: https://pypi.org/p/resume-markdown
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
name: release-dists
path: dist/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2020 Mike Lee Williams
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# resume-markdown

Write your resume in
[Markdown](https://raw.githubusercontent.com/mikepqr/resume-markdown/main/src/resume_markdown/resume.md),
style it with [CSS](src/resume_markdown/resume.css), output to [`resume.html`](example/resume.html) and
[`resume.pdf`](example/resume.pdf).
## Prerequisites
- Python ≥ 3.9 or `uv`
- Optional, required for PDF output: Google Chrome or Chromium
## Installation
### Using uv
Run directly without installing:
```bash
uvx resume-markdown
```
Or install once:
```bash
uv tool install resume-markdown
```
### Using pip
```bash
pip install resume-markdown
```
## Usage
### Quick start
1. Create template files in your current directory:
```bash
resume-markdown init
# or with uvx: uvx resume-markdown init
```
This creates [`resume.md`](src/resume_markdown/resume.md) and [`resume.css`](src/resume_markdown/resume.css) in the current directory.
2. Edit your copy of `resume.md` with your resume content (the placeholder text is taken
with thanks from the [JSON Resume Project](https://jsonresume.org/themes/))
3. Build HTML and PDF output:
```bash
resume-markdown build
# or with uvx: uvx resume-markdown build
```
### Build options
- Use `--no-html` or `--no-pdf` to disable HTML or PDF output:
```bash
resume-markdown build --no-pdf
```
- Use `--chrome-path=/path/to/chrome` if the tool cannot find your Chrome
or Chromium executable (needed for PDF output)
```bash
resume-markdown build --chrome-path=/path/to/chrome
```
- Specify a custom input file:
```bash
resume-markdown build myresume.md
```
## Customization
Edit [`resume.css`](src/resume_markdown/resume.css) to change the appearance of your resume. The
default style is extremely generic, which is perhaps what you want in a resume,
but CSS gives you a lot of flexibility. See, e.g. [The Tech Resume
Inside-Out](https://www.thetechinterview.com/) for good advice about what a
resume should look like (and what it should say).
Change the appearance of the PDF version (without affecting the HTML version) by
adding rules under the `@media print` CSS selector.
Change the margins and paper size of the PDF version by editing the [`@page` CSS
rule](https://developer.mozilla.org/en-US/docs/Web/CSS/%40page/size).
================================================
FILE: example/resume.html
================================================
Richard Hendricks
Richard Hendricks
CEO and Software Engineer with knowledge of applied information theory,
including optimizing lossless compression schema of both the length-limited and
adaptive variants.
Experience
CEO/President, Pied Piper Dec 2013 – Dec 2014
Pied Piper is a multi-platform technology based on a proprietary universal
compression algorithm that has consistently fielded high Weisman Scores™ that
are not merely competitive, but approach the theoretical limit of lossless
compression.
- Build an algorithm for artist to detect if their music was violating
copyright infringement laws
- Successfully won Techcrunch Disrupt
- Optimized an algorithm that holds the current world record for Weisman Scores
Teacher, CoderDojo July 2013 – Dec 2013
Global movement of free coding clubs for young people.
- Awarded ‘Teacher of the Month’
Projects
Miss Direction Aug 2016
A mapping engine that misguides you:
- Won award at AIHacks 2016
- Built by all women team of newbie programmers
- Using modern technologies such as GoogleMaps, Chrome Extension and Javascript
Education
University of Oklahoma, BA Information Technology 2011 – 2014
- GPA 4.0
- DB1101 - Basic SQL
- CS2011 - Java Introduction
Skills
- Web development: HTML, CSS, JavaScript
- Compression: Mpeg, MP4, GIF
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "resume-markdown"
version = "1.0.1"
description = "Convert Markdown resumes to HTML and PDF"
readme = "README.md"
license = {text = "MIT"}
authors = [
{name = "Mike Lee Williams", email = "mike@mike.place"}
]
requires-python = ">=3.9"
dependencies = [
"markdown>=3.0",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Text Processing :: Markup :: Markdown",
]
[project.urls]
Homepage = "https://github.com/mikepqr/resume-markdown"
Repository = "https://github.com/mikepqr/resume-markdown"
Issues = "https://github.com/mikepqr/resume-markdown/issues"
[project.scripts]
resume-markdown = "resume_markdown:main"
================================================
FILE: src/resume_markdown/__init__.py
================================================
"""Convert Markdown resumes to HTML and PDF."""
__version__ = "1.0.1"
from resume_markdown.__main__ import main
__all__ = ["main"]
================================================
FILE: src/resume_markdown/__main__.py
================================================
#!/usr/bin/env python3
import argparse
import base64
import itertools
import logging
import os
import re
import shutil
import subprocess
import sys
import tempfile
from importlib.resources import files
import markdown
preamble = """\
{title}
"""
postamble = """\
"""
CHROME_GUESSES_MACOS = (
"/Applications/Chromium.app/Contents/MacOS/Chromium",
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary",
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
)
# https://stackoverflow.com/a/40674915/409879
CHROME_GUESSES_WINDOWS = (
# Windows 10
os.path.expandvars(r"%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe"),
os.path.expandvars(r"%ProgramFiles%\Google\Chrome\Application\chrome.exe"),
os.path.expandvars(r"%LocalAppData%\Google\Chrome\Application\chrome.exe"),
# Windows 7
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
# Vista
r"C:\Users\UserName\AppDataLocal\Google\Chrome",
# XP
r"C:\Documents and Settings\UserName\Local Settings\Application Data\Google\Chrome",
)
# https://unix.stackexchange.com/a/439956/20079
CHROME_GUESSES_LINUX = [
"/".join((path, executable))
for path, executable in itertools.product(
(
"/usr/local/sbin",
"/usr/local/bin",
"/usr/sbin",
"/usr/bin",
"/sbin",
"/bin",
"/opt/google/chrome",
),
("google-chrome", "chrome", "chromium", "chromium-browser"),
)
]
def guess_chrome_path() -> str:
if sys.platform == "darwin":
guesses = CHROME_GUESSES_MACOS
elif sys.platform == "win32":
guesses = CHROME_GUESSES_WINDOWS
else:
guesses = CHROME_GUESSES_LINUX
for guess in guesses:
if os.path.exists(guess):
logging.info("Found Chrome or Chromium at " + guess)
return guess
raise ValueError("Could not find Chrome. Please set --chrome-path.")
def title(md: str) -> str:
"""
Return the contents of the first markdown heading in md, which we
assume to be the title of the document.
"""
for line in md.splitlines():
if re.match("^#[^#]", line): # starts with exactly one '#'
return line.lstrip("#").strip()
raise ValueError(
"Cannot find any lines that look like markdown h1 headings to use as the title"
)
def make_html(md: str, prefix: str = "resume") -> str:
"""
Compile md to HTML and prepend/append preamble/postamble.
Insert .css if it exists.
"""
try:
with open(prefix + ".css") as cssfp:
css = cssfp.read()
except FileNotFoundError:
print(prefix + ".css not found. Output will by unstyled.")
css = ""
return "".join(
(
preamble.format(title=title(md), css=css),
markdown.markdown(md, extensions=["smarty", "abbr"]),
postamble,
)
)
def init_resume(directory: str = ".") -> None:
"""
Write template resume.md and resume.css files to the specified directory.
"""
package_files = files("resume_markdown")
for filename in ["resume.md", "resume.css"]:
dest_path = os.path.join(directory, filename)
if os.path.exists(dest_path):
logging.warning(f"{dest_path} already exists, skipping")
continue
template_content = (package_files / filename).read_text(encoding="utf-8")
with open(dest_path, "w", encoding="utf-8") as f:
f.write(template_content)
logging.info(f"Wrote {dest_path}")
def write_pdf(html: str, prefix: str = "resume", chrome: str = "") -> None:
"""
Write html to prefix.pdf
"""
chrome = chrome or guess_chrome_path()
html64 = base64.b64encode(html.encode("utf-8"))
options = [
"--no-sandbox",
"--headless",
"--print-to-pdf-no-header",
# Keep both versions of this option for backwards compatibility
# https://developer.chrome.com/docs/chromium/new-headless.
"--no-pdf-header-footer",
"--enable-logging=stderr",
"--log-level=2",
"--in-process-gpu",
"--disable-gpu",
]
# Ideally we'd use tempfile.TemporaryDirectory here. We can't because
# attempts to delete the tmpdir fail on Windows because Chrome creates a
# file the python process does not have permission to delete. See
# https://github.com/puppeteer/puppeteer/issues/2778,
# https://github.com/puppeteer/puppeteer/issues/298, and
# https://bugs.python.org/issue26660. If we ever drop Python 3.9 support we
# can use TemporaryDirectory with ignore_cleanup_errors=True as a context
# manager.
tmpdir = tempfile.mkdtemp(prefix="resume.md_")
options.append(f"--crash-dumps-dir={tmpdir}")
options.append(f"--user-data-dir={tmpdir}")
try:
subprocess.run(
[
chrome,
*options,
f"--print-to-pdf={prefix}.pdf",
"data:text/html;base64," + html64.decode("utf-8"),
],
check=True,
)
logging.info(f"Wrote {prefix}.pdf")
except subprocess.CalledProcessError as exc:
if exc.returncode == -6:
logging.warning(
"Chrome died with "
f"but you may find {prefix}.pdf was created successfully."
)
else:
raise exc
finally:
shutil.rmtree(tmpdir, ignore_errors=True)
if os.path.isdir(tmpdir):
logging.debug(f"Could not delete {tmpdir}")
def main():
parser = argparse.ArgumentParser(
description="Convert Markdown resumes to HTML and PDF"
)
parser.add_argument("-q", "--quiet", action="store_true")
parser.add_argument("--debug", action="store_true")
subparsers = parser.add_subparsers(dest="command", help="Available commands")
# init command
init_parser = subparsers.add_parser(
"init",
help="Create resume.md and resume.css template files"
)
# build command
build_parser = subparsers.add_parser(
"build",
help="Build HTML and PDF from Markdown resume"
)
build_parser.add_argument(
"file",
help="markdown input file [resume.md]",
default="resume.md",
nargs="?",
)
build_parser.add_argument(
"--no-html",
help="Do not write html output",
action="store_true",
)
build_parser.add_argument(
"--no-pdf",
help="Do not write pdf output",
action="store_true",
)
build_parser.add_argument(
"--chrome-path",
help="Path to Chrome or Chromium executable",
)
args = parser.parse_args()
if args.quiet:
logging.basicConfig(level=logging.WARN, format="%(message)s")
elif args.debug:
logging.basicConfig(level=logging.DEBUG, format="%(message)s")
else:
logging.basicConfig(level=logging.INFO, format="%(message)s")
if args.command == "init":
init_resume()
elif args.command == "build":
prefix, _ = os.path.splitext(os.path.abspath(args.file))
with open(args.file, encoding="utf-8") as mdfp:
md = mdfp.read()
html = make_html(md, prefix=prefix)
if not args.no_html:
with open(prefix + ".html", "w", encoding="utf-8") as htmlfp:
htmlfp.write(html)
logging.info(f"Wrote {htmlfp.name}")
if not args.no_pdf:
write_pdf(html, prefix=prefix, chrome=args.chrome_path)
else:
parser.print_help()
if __name__ == "__main__":
main()
================================================
FILE: src/resume_markdown/resume.css
================================================
body {
color: #000000;
background: #EEEEEE;
font: 1.1em "Times New Roman";
line-height: 1.2;
margin: 40px 0;
}
#resume {
margin: 0 auto;
max-width: 800px;
padding: 40px 60px;
background: #FFFFFF;
border: 1px solid #CCCCCC;
box-shadow: 2px 2px 4px #AAAAAA;
-webkit-box-shadow: 2px 2px 4px #AAAAAA;
}
h1 {
text-transform: uppercase;
text-align: center;
font-size: 200%;
margin: 0;
padding: 0;
}
h2 {
border-bottom: 1px solid #000000;
text-transform: uppercase;
font-size: 130%;
margin: 1em 0 0 0;
padding: 0;
}
h3 {
font-size: 100%;
margin: 0.8em 0 0.3em 0;
padding: 0;
display: flex;
justify-content: space-between;
}
p {
margin: 0 0 0.5em 0;
padding: 0;
}
ul {
padding: 0;
margin: 0 1.5em;
}
/* ul immediately after h1 = contact list */
h1 + ul {
text-align: center;
margin: 0;
padding: 0;
}
h1 + ul > li {
display: inline;
white-space: pre;
list-style-type: none;
}
h1 + ul > li:after {
content: " \2022 ";
}
h1 + ul > li:last-child:after {
content: "";
}
/* p immediately after contact list = summary */
h1 + ul + p {
margin: 1em 0;
}
@media print {
body {
font-size: 10pt;
margin: 0;
padding: 0;
background: none;
}
#resume {
margin: 0;
padding: 0;
border: 0px;
background: none;
box-shadow: none;
-webkit-box-shadow: none;
}
/* Do not underline abbr tags in PDF */
abbr {
text-decoration: none;
font-variant: none;
}
/* Make links black in PDF */
/* Move this outside the print block to apply this in HTML too */
a, a:link, a:visited, a:hover {
color: #000000;
text-decoration: underline;
}
}
@page {
/* Change margins and paper size of PDF */
/* https://developer.mozilla.org/en-US/docs/Web/CSS/@page */
size: letter;
margin: 0.5in 0.8in;
}
@media screen and (max-width: 800px) {
body {
font-size: 16pt;
margin: 0;
padding: 0;
background: #FFFFFF !important;
}
#resume {
margin: 0;
padding: 1em;
border: 0px;
background: none;
box-shadow: none;
-webkit-box-shadow: none;
}
}
================================================
FILE: src/resume_markdown/resume.md
================================================
# Richard Hendricks
-
- (912) 555-4321
- [richardhendricks.example.com](http://richardhendricks.example.com)
- San Francisco, CA
CEO and Software Engineer with knowledge of applied information theory,
including optimizing lossless compression schema of both the length-limited and
adaptive variants.
## Experience
### CEO/President, Pied Piper Dec 2013 -- Dec 2014
Pied Piper is a multi-platform technology based on a proprietary universal
compression algorithm that has consistently fielded high Weisman Scores™ that
are not merely competitive, but approach the theoretical limit of lossless
compression.
- Build an algorithm for artist to detect if their music was violating
copyright infringement laws
- Successfully won Techcrunch Disrupt
- Optimized an algorithm that holds the current world record for Weisman Scores
### Teacher, CoderDojo July 2013 -- Dec 2013
Global movement of free coding clubs for young people.
- Awarded 'Teacher of the Month'
## Projects
### Miss Direction Aug 2016
A mapping engine that misguides you:
- Won award at AIHacks 2016
- Built by all women team of newbie programmers
- Using modern technologies such as GoogleMaps, Chrome Extension and Javascript
## Education
### University of Oklahoma, BA Information Technology 2011 -- 2014
- GPA 4.0
- DB1101 - Basic SQL
- CS2011 - Java Introduction
## Skills
- Web development: HTML, CSS, JavaScript
- Compression: Mpeg, MP4, GIF