June 13, 2023
While publishing my series of dev diaries for my URL shortener project, I encountered some strange errors from Jekyll. Below is example code to create a table using MUI in React:
return (
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Short Path</TableCell>
<TableCell>URL</TableCell>
<TableCell>Created</TableCell>
<TableCell>Updated</TableCell>
</TableRow>
</TableHead>
<TableBody>
{links.map((row) => (
<TableRow
key={row.uid}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.shortPath}
</TableCell>
<TableCell>{row.url}</TableCell>
<TableCell>{toTimestamp(row.createdAt)}</TableCell>
<TableCell>{toTimestamp(row.updatedAt)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}
When I save this codeblock to a markdown file, I get a strange syntax error from liquid out of nowhere:
Regenerating: 2 file(s) changed at 2023-06-05 19:31:13
_posts/2023-06-13-yyyy.md
.history/_posts/2023-06-13-yyyy_20230605193112.md
Liquid Exception: Liquid syntax error (line 70): Variable '{{ "&:last-child td, &:last-child th": { border: 0 }' was not properly terminated with regexp: /\}\}/ in /Users/nickymarino/Dropbox/Mac (3)/Documents/Developer/nickymarino.github.io/_posts/2023-06-13-yyyy.md
Error: Liquid syntax error (line 70): Variable '{{ "&:last-child td, &:last-child th": { border: 0 }' was not properly terminated with regexp: /\}\}/
Error: Run jekyll build --trace for more information.
This was really strange to me. At first I couldn’t figure out what liquid even was–I had forgotten it’s a tool Jekyll uses to pre-process markdown files–and I wasn’t sure why liquid was looking inside the codeblock.
Thanks to chuckhoupt’s comment on the Jekyll help forum though, I learned I can wrap my code in a raw tag to prevent processing:
<!--
{% raw %}
Disable liquid parsing on this codeblock to prevent errors reading '{{'
See: https://talk.jekyllrb.com/t/code-block-is-improperly-handled-and-generates-liquid-syntax-error/7599/2
-->
```ts
return (
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Short Path</TableCell>
<TableCell>URL</TableCell>
<TableCell>Created</TableCell>
<TableCell>Updated</TableCell>
</TableRow>
</TableHead>
<TableBody>
{links.map((row) => (
<TableRow
key={row.uid}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.shortPath}
</TableCell>
<TableCell>{row.url}</TableCell>
<TableCell>{toTimestamp(row.createdAt)}</TableCell>
<TableCell>{toTimestamp(row.updatedAt)}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
}
```
<!-- {% endraw %} -->
While writing this post, I also discovered this trick by Josh to wrap the double quotes in a variable:
The downside is that it leads to some strange looking code. For example, to escape the above raw and endraw tags, I used:
<!--
{% raw %}
...
-->
<!-- {% endraw %} -->
Outside of code snippets, you can use the HTML entities as well: { and }. And to write this post, I had to use this trick by Waylan to use more backticks to mark the start and end of a snippet:
````text
<!-- ... -->
```ts
return ( ... )
```
<!-- ... -->
````
June 5, 2023
I’m going to strengthen my fullstack serverless developer skills by building a clone of bitly.
This idea came from a conversation I had with a friend of mine, who hosts parties often and manage the invite lists with a Telegram chat. He wanted to create fancy invitations by embossing a link to the chat as a QR code, and to do that he needed to create a rubber stamp for the QR code.
Custom rubber stamps are pretty expensive, and so I suggested that the QR code point to a shortened URL. That way, he can change the URL later without creating a new stamp. To which he replied:
“I’ll use bitly to create the invite link, of course.”
Now don’t get me wrong, bitly is great for non-technical folks to create shortened URLs. However, especially after Heroku ended their free tier, I’ve learned that there’s no such thing as a free lunch. I’m immediately leery of any free service that services a lot of people without a very obvious market strategy behind it.
So, it’s about time I build my own URL shortener! I have plenty of use cases for it, it’s a great side project to learn how to write fullstack apps, and I can always open it up to my friends to use as well.
A Foray into Developer Journals
Inspired by Juraj Majerik’s diary of an Uber clone and David Smith’s design diary, my plan is to write short blog posts on the days I work on this project. In a perfect world, I’d like to devote at least an hour daily to this, but I’m aiming to make a post at least once a week until completion.
Architecture Overview
Before I get started, I drafted what I expect my tech stack to look like in broad strokes. Additional context I used:
- Unlike most side projects, this is one I hope to use personally going forward.
- At work, our stack is AWS and SST for infrastructure, and React and MUI for the frontend.
Here’s my plan for the architecture:

I’m going to use SST to manage the infrastructure and stitching all the pieces together. For the API backend, the data store will be a DynamnoDB mono-table, and the compute will be AWS Lambda functions behind an API Gateway. AWS Cognito will manage the user authentication and authorization.
I’m really excited to embark on this journey. By documenting my progress in short blog posts, I hope to share my experiences and insights with others who may be interested in similar projects. Stay tuned for future updates as I delve into the development process and bring this project to life. Let’s build something amazing together!
May 30, 2022
There are two hard problems to solve in a Python project: installing packages, and installing packages in other projects.
Over the years, the challenges I’ve come across in Python projects has narrowed down to two topics :
- Installing packages for just my current project – not for all projects using that version of Python
- Using different Python versions for different projects
Problem (1) can be solved with Python’s built in virtual environments, but they don’t automatically activate when you enter a project folder. As a result, I often forget to run source venv/activate and then accidentally install a bunch of packages to my system Python.
I highly recommend Pyenv to solve both problems. Pyenv is a fantastic tool for managing Python environments simply by changing your current folder. Pyenv also integrates with virtualenv so that you can create virtual environments for any Python version you’d like to use.
Overview
By the end of this post you will know how to:
- Install
pyenv on your computer
- Build a version of Python with
pyenv
- Change your global and local folder settings to run the new version of Python
- Connect
pyenv to virtualenv to create a new virtual environment
Install Pyenv
First, install Xcode tools and a few development libraries that the Python build step will need:
# Note: These are macOS specific commands
# Update XCode and Homebrew
xcode-select --install
brew update
# Install libraries you'll need to build Python from source
brew install openssl readline sqlite3 xz zlib
# Install pyenv
brew install pyenv
Note: The installation instructions for pyenv change frequently. These are the steps for pyenv==2.3.1 on macOS. If you are using a different version or OS, see the pyenv installation guide.
If you’re using a zsh shell, run these commands to add pyenv to your dot files. If you’re using another shell, follow the installation steps for your shell under the Shell environment section of the pyenv README.
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.zshrc
echo 'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(pyenv init -)"' >> ~/.zshrc
Restart your terminal for the changes to take effect, and verify your installation by entering the command which python3 and confirming that pyenv/.shims is somewhere in the output:
$ which python3
/Users/nicky/.pyenv/shims/python3
Install Python 3.8.2
Next let’s install Python 3.8.2. I chose this particular version of Python because I use it in a few of my side projects, so you can use any other version of Python if you’d like.
Recently, I came across a bug in macOS Big Sur that prevents libraries like numpy and pandas to use the lzma package. Credit to the thread for pyenv Issue #1737 for the below instructions to correct the 3.8.2 build.
To install Python 3.8.2, first reinstall the zlib and bzip2 libraries via Homebrew:
brew reinstall zlib bzip2
In your ~/.zshrc (or ~/.bashrc if you’re using bash), add the below flags for the Python compiler to read:
export LDFLAGS="-L/usr/local/opt/zlib/lib -L/usr/local/opt/bzip2/lib"
export CPPFLAGS="-I/usr/local/opt/zlib/include -I/usr/local/opt/bzip2/include"
Then install Python 3.8.2 using the below command. This may take some time to compile.
CFLAGS="-I$(brew --prefix openssl)/include -I$(brew --prefix bzip2)/include -I$(brew --prefix readline)/include -I$(xcrun --show-sdk-path)/usr/include" \
LDFLAGS="-L$(brew --prefix openssl)/lib -L$(brew --prefix readline)/lib -L$(brew --prefix zlib)/lib -L$(brew --prefix bzip2)/lib" \
pyenv install --patch 3.8.2 < <(curl -sSL https://github.com/python/cpython/commit/8ea6353.patch\?full_index\=1)
Verify that you built and installed Python 3.8.2 by checking what versions of Python pyenv has:
$ pyenv versions
system
3.8.2
Manage Pyenv Versions
Pyenv has two types of versions:
local – the Python version for your current directory
global – the default Python version to use if no local version is set.
When you switch directories in your terminal, Pyenv checks for a .python-version file in the root of that directory. If there’s no .python-version, Pyenv will search each parent folder until one is found.
You can set what version of Python pyenv will use in your current folder with pyenv local:
You can check .python-version to see what version is used:
$ cat .python-version
3.8.2
And pyenv version will also tell you what version of Python you’re currently using as well as what config file set it:
$ pyenv version
3.8.2 (set by /Users/nicky/Developer/my-project/.python-version)
If you move to a different folder without a .python-version, Pyenv will use your global setting, which is system by default:
$ cd /path/to/other/folder
$ pyenv version
system (set by /Users/nicky/.python-version)
How to Use Pyenv Virtual Environments
Give pyenv superpowers by installing pyenv-virtualenv. With this plugin, pyenv can manage virtual environments like venv and Conda environments.
Install the plugin with Homebrew:
brew install pyenv-virtualenv
And add the following to your shell’s .rc file (such as .zshrc or .bashrc):
eval "$(pyenv virtualenv-init -)"
To create a virtual environment for the Python version you’re using with pyenv, run pyenv virtualenv [version] [new environment name]. For example, to create a new venv for a sample project using 3.8.2:
pyenv virtualenv 3.8.2 my-project-3.8.2
While not required, I recommend adding the Python version to your environment names to manage them easier.
You can also create a new virtual environment using the current pyenv Python version:
pyenv virtualenv my-default-venv
Just like pyenv, your virtual environment will automatically activate whenever you move into that folder!
Helpful Commands
Below is a list of commands for pyenv that I use often.
To show all of your Python versions and all virtual environments:
To set a different version for the python and python3 commands for your global settings:
# (This works the same with `local` as well)
pyenv global [python version] [python3 version]
To create a new virtual environment and use it locally:
pyenv virtualenv [python version] [new environment name]
pyenv local [new environment name]