Convert website to Hugo
0
.hugo_build.lock
Normal file
4
Makefile
|
@ -2,11 +2,11 @@ all: build
|
|||
|
||||
@PHONY: build
|
||||
build:
|
||||
./build.py
|
||||
hugo
|
||||
|
||||
@PHONY: watch
|
||||
watch:
|
||||
./watch.sh
|
||||
hugo server
|
||||
|
||||
@PHONY: clean
|
||||
clean:
|
||||
|
|
16
README.md
|
@ -1,19 +1,9 @@
|
|||
# noah.is
|
||||
|
||||
* Everything in `templates/` is assumed to be a Jinja2 template.
|
||||
* Everything in `static/` is assumed to be a file to be taken literally.
|
||||
* Run `make build` to compile all the templates from `templates/` and copy
|
||||
all of the files from `static/` into a shared public root directory called
|
||||
`public/`
|
||||
* Upload `public/*` to your web server.
|
||||
Just a simple Hugo website.
|
||||
|
||||
## Features
|
||||
|
||||
* Pretty path names (`templates/about.html` -> `public/about/index.html`)
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
For Fedora: `sudo dnf install python3-jinja2 inotify-tools`
|
||||
* `make build` to build it
|
||||
* `make watch` to run it locally
|
||||
|
||||
## License
|
||||
|
||||
|
|
6
archetypes/default.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
title: "{{ replace .Name "-" " " | title }}"
|
||||
date: {{ .Date }}
|
||||
draft: true
|
||||
---
|
||||
|
145
build.py
|
@ -1,145 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import codecs
|
||||
import logging
|
||||
import os
|
||||
from shutil import copyfile
|
||||
|
||||
import jinja2
|
||||
|
||||
logging.basicConfig()
|
||||
log = logging.getLogger("build")
|
||||
log.setLevel(logging.INFO)
|
||||
|
||||
class App(object):
|
||||
# Global template variables.
|
||||
def vars(self):
|
||||
return {
|
||||
"site": {
|
||||
"name": "noah.is",
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
# Jinja environment.
|
||||
self.env = jinja2.Environment(
|
||||
loader=jinja2.FileSystemLoader("./templates"),
|
||||
)
|
||||
|
||||
# Collect all the templates and all the static files.
|
||||
self.templates = self.crawl("./templates") # TODO: remove list() wrapper
|
||||
self.static = self.crawl("./static")
|
||||
|
||||
# Copy all of the static files into the public root.
|
||||
if not os.path.isdir("./public"):
|
||||
self.mkdir("./public")
|
||||
for static in self.static:
|
||||
public = static.replace("./static/", "./public/")
|
||||
self.mkdir(public.rsplit("/", 1)[0]) # ensure the path will exist
|
||||
if self.should_copy_static(static, public):
|
||||
log.info("COPY STATIC FILE %s -> %s", static, public)
|
||||
copyfile(static, public)
|
||||
|
||||
# And render all of the templates.
|
||||
for template in self.templates:
|
||||
if template.startswith("./templates/layout"): # don't create these
|
||||
continue
|
||||
public = self.template_to_public(template)
|
||||
self.mkdir(public.rsplit("/", 1)[0]) # ensure the path will exist
|
||||
log.info("RENDER TEMPLATE: %s -> %s", template, public)
|
||||
self.render_template(template, public)
|
||||
|
||||
def render_template(self, template, public):
|
||||
"""
|
||||
Render a template to the public path.
|
||||
|
||||
Parameters:
|
||||
template (str): path in ./templates
|
||||
public (str): path in ./public
|
||||
"""
|
||||
tpl = self.env.get_template(template.replace("./templates/", ""))
|
||||
vars = self.vars()
|
||||
vars["template"] = template
|
||||
vars["filename"] = template.split("/")[-1]
|
||||
html = tpl.render(vars)
|
||||
|
||||
with codecs.open(public, "w", "utf-8") as fh:
|
||||
fh.write(html)
|
||||
|
||||
|
||||
def crawl(self, path):
|
||||
"""
|
||||
Crawl a directory recursively and return a list of file paths.
|
||||
|
||||
Parameters:
|
||||
path (str)
|
||||
|
||||
Returns:
|
||||
filepaths (list[str])
|
||||
"""
|
||||
for root, dirs, files in os.walk(path):
|
||||
for file in files:
|
||||
yield os.path.join(root, file)
|
||||
|
||||
def should_copy_static(self, static_file, public_file):
|
||||
"""
|
||||
Determine if a static file should be copied over to the public folder.
|
||||
|
||||
This means the public file either does not exist yet, or is older
|
||||
than the static file and so the new static file should be copied over it.
|
||||
|
||||
Parameters:
|
||||
static_file (str)
|
||||
public_file (str)
|
||||
|
||||
Returns:
|
||||
bool: True if public_file doesn't exist or is older than static_file.
|
||||
"""
|
||||
if not os.path.exists(public_file):
|
||||
return True
|
||||
return os.stat(static_file).st_mtime > os.stat(public_file).st_mtime
|
||||
|
||||
def template_to_public(self, template_filename):
|
||||
"""
|
||||
Convert a template filename into a public name, preferring clean URL
|
||||
paths without file extension suffixes.
|
||||
|
||||
Examples:
|
||||
* /index.html -> /index.html
|
||||
* /about.html -> /about/index.html
|
||||
* /photos/index.html -> /photos/index.html
|
||||
* /photos/2018.html -> /photos/2018/index.html
|
||||
|
||||
The `.html` and `.md` file types will suppress suffixes in this way.
|
||||
|
||||
Parameters:
|
||||
template_filename: like ``./templates/about.html``
|
||||
|
||||
Returns:
|
||||
public_filename: like ``./public/about/index.html``
|
||||
"""
|
||||
public = template_filename.replace("./templates/", "./public/")
|
||||
path, filename = public.rsplit("/", 1)
|
||||
basename, ext = filename.rsplit(".", 1)
|
||||
if ext not in ["html", "md"]:
|
||||
# Not a web page so just keep the path literal.
|
||||
return public
|
||||
|
||||
print("template_to_public:", template_filename, public)
|
||||
|
||||
# See if it's already an index page or if we need to create one.
|
||||
if filename in ["index.html", "index.md"]:
|
||||
return public # already a good name
|
||||
else:
|
||||
print("HERE:", os.path.join(path, basename, "index."+ext))
|
||||
log.info("basename=%r path=%r filename=%s", basename, path, filename)
|
||||
return os.path.join(path, basename, "index."+ext)
|
||||
|
||||
def mkdir(self, path):
|
||||
"""Create a directory and log it."""
|
||||
if not os.path.isdir(path):
|
||||
log.info("mkdir: %s", path)
|
||||
os.makedirs(path, mode=0o755)
|
||||
|
||||
if __name__ == "__main__":
|
||||
App()
|
31
config.toml
Normal file
|
@ -0,0 +1,31 @@
|
|||
baseURL = 'https://noah.is/'
|
||||
languageCode = 'en-us'
|
||||
title = 'Noah Petherbridge'
|
||||
theme = 'noah'
|
||||
|
||||
[menu]
|
||||
|
||||
[[menu.main]]
|
||||
name = "Home"
|
||||
url = "/"
|
||||
weight = 1
|
||||
|
||||
[[menu.main]]
|
||||
name = "Skills"
|
||||
url = "/skills"
|
||||
weight = 2
|
||||
|
||||
[[menu.main]]
|
||||
name = "Portfolio"
|
||||
url = "/portfolio"
|
||||
weight = 3
|
||||
|
||||
[[menu.main]]
|
||||
name = "Resume"
|
||||
url = "/resume"
|
||||
weight = 4
|
||||
|
||||
[[menu.main]]
|
||||
name = "Contact"
|
||||
url = "/contact"
|
||||
weight = 5
|
60
content/_index.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
title: "Home"
|
||||
date: 2023-09-13T14:56:47-07:00
|
||||
draft: false
|
||||
---
|
||||
<section class="content">
|
||||
<img src="/images/photo.jpg" class="circle center" width="320">
|
||||
|
||||
<h1>Hello world! 👋</h1>
|
||||
|
||||
<p>
|
||||
You have reached the personal homepage of <strong>Noah Petherbridge</strong>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
I'm an introvert and a high-functioning computer nerd who lives near Portland, OR. I
|
||||
enjoy deep conversations about a wide variety of things -- you could say my pool of
|
||||
knowledge is (on average) meters deep but kilometers wide. There are a handful of things I get <em>really</em>
|
||||
passionate and could talk forever about <small>(such as programming,
|
||||
Linux, and whatever's my latest obsession)</small> but also love to learn anything I
|
||||
can about the nature of the universe, astrophysics, spirituality, and all kinds of
|
||||
other rabbit holes.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
I write about random stuff on my <a href="https://www.kirsle.net/" target="_blank">web blog</a> and
|
||||
maintain a handful of open source projects on
|
||||
<a href="https://github.com/kirsle" target="_blank">GitHub</a>
|
||||
and <a href="https://git.kirsle.net/" target="_blank">git.kirsle.net</a>.
|
||||
</p>
|
||||
|
||||
<h2>Software Development</h2>
|
||||
|
||||
<p>
|
||||
I'm a full stack web developer and I program in Go, Python, and
|
||||
JavaScript. One of my largest personal projects is
|
||||
<a href="https://www.rivescript.com/" target="_blank">RiveScript</a>,
|
||||
a scripting language for programming chat bots — matching a user's
|
||||
message to a certain response. I programmed five different ports of
|
||||
RiveScript in different programming languages: Perl, Java, JavaScript,
|
||||
Python, and Go.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
My web blog, <a href="https://www.kirsle.net/" target="_blank">Kirsle.net</a>,
|
||||
is written in Go and I keep that source code <a href="https://github.com/kirsle/blog" target="_blank">here</a>
|
||||
for now. Previously it was written in
|
||||
<a href="https://git.kirsle.net/apps/rophako" target="_blank">Python</a>
|
||||
and its <a href="https://git.kirsle.net/kirsle/kirsle.net" target="_blank">HTML pages</a>
|
||||
are readable too. The site you're reading is a simple Python-generated
|
||||
static site <a href="https://git.kirsle.net/kirsle/noah.is">here</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You may learn a little more about my projects and check out some examples
|
||||
of my work on my <a href="/portfolio">Portfolio</a> page. Also see my
|
||||
<a href="/a-software-developer">software development skills</a> page
|
||||
and <a href="/social">get in touch</a> with me if you want!
|
||||
</p>
|
||||
</section>
|
|
@ -1,7 +1,9 @@
|
|||
{% extends "layout/base.html" %}
|
||||
{% block title %}Contact Me{% endblock %}
|
||||
{% block content %}
|
||||
<section class="first content">
|
||||
---
|
||||
title: "Contact"
|
||||
date: 2023-09-13T15:08:12-07:00
|
||||
draft: false
|
||||
---
|
||||
<section class="content">
|
||||
<h1>Contact Me</h1>
|
||||
|
||||
<p>
|
||||
|
@ -10,6 +12,20 @@
|
|||
</p>
|
||||
|
||||
<aside class="menu">
|
||||
<p class="menu-label">
|
||||
<strong class="has-text-dark is-size-7">Websites</strong>
|
||||
</p>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<a href="https://www.kirsle.net" target="_blank">
|
||||
<i class="fa fa-blog mr-1"></i>
|
||||
<strong>Web blog:</strong>
|
||||
Kirsle.net<br>
|
||||
<small>my personal homepage, tech blog, & creative projects</small>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="menu-label">
|
||||
<strong class="has-text-dark is-size-7">Social Links</strong>
|
||||
</p>
|
||||
|
@ -100,12 +116,4 @@
|
|||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
|
||||
<h2>External Links</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://www.kirsle.net/">Kirsle.net</a>, my web blog.</a></li>
|
||||
<li><a href="https://github.com/kirsle">GitHub</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -1,7 +1,9 @@
|
|||
{% extends "layout/base.html" %}
|
||||
{% block title %}Portfolio{% endblock %}
|
||||
{% block content %}
|
||||
<section class="first content">
|
||||
---
|
||||
title: "Portfolio"
|
||||
date: 2023-09-13T15:08:12-07:00
|
||||
draft: false
|
||||
---
|
||||
<section class="content">
|
||||
<h1>Portfolio</h1>
|
||||
|
||||
<p>
|
||||
|
@ -136,4 +138,3 @@
|
|||
</div>
|
||||
|
||||
</section>
|
||||
{% endblock %}
|
338
content/resume.html
Normal file
|
@ -0,0 +1,338 @@
|
|||
---
|
||||
title: "Resume"
|
||||
date: 2023-09-13T15:08:12-07:00
|
||||
draft: false
|
||||
---
|
||||
<section class="content">
|
||||
<h1>Resume</h1>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<a href="#work-experience">Work Experience</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#education">Education</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3 id="work-experience" class="anchor">Work Experience</h3>
|
||||
</section>
|
||||
|
||||
<!-- Meta -->
|
||||
<div class="columns is-mobile mb-0">
|
||||
<div class="column is-narrow pr-1">
|
||||
<img src="/img/meta.jpg" width="32" height="32" alt="Meta company logo">
|
||||
</div>
|
||||
<div class="column pl-1">
|
||||
<h4 class="subtitle mb-0">Software Engineer <small class="has-text-grey">@ Meta</small></h4>
|
||||
<p>
|
||||
<small>Mar 2023 - Present | Portland, OR (Remote)</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content smaller ml-5">
|
||||
<p>
|
||||
Supernatural was <a href="https://www.latest.oculus.com/blog/within-to-join-meta/" target="_blank">acquired by Meta</a>
|
||||
in March 2023 to bring our VR application in-house as a first-party Oculus studio.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
I helped spearhead the data migration effort (along with a few other senior engineers) to adapt Supernatural's
|
||||
database and API servers into Meta's infrastructure (rewriting back-end services from Python into Hack).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technologies:</strong>
|
||||
<i class="fab fa-python"></i> Python (Flask, SQLAlchemy); <i class="fab fa-js"></i> JavaScript (Vue.js);
|
||||
<i class="fab fa-php"></i> Hack (like PHP); Graph API; GraphQL; several proprietary internal tools for CI/CD,
|
||||
issue management, code review, etc.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Supernatural -->
|
||||
<div class="columns is-mobile mb-0">
|
||||
<div class="column is-narrow pr-1">
|
||||
<img src="/img/supernatural.jpg" width="32" height="32" alt="Supernatural company logo">
|
||||
</div>
|
||||
<div class="column pl-1">
|
||||
<h4 class="subtitle mb-0">Principal Software Engineer <small class="has-text-grey">@ Supernatural</small></h4>
|
||||
<p>
|
||||
<small>Oct 2018 - Mar 2023 | Los Angeles, CA</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content smaller ml-5">
|
||||
<p>
|
||||
Within rebranded to Supernatural named after the Oculus VR app of the same title after finding a
|
||||
market fit with that application.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
I helped build out the back-end Python API server for Supernatural and its deployment pipeline on
|
||||
AWS Elastic Beanstalk <small>(and later in <abbr title="Amazon Elastic Kubernetes Service">EKS</abbr>)</small>.
|
||||
</li>
|
||||
<li>
|
||||
I extended the Within CMS to support managing content & data for Supernatural before forking
|
||||
the CMS into a dedicated app for Supernatural.
|
||||
</li>
|
||||
<li>
|
||||
I built an internal Music Curation tool <small>(named Wormhole)</small> to help the content team
|
||||
identify and manage licensed music to use with the app. It provided a user interface to peruse
|
||||
the massive volume of music data delivered to us by our partner labels (sent in <abbr title="Digital Data Exchange">DDEX</abbr> feed
|
||||
format). Behind the web app was a pipeline of Python worker scripts that ingested the DDEX feeds
|
||||
and handled licensing updates and metadata sync (with Spotify and Music Reports, Inc). Wormhole
|
||||
was backed by a Mongo DB which was the best fit for the loosely structured music data we were working with.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technologies:</strong>
|
||||
<i class="fab fa-python"></i> Python (Flask, SQLAlchemy); <i class="fab fa-golang"></i> Go;
|
||||
<i class="fab fa-js"></i> JavaScript (Vue.js); <i class="fab fa-github"></i> GitHub;
|
||||
<i class="fab fa-docker"></i> Docker; MongoDB; PostgreSQL; Redis; AWS
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Within -->
|
||||
<div class="columns is-mobile mb-0">
|
||||
<div class="column is-narrow pr-1">
|
||||
<img src="/img/within.jpg" width="32" height="32" alt="Within company logo">
|
||||
</div>
|
||||
<div class="column pl-1">
|
||||
<h4 class="subtitle mb-0">Senior Software Engineer <small class="has-text-grey">@ Within (Virtual Reality)</small></h4>
|
||||
<p>
|
||||
<small>Apr 2017 - Mar 2023 | Los Angeles, CA</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content smaller ml-5">
|
||||
<p>
|
||||
I joined Within back when their primary product was a <abbr title="Virtual Reality">VR</abbr> 360° video
|
||||
streaming platform <small>(with multiple web & native apps)</small>, where videos were published via hand-edited JSON
|
||||
files (unique format per platform) and there were no dynamic web apps within the company. Within had ambitions to create a
|
||||
"Lobby" app for you & your friends to hang out in VR and join multiplayer "interactive experiences" together.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
I built out a Python microservice architecture that included an Authentication service (centralized
|
||||
user accounts for the Lobby app) and a Matchmaking service to coordinate multiplayer VR games. I also
|
||||
created a custom Push Notification service (written in Go) to target our desktop apps and website,
|
||||
which did not have a native push mechanism like Android or iOS do.
|
||||
</li>
|
||||
<li>
|
||||
I built a Content Management System (CMS) in Python to consolidate and automate the content
|
||||
publishing pipeline for the 360° VR streaming platform. I studied the various legacy JSON config
|
||||
formats (for the Android, iOS, PS VR, web player, and Unity stand-alone VR player apps) to determine
|
||||
the commonalities and data requirements to translate into a relational (PostgreSQL) database and
|
||||
CMS platform.
|
||||
<ul>
|
||||
<li>The video transcoding pipeline was based on <code>ffmpeg</code> via a third party API called Hybrik.</li>
|
||||
<li>The CMS also managed Unity asset bundles and content publishing for our VR interactive experiences.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technologies:</strong>
|
||||
<i class="fab fa-python"></i> Python (Flask, SQLAlchemy); <i class="fab fa-golang"></i> Go;
|
||||
<i class="fab fa-js"></i> JavaScript (Vue.js, Photon); <i class="fab fa-github"></i> GitHub;
|
||||
<i class="fab fa-docker"></i> Docker; ffmpeg; PostgreSQL; Redis;
|
||||
<abbr title="Amazon Web Services">AWS</abbr> (Elastic Beanstalk, S3)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Media Temple -->
|
||||
<div class="columns is-mobile mb-0">
|
||||
<div class="column is-narrow pr-1">
|
||||
<img src="/img/mediatemple.jpg" width="32" height="32" alt="Media Temple company logo">
|
||||
</div>
|
||||
<div class="column pl-1">
|
||||
<h4 class="subtitle mb-0">Software Developer <small class="has-text-grey">@ (mt) Media Temple</small></h4>
|
||||
<p>
|
||||
<small>Dec 2015 - Apr 2017 | Culver City, CA</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content smaller ml-5">
|
||||
<ul class="mt-0">
|
||||
<li>
|
||||
Built out Python microservices (Tornado framework) to translate between Media Temple and parent company
|
||||
GoDaddy's systems (for example, a Payments service that ran within a secure server on GoDaddy's infrastructure
|
||||
and was called out to from legacy Perl apps at Media Temple).
|
||||
</li>
|
||||
<li>
|
||||
Helped maintain and add new features to a legacy Perl codebase (including the Account Center web app).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technologies:</strong>
|
||||
<i class="fab fa-python"></i> Python (Tornado);
|
||||
<img src="/images/perl.png" width="12" height="12"> Perl (Template::Toolkit, Moose);
|
||||
Puppet; RabbitMQ; cgit; Atlassian FishEye & Crucible.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- ZEFR -->
|
||||
<div class="columns is-mobile mb-0">
|
||||
<div class="column is-narrow pr-1">
|
||||
<img src="/img/zefr.jpg" width="32" height="32" alt="ZEFR company logo">
|
||||
</div>
|
||||
<div class="column pl-1">
|
||||
<h4 class="subtitle mb-0">Software Engineer <small class="has-text-grey">@ ZEFR</small></h4>
|
||||
<p>
|
||||
<small>May 2013 - Dec 2015 | Venice, CA</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content smaller ml-5">
|
||||
<ul class="mt-0">
|
||||
<li>
|
||||
Built out a Python/Flask web app called <abbr title="Brand ID">BrandID</abbr> that provided search and
|
||||
notifications for brand managers regarding YouTube. The idea was that if a video was about to go
|
||||
viral about your brand, you should hear about it from us first. <small>(We had code that would deeply crawl
|
||||
YouTube daily and make predictions based on view counts and trends detected).</small>
|
||||
</li>
|
||||
<li>
|
||||
Also built out a free-to-use, consumer facing app called BrandID Pulse where users could input topics
|
||||
they're interested in and get daily digest e-mails of new and trending videos that match their interests.
|
||||
</li>
|
||||
<li>
|
||||
Helped build out an advertising platform that helped match advertisers to specific YouTube videos that
|
||||
we detected are about to go viral. <small>(On Google's ad platform, you can either target general demographics
|
||||
or you can target specific Video IDs, but the latter feature is rarely used because it was tedious and unpredictable;
|
||||
ZEFR's product helped match advertisers to specific videos relevant to their brand that were detected to be going
|
||||
viral, e.g. the same data that powered BrandID).</small>
|
||||
</li>
|
||||
<li>
|
||||
Upgraded several codebases from Python 2 to 3;
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technologies:</strong>
|
||||
<i class="fab fa-python"></i> Python (Flash, Celery); <i class="fab fa-js"></i> JavaScript (jQuery, Knockout.js, Angular, MEAN stack);
|
||||
<i class="fab fa-github"></i> GitHub; MongoDB; PostgreSQL; Redis; Amazon Web Services (EC2, S3, SQS, SES, Elastic Beanstalk, RDS);
|
||||
HTML/CSS (Bootstrap); <i class="fab fa-docker"></i> Docker; Jenkins
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- DreamHost -->
|
||||
<div class="columns is-mobile mb-0">
|
||||
<div class="column is-narrow pr-1">
|
||||
<img src="/img/dreamhost.jpg" width="32" height="32" alt="DreamHost company logo">
|
||||
</div>
|
||||
<div class="column pl-1">
|
||||
<h4 class="subtitle mb-0">Senior Hosting Developer <small class="has-text-grey">@ DreamHost</small></h4>
|
||||
<p>
|
||||
<small>Feb 2012 - Apr 2013 | Los Angeles, CA</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content smaller ml-5">
|
||||
<ul class="mt-0">
|
||||
<li>
|
||||
I helped maintain and add features to web apps written in Perl (using a custom <abbr title="Object Relational Mapper">ORM</abbr>
|
||||
and templating system for server-side rendered pages) and HTML/JavaScript (jQuery).
|
||||
</li>
|
||||
<li>
|
||||
Projects included work on the customer Control Panel, report automations for financial & marketing,
|
||||
and integration for customer sites and <i class="fab fa-cloudflare"></i> Cloudflare.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technologies:</strong>
|
||||
<img src="/images/perl.png" width="12" height="12"> Perl;
|
||||
<i class="fab fa-javascript"></i> JavaScript;
|
||||
Chef; RabbitMQ; cgit.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Fonality -->
|
||||
<div class="columns is-mobile mb-0">
|
||||
<div class="column is-narrow pr-1">
|
||||
<img src="/img/fonality.jpg" width="32" height="32" alt="Fonality company logo">
|
||||
</div>
|
||||
<div class="column pl-1">
|
||||
<h4 class="subtitle mb-0">Software Engineer <small class="has-text-grey">@ Fonality</small></h4>
|
||||
<p>
|
||||
<small>Jun 2008 - Feb 2012 | Culver City, CA</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content smaller ml-5">
|
||||
<ul class="mt-0">
|
||||
<li>
|
||||
Maintained and contributed features for a customer control panel (written in Perl) for
|
||||
Asterisk <abbr title="Private Branch Exchange (Business telephone system)">PBX</abbr> servers
|
||||
(running a custom CentOS-based operating system branded as PBXtra and Trixbox).
|
||||
</li>
|
||||
<li>
|
||||
One project had me port our PBXtra OS to newer Dell server blades after our OEM discontinued
|
||||
the model we were selling. PBXtra was based on the (already outdated) CentOS 4 which lacked hardware
|
||||
drivers to support the new blades. I troubleshooted and created new installation ISOs that slipstreamed
|
||||
an updated Linux kernel and CD-ROM driver to function on these newer servers.
|
||||
</li>
|
||||
<li>
|
||||
Worked on an HTML5 web front-end for Fonality HUD (an in-house softphone and productivity tool that
|
||||
showed online/call status of your co-workers and managed chat and calls).
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technologies:</strong>
|
||||
<img src="/images/perl.png" width="12" height="12"> Perl (Template::Toolkit, Moose);
|
||||
<i class="fab fa-javascript"></i> HTML/JavaScript (jQuery in some places, mostly vanilla JS, AJAX);
|
||||
<i class="fab fa-asterisk"></i> Asterisk; MySQL; Linux (CentOS)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Liquid Web-->
|
||||
<div class="columns is-mobile mb-0">
|
||||
<div class="column is-narrow pr-1">
|
||||
<img src="/img/liquidweb.jpg" width="32" height="32" alt="Liquid Web company logo">
|
||||
</div>
|
||||
<div class="column pl-1">
|
||||
<h4 class="subtitle mb-0">Linux Systems Administrator <small class="has-text-grey">@ Liquid Web</small></h4>
|
||||
<p>
|
||||
<small>Jan 2008 - May 2008 | Lansing, MI</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content smaller ml-5">
|
||||
<p>
|
||||
The job description was tech support and tech support at a web hosting company is synonymous with
|
||||
systems administration. A large portion of our customer base ran Linux servers (CentOS). My aim was
|
||||
to get hired as a software developer but as I had no prior work experience they started me out in
|
||||
support.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Helped customers debug issues with their Apache config, managed cPanel installations,
|
||||
reading exim logs, and wrote custom Perl scripts to help me troubleshoot their e-mail
|
||||
servers and other such tasks.
|
||||
</li>
|
||||
<li>
|
||||
After a couple months I transitioned to the software dev team and worked on some internal
|
||||
tools and the customer control panel written in Perl.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Technologies:</strong>
|
||||
<img src="/images/perl.png" width="12" height="12"> Perl (Template::Toolkit, POE);
|
||||
<i class="fab fa-javascript"></i> HTML/JavaScript (jQuery); CVS version control;
|
||||
MySQL; Linux (CentOS)
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<section class="content">
|
||||
<h3 id="education" class="anchor">Education</h3>
|
||||
|
||||
<h5 class="mb-0">ITT Technical Institute</h5>
|
||||
<p class="smaller">
|
||||
<em>Associates, Computer Network Systems</em><br>
|
||||
<small>2006 - 2009 | Swartz Creek, MI | Culver City, CA | Torrance, CA</small>
|
||||
</p>
|
||||
|
||||
<h5 class="mb-0">Flushing High School</h5>
|
||||
<p class="smaller">
|
||||
<small>2002 - 2006 | Flushing, MI</small>
|
||||
</p>
|
||||
</section>
|
|
@ -1,14 +1,17 @@
|
|||
{% extends "layout/base.html" %}
|
||||
{% block title %}Software Development{% endblock %}
|
||||
{% block content %}
|
||||
<section class="first content">
|
||||
<h1>Software Development</h1>
|
||||
---
|
||||
title: "Skills"
|
||||
date: 2023-09-13T15:08:12-07:00
|
||||
draft: false
|
||||
---
|
||||
<section class="content">
|
||||
<h1>Skills</h1>
|
||||
|
||||
<p>
|
||||
For pretty much all of my career (apart from some retail jobs early on while I went to college)
|
||||
I've worked as a software engineer (professionally since 2008). I taught myself to program since
|
||||
I was a young teenager, and my first 'real' language was Perl which I first learned because I
|
||||
wanted to build my own chatbots for AOL Instant Messenger.
|
||||
I was a young teenager (having learned HTML and created my first website at 12 years old), and
|
||||
then my first 'real' language was Perl which I first learned because I wanted to build my own
|
||||
chatbots for AOL Instant Messenger.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -27,11 +30,98 @@
|
|||
</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="#skills">Technical Skills</a></li>
|
||||
<li><a href="#web-development">Web Development</a></li>
|
||||
<li><a href="#technical">Technical Skills</a></li>
|
||||
<li><a href="#examples">Example Projects</a></li>
|
||||
</ul>
|
||||
|
||||
<h2 id="skills" class="anchor">Technical Skills</h2>
|
||||
<h2 id="web-development" class="anchor">Web Development</h2>
|
||||
|
||||
<p>
|
||||
I call myself a "full stack" web developer and have a varied skillset to tackle both front-end
|
||||
and back-end concerns of web applications. I am most at home on the back-end side (everything
|
||||
from developing the app itself to getting it deployed on a server or cloud provider) but have
|
||||
also been known to create a nice front-end web design or Single Page App (SPA) from time to time.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-header has-background-info">
|
||||
<p class="card-header-title has-text-light">
|
||||
<i class="fa fa-server mr-2"></i> Back-end (server side)
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-content content">
|
||||
<h6>Languages</h6>
|
||||
|
||||
<p>
|
||||
<i class="fab fa-golang"></i> Go,
|
||||
<i class="fab fa-python"></i> Python,
|
||||
<i class="fab fa-node-js"></i> JavaScript,
|
||||
<img src="/images/perl.png" width="16" height="16"> Perl
|
||||
</p>
|
||||
|
||||
<h6>Frameworks</h6>
|
||||
|
||||
<p>
|
||||
<i class="fab fa-golang"></i> <a href="https://gorm.io" target="_blank">GORM</a>,
|
||||
<a href="https://github.com/urfave/negroni" target="_blank">Negroni</a>,
|
||||
<a href="https://github.com/gin-gonic/gin" target="_blank">Gin</a><br>
|
||||
<i class="fab fa-python"></i> <a href="https://flask.palletsprojects.com/en/2.3.x/" target="_blank">Flask</a>,
|
||||
<a href="https://www.tornadoweb.org/en/stable/" target="_blank">Tornado</a>,
|
||||
<a href="https://www.sqlalchemy.org/" target="_blank">SQLAlchemy</a><br>
|
||||
<i class="fab fa-node-js"></i> <a href="https://expressjs.com/" target="_blank">Express.js</a>
|
||||
</p>
|
||||
|
||||
<h6>Technologies</h6>
|
||||
|
||||
<p>
|
||||
<i class="fab fa-docker"></i> Docker, <i class="fab fa-github"></i> GitHub, <i class="fab fa-linux"></i> Linux,
|
||||
RESTful API, server-side templates,
|
||||
DevOps/systems administration, Amazon AWS <small>(EC2, S3, SQS, SES, EKS, Elastic Beanstalk)</small>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<div class="card-header has-background-info">
|
||||
<p class="card-header-title has-text-light">
|
||||
<i class="fa fa-brush mr-2"></i> Front-end (web design)
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-content content">
|
||||
<h6>Languages</h6>
|
||||
|
||||
<p>
|
||||
<i class="fab fa-html5"></i> HTML,
|
||||
<i class="fab fa-css3"></i> CSS,
|
||||
<i class="fab fa-node-js"></i> JavaScript
|
||||
</p>
|
||||
|
||||
<h6>Frameworks</h6>
|
||||
|
||||
<p>
|
||||
<i class="fab fa-node-js"></i> Vue.js, Angular, React, Knockout, vanilla<br>
|
||||
<i class="fab fa-css3"></i> Bootstrap, Bulma, Tailwind
|
||||
</p>
|
||||
|
||||
<h6>Technologies</h6>
|
||||
|
||||
<p>
|
||||
Ajax, WebSockets, WebRTC, responsive design (mobile friendly), Progressive
|
||||
Web Apps (PWAs), Single Page Apps (SPAs), localStorage, Cross Origin Resource Sharing (CORS)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="content">
|
||||
<h2 id="technical" class="anchor">Technical Skills</h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
|
@ -43,7 +133,7 @@
|
|||
(npm and web) and use these languages regularly for work and side projects.
|
||||
</li>
|
||||
<li>
|
||||
I also once knew <img src="/images/perl.png" width="16" height="16"> <strong>Perl 5</strong> well (13 years straight)
|
||||
I also once knew <img src="/images/perl.png" width="16" height="16"> <strong>Perl 5</strong> well (13 years straight!)
|
||||
but may be a bit rusty now!
|
||||
</li>
|
||||
<li>
|
||||
|
@ -193,4 +283,3 @@
|
|||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
{% endblock %}
|
|
@ -1,6 +1,8 @@
|
|||
{% extends "layout/base.html" %}
|
||||
{% block title %}Welcome{% endblock %}
|
||||
{% block content %}
|
||||
---
|
||||
title: "Home Page"
|
||||
date: 2023-09-13T14:56:47-07:00
|
||||
draft: false
|
||||
---
|
||||
<section class="first content">
|
||||
<img src="/images/photo.jpg" class="circle center" width="320">
|
||||
|
||||
|
@ -56,4 +58,3 @@
|
|||
and <a href="/social">get in touch</a> with me if you want!
|
||||
</p>
|
||||
</section>
|
||||
{% endblock %}
|
BIN
static/img/dreamhost.jpg
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
static/img/fonality.jpg
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
static/img/liquidweb.jpg
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
static/img/mediatemple.jpg
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
static/img/meta.jpg
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
static/img/supernatural.jpg
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
static/img/within.jpg
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
static/img/zefr.jpg
Normal file
After Width: | Height: | Size: 3.0 KiB |
|
@ -1,50 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="/css/bulma.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/fontawesome-free-6.1.2-web/css/all.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/theme/style.css">
|
||||
<link rel="shortcut icon" href="/images/photo.jpg">
|
||||
<title>{% block title %}Untitled{% endblock %} - {{ site.name }}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<nav class="navbar is-fullwidth" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-tab {% if filename == 'index.html'%} is-active{% endif %}" href="/">{{ site.name }}</a>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="myNavbar">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="myNavbar" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
|
||||
<a class="navbar-item is-tab{% if filename == 'a-software-developer.html'%} is-active{% endif %}" href="/a-software-developer">Development</a></li>
|
||||
<a class="navbar-item is-tab{% if filename == 'portfolio.html'%} is-active{% endif %}" href="/portfolio">Portfolio</a></li>
|
||||
<a class="navbar-item is-tab{% if filename == 'social.html'%} is-active{% endif %}" href="/social">Contact</a></li>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<script src="/js/bulma.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
2
themes/noah/archetypes/default.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
+++
|
||||
+++
|
1
themes/noah/layouts/404.html
Normal file
|
@ -0,0 +1 @@
|
|||
<h1>Not Found</h1>
|
11
themes/noah/layouts/_default/baseof.html
Normal file
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
{{- partial "head.html" . -}}
|
||||
<body>
|
||||
{{- partial "header.html" . -}}
|
||||
|
||||
{{- block "main" . }}{{- end }}
|
||||
|
||||
{{- partial "footer.html" . -}}
|
||||
</body>
|
||||
</html>
|
10
themes/noah/layouts/_default/list.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{{ define "main" }}
|
||||
<h1>{{ .Title }}</h1>
|
||||
{{ range .Pages.ByPublishDate.Reverse }}
|
||||
<p>
|
||||
<h3><a class="title" href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
|
||||
{{ partial "metadata.html" . }}
|
||||
<p>{{ .Summary }}</p>
|
||||
</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
10
themes/noah/layouts/_default/single.html
Normal file
|
@ -0,0 +1,10 @@
|
|||
{{ define "main" }}
|
||||
<main aria-role="main">
|
||||
<div class="first">
|
||||
{{ if .Params.Toc }}
|
||||
{{.TableOfContents}}
|
||||
{{ end }}
|
||||
{{ .Content }}
|
||||
</div>
|
||||
</main>
|
||||
{{ end }}
|
14
themes/noah/layouts/index.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{{ define "main" }}
|
||||
<main aria-role="main">
|
||||
<section class="first">
|
||||
<!-- Note that the content for index.html, as a sort of list page, will pull from content/_index.md -->
|
||||
{{ .Content }}
|
||||
<!-- </div>
|
||||
<div>
|
||||
{{ range first 10 .Site.RegularPages }}
|
||||
{{ .Render "summary" }}
|
||||
{{ end }}
|
||||
</div> -->
|
||||
</section>
|
||||
</main>
|
||||
{{ end }}
|
8
themes/noah/layouts/partials/footer.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<div class="container px-4">
|
||||
<div class="notification is-dark has-text-centered has-text-grey smaller my-4 py-3">
|
||||
Copyright © {{ now.Format "2006"}} Noah Petherbridge.
|
||||
All rights reserved.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/bulma.js"></script>
|
12
themes/noah/layouts/partials/head.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<head>
|
||||
{{ $title := print .Title " | " .Site.Title }}
|
||||
{{ if .IsHome }}{{ $title = .Site.Title }}{{ end }}
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link rel="canonical" href="{{ .Permalink }}">
|
||||
<link rel="stylesheet" type="text/css" href="/css/bulma.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/fontawesome-free-6.1.2-web/css/all.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/css/style.css">
|
||||
<link rel="shortcut icon" href="/images/photo.jpg">
|
||||
<title>{{ $title }}</title>
|
||||
</head>
|
22
themes/noah/layouts/partials/header.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<header>
|
||||
<nav class="navbar is-fullwidth" role="navigation" aria-label="main navigation">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-tab {% if filename == 'index.html'%} is-active{% endif %}" href="/">{{ .Site.Title }}</a>
|
||||
|
||||
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="myNavbar">
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div id="myNavbar" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
|
||||
</div>
|
||||
|
||||
{{- partial "navbar.html" . -}}
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
</header>
|
13
themes/noah/layouts/partials/metadata.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<div class="mb-4">
|
||||
{{ if .PublishDate }}
|
||||
{{ $dateTime := .PublishDate.Format "2006-01-02" }}
|
||||
{{ $dateFormat := .Site.Params.dateFormat | default "Jan 2, 2006" }}
|
||||
<time datetime="{{ $dateTime }}">{{ .PublishDate.Format $dateFormat }}</time>
|
||||
{{ end }}
|
||||
{{ with .Params.tags }}
|
||||
{{ range . }}
|
||||
{{ $href := print (absURL "tags/") (urlize .) }}
|
||||
<a class="btn btn-sm btn-outline-dark tag-btn" href="{{ $href }}">{{ . }}</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
6
themes/noah/layouts/partials/navbar.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{{ $Root := . }}
|
||||
<div class="navbar-end">
|
||||
{{ range .Site.Menus.main }}
|
||||
<a class="navbar-item is-tab{{if eq .Name $Root.Title}} is-active{{end}}" href="{{ .URL }}">{{ .Name }}</a>
|
||||
{{ end }}
|
||||
</div>
|
2
themes/noah/layouts/shortcodes/rawhtml.html
Normal file
|
@ -0,0 +1,2 @@
|
|||
<!-- raw html -->
|
||||
{{.Inner}}
|
|
@ -31,6 +31,7 @@ header {
|
|||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 9000;
|
||||
line-height: 3rem;
|
||||
background-color: rgba(255, 153, 255, 0.9);
|
||||
box-shadow: 0px 2px 2px rgba(128, 0, 128, 0.4);
|
||||
|
@ -62,15 +63,10 @@ header nav a {
|
|||
|
||||
main {
|
||||
min-height: 100%;
|
||||
margin: 0 24px;
|
||||
margin: 0 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
section.first {
|
||||
padding-top: 5rem;
|
||||
padding: 5rem 1.5rem 1.5rem 1.5rem;
|
||||
}
|
||||
|
||||
footer {
|
||||
|
@ -83,6 +79,10 @@ footer {
|
|||
font-style: italic;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.center {
|
||||
display: block;
|
||||
text-align: center;
|