A number of the companies I’ve been interviewing with are using Guide to create an “interview guide” that they send to candidates. I really like this. It includes information about the interview topic, interviewer profiles, and relevant links to pages on the company website and/or blog.
For one company, an interviewer profile linked to that person’s GitHub profile, so I took a look. Instead of the standard profile, they have a custom thing with lots of fun stats about their activity on GitHub. Neat!
But how do I do that? It took me an embarrassingly long time to figure out what
the GitHub feature for this was named. The docs call it “Managing your
it is simple. If your account name is “githubuser”, you make a new repo called
“githubuser”. Then any
README.md in that repo will be displayed on your
GitHub profile page.
I think this feature has existed for a long time but I somehow managed to miss
it. I took a look at how the profile I linked to,
Eric’s, is generated. It’s using the
Actions. Each plugin generates an
infographic, and you control what is generated by which plugins you use in
your GitHub Actions workflow. The actual generation uses GitHub’s API (or
other services for a few plugins).
But while I like how it looks, I don’t like that it only generates images. This means that nothing is clickable, so you can’t link to recent PRs or repos with recent activity, for example.
Fortunately, there are many other options. A search for “github profile generator” finds many options. One of the most popular appears to be Rahul Jain’s. You fill out a web form and it gives you some markdown to turn into your profile.
But I don’t like this one either. The content it generates is essentially static. If you want to update it you need to go to the form and change things (though you can save your config between uses).
I wanted something that would regenerate regularly, and I wanted it to include whatever I wanted. I also wanted the output to be Markdown (without lots of images) so it could have clickable links. There was nothing else to do but to build yet another profile generator!
Does the world need another profile generator? No, it doesn’t. Did I do it anyway? Yes, I did.
How It Works
I wrote it in Rust. Is Rust the best language for this? Yes, because I want to learn more Rust.
I started with
uses GitHub’s REST API (the V3 API). REST
APIs are usually quite easy to understand, even though you end up with a lot
of API calls in many cases. However, at one point I found a weird bug in
octorust where the
inexplicably returns a
Result<i64> instead of the data it should
octorust crate is entirely generated, and the generated code lives
alongside the generator in the
repo. That repo has
issues disabled, so I couldn’t report this (though it does allow PRs).
In the meantime, I’d been looking at how
lowlighter/metrics was implemented
and saw that it was using GitHub’s GraphQL API (the V4
API). I find GraphQL more challenging to
use than REST since you need to formulate queries from scratch, rather than
using predefined REST endpoints that correspond to specific resources. But the
big advantage of GraphQL is that you can get exactly the data you want across
multiple resources in a single query.
I decided that this was a good opportunity to learn something new, so I took
some queries from the
lowlighter/metrics repo and started massaging them to
give me what I wanted.
At first, I tried a single query to give me all the repos to which I had access via the User repositories field. But for some reason, this seemed to skip some repos in my houseabsolute organization. There’s a good chance this was user error on my part, but I eventually ended up simply doing one query for the user and one for the org.
I used the
graphql_client crate to
make these requests. At first, I used its macros to generate code at compile
time from my queries. But the huge downside of this is that it doesn’t play
very nicely with code completion. The generated structs are quite complex, and
the only way for me to find out what fields they contained was to use
cargo-expand. But that didn’t
help with code completion.
Fortunately, the crate also includes a
graphql-client binary that will
generate the same code that the macro does. This was much better. I could open
the code in Emacs to view the generated structs and modules, and code
completion just worked.
There was still one unfortunate issue, which is that even though my queries for user repos and org repos are nearly identical, the generator produces different Rust types for the user’s list of repos versus the org’s. This means that my stats collection code needs to be repeated for each type.
There are a couple of ways I could fix it. One would be to create a trait that each type can implement. But traits are defined in terms of methods. That means I’d have to write methods for each field in common between the types that I wanted to access. I did try this, but then I ran into lifetime issues when trying to take references to data in these structs. I’m sure those issues are solvable, but I just wanted to get a profile up, not spend all my time solving lifetime issues!
The other way to deal with this would be to write a macro of my own for the
stats collection code. But that would have the same code completion problems,
and that feels way over-engineered. I think what would be ideal would be a way
to tell the
graphql-client codegen that these two types are the same
type. But that’s a PR for another day.
The final piece to make it all work is a simple GitHub Actions workflow that
runs whenever I push to the master branch of the generator repo as well as
running nightly. If the generated
README.md changes, it commits the changes
and pushes the commit to the repo.
GitHub will stop running scheduled workflows for a repo after 60 days of inactivity, but I’m fairly sure that commits made by this workflow will count as activity, so it will never stop running. But I will find out in a couple of months.
If you want to use this for your own profile you’ll have to change some of the code. I have my username and org as a constant in the code, and most people probably don’t have a single org for most of their code. But really, you should probably use one of the many more battle-tested alternatives.