I often pop into my blog from neovim with the intent to look at just a
single series of posts, til, gratitude, or just see todays posts.
Markata [1] has a great way of mapping over posts
and returning their path that is designe exactly for this use case.
[2]
To tie these into a Telescope picker you add the command as the
find_command, and comma separate the words of the command, with no
spaces. I did also --sort,date,--reverse in there so that the newest
posts are closest to the cursor.
nnoremap geit <cmd>Telescope find_files find_command=markata,list,--map,path,--filter,date==today<cr>
nnoremap geil <cmd>Telescope find_files find_command=markata,list,--map,path,--filter,templateKey=='til',--sort,date,--reverse<cr>
nnoremap geig <cmd>Telescope find_files find_command=markata,list,--map,path,--filter,templateKey=='gratitude',--sort,date,--reverse<cr>
NOTE telescope treates each word as a string, do not wrap an extra
layer of quotes around your words, it gets messy.
[3]
References:
[1]: https://markata.dev/
[2]: https://images.waylonwalker.com/markta-list-todays-posts.png
[3]: https://images.waylonwalker.com/markata-list-telescope-picker.png
GitHub Stars
GitHub stars posts
1859 posts
latest post 2026-05-24
Publishing rhythm
If you’re into interesting projects, don’t miss out on dynaconf [1], created by dynaconf [2].
Configuration Management for Python ⚙
References:
[1]: https://github.com/dynaconf/dynaconf
[2]: https://github.com/dynaconf
Copier allows you to run post render tasks, just like cookiecutter. These are
defined as a list of tasks in your copier.yml. They are simply shell
commands to run.
The example I have below runs an update-gratitude bash script after the
copier template has been rendered.
# copier.yml
num: 128
_answers_file: .gratitude-copier-answers.yml
_tasks:
- "update-gratitude"
I have put the script in ~/.local/bin so that I know it’s always on my
$PATH. It will reach back into the copier.yml and update the default
number.
#!/bin/bash
# ~/.local/bin/update-gratitude
current=`awk '{print $2}' ~/.copier-templates/gratitude/copier.yml | head -n 1`
new=`expr $current + 1`
echo $current
echo $new
sed -i "s/$current/$new/g" ~/.copier-templates/gratitude/copier.yml
I’ve referenced a video from Anthony Sotile in passing conversation several
times. Walking through his gradual typing process has really helped me
understand typing better, and has helped me make some projects better over time
rather than getting slammed with typing errors.
https://youtu.be/Rk-Y71P_9KE
Step 1
Run Mypy as is, don’t get fancy yet. This will not reach into any functions
unless they are alreay explicitly typed. It will not enforce you to type them
either.
pip install mypy
mypy .
# or your specific project to avoid .venvs
mypy src
# or a single file
mypy my-script.py
Step 2 # [1]
Next we will add check-untyped-defs, this will start checking inside
functions that are not typed. To add this to your config create a
setup.cfg with the following.
[mypy]
check_untyped_defs = True
Step 3 # [2]
The final stage to this series is to add disallow_untyped_defs. This will
start requiring all of your functions to be type hinted. This one is probably
the toughest, because as you type functions mypy can uncover more issues for
you to fix. Often times the list of errors grows before it shrinks.
[mypy]
check_untyped_defs = True
disallow_untyped_defs = True
Anthony’s video # [3...
In order to make an auto title plugin for markata I needed to come up
with a way to reverse the slug of a post to create a title for one that
does not explicitly have a title.
slugs
a slug is generally all lowercase and free of spaces, and is a way to
make website routes (urls)
Here I have a path available that gives me the articles path, ex.
python-reverse-sluggify.md. An easy way to get rid of the file
extension, is to pass it into pathlib.Path and ask for the stem, which
returns python-reverse-sluggify. Then from There I chose to replace
- and _ with a space.
article["title"] = (
Path(article["path"]).stem.replace("-", " ").replace("_", " ").title()
)
To turn this into a markata plugin I put it into a pre_render hook.
from pathlib import Path
from markata.hookspec import hook_impl, register_attr
@hook_impl
@register_attr("articles")
def pre_render(markata) -> None:
for article in markata.filter('title==""'):
article["title"] = (
Path(article["path"]).stem.replace("-", " ").replace("_", " ").title()
)
I really appreciate that in linux anything can be scripted, including
setting the wallpaper. So everytime I disconnect a monitor I can just
rerun my script and fix my wallpaper without digging deep into the ui
and fussing through a bunch of settings.
feh --bg-scale ~/.config/awesome/wallpaper/my_wallpaper.png
I set my default wallpaper with feh using the command above.
Leaning in on feh, we can use fzf to pick a wallpaper from a directory
full of wallpapers with very few keystrokes.
alias wallpaper='ls ~/.config/awesome/wallpaper | fzf --preview="feh --bg-scale ~/.config/awesome/wallpaper/{}" | xargs -I {} feh --bg-scale ~/.config/awesome/wallpaper/{}'
I have mine alias’d to wallpaper so that I can quickly run it from
my terminal.
Getting docstrings from python’s ast is far simpler and more reliable than any
method of regex or brute force searching. It’s also much less intimidating
than I originally thought.
Parsing # [1]
First you need to load in some python code as a string, and parse it with
ast.parse. This gives you a tree like object, like an html [2] dom.
py_file = Path("plugins/auto_publish.py")
raw_tree = py_file.read_text()
tree = ast.parse(raw_tree)
Getting the Docstring # [3]
You can then use ast.get_docstring to get the docstring of the node you are
currently looking at. In the case of freshly loading in a file, this will be
the module level doctring that is at the very top of a file.
module_docstring = ast.get_docstring(tree)
Walking for all functions # [4]
To get all of the functions docstrings we can use ast.walk to look for nodes
that are an instance of ast.FunctionDef, then run get_docstring on those
nodes.
functions = [f for f in ast.walk(tree) if isinstance(f, ast.FunctionDef)]
function_docs = [ast.get_docstring(f) for f in functions]
ast.walk docs: Recursively yield all descendant nodes in the tree starting at node
(including node itself), in no specified order. This is useful ...
Many tools such as ripgrep respect the .gitignore file in the directory
it’s searching in. This helps make it incredibly faster and generally
more intuitive for the user as it just searches files that are part of
thier project and not things like their virtual environments, node
modules, or compiled builds.
Editors like vscode often do not include files that are .gitignored in
their search either.
pathspec is a pattern matching library that implements git [1]’s wildmatch
pattern so that you can ignore files included in your .gitignore
patterns. You might want this to help make your libraries more
performant, or more intuitive for you users.
import pathspec
from pathlib import Path
markdown_files = Path().glob('**/*.md')
if (Path(".gitignore").exists():
lines = Path(".gitignore").read_text().splitlines()
spec = pathspec.PathSpec.from_lines("gitwildmatch", lines)
markdown_files = [
file for file in markdown_files if not spec.match_file(str(file))
]
pathspec home page [2]
References:
[1]: /glossary/git/
[2]: https://github.com/cpburnz/python-path-specification
I don’t use refactoring tools as much as I probably should. mostly
because I work with small functions with unique names, but I recently
had a case where a variable name m was everywhere and I wanted it
named better. This was not possible with find and replace, because
there were other m’s in this region.
I first tried the nvim lsp rename, and it failed, Then I pip installed
rope, a refactoring tool for python, and it just worked!
pip install rope
Once you have rope installed you can call rename on the variable.
:lua vim.lsp.buf.rename()
When running a python process that requires a port it’s handy if there is an
option for it to just run on the next avaialble port. To do this we can use
the socket module to determine if the port is in use or not before starting our
process.
import socket
def find_port(port=8000):
"""Find a port not in ues starting at given port"""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
if s.connect_ex(("localhost", port)) == 0:
return find_port(port=port + 1)
else:
return port
Check out neovim-grimoire [1] by alanwsmith [2]. It’s a well-crafted project with great potential.
No description available.
References:
[1]: https://github.com/alanwsmith/neovim-grimoire
[2]: https://github.com/alanwsmith
functools.total_ordering makes adding all of six of the rich comparison
operators to your custom classes much easier, and more likely that you
remember all of them.
From the Docs: The class must define one of __lt__(), __le__(),
__gt__(), or __ge__ In addition, the class should supply an
__eq__() method.
one of these
- lt()
- le()
- gt()
- ge()
and required to have this one
- eq()
Total Ordering Docs [1]
Here is an example using the Enum I was working on the other day.
from enum import Enum, auto
from functools import total_ordering
@total_ordering
class LifeCycle(Enum):
configure = auto()
glob = auto()
load = auto()
pre_render = auto()
render = auto()
post_render = auto()
save = auto()
def __lt__(self, other):
try:
return self.value < other.value
except AttributeError:
return self.value < other
def __eq__(self, other):
try:
return self.value == other.value
except AttributeError:
return self.value == other
References:
[1]: https://docs.python.org/3/library/functools.html#functools.total_ordering
Check out ipython [1] and their project ipython [2].
Official repository for IPython itself. Other repos in the IPython organization contain things like the website, documentation builds, etc.
References:
[1]: https://github.com/ipython
[2]: https://github.com/ipython/ipython
Adding a --pdb flag to your applications can make them much easier for
those using it to debug your application, especially if your applicatoin
is a cli application where the user has much fewer options to start this
for themselves. To add a pdb flag --pdb to your applications you will
need to wrap your function call in a try/except, and start a post_mortem
debugger. I give credit to
this stack overflow post [1]
for helping me figure this out.
import pdb, traceback, sys
def bombs():
a = []
print(a[0])
if __name__ == "__main__":
if "--pdb" in sys.argv:
try:
bombs()
except:
extype, value, tb = sys.exc_info()
traceback.print_exc()
pdb.post_mortem(tb)
else:
bombs()
Using –pdb # [2]
python yourfile.py --pdb
[3]
References:
[1]: https://stackoverflow.com/questions/242485/starting-python-debugger-automatically-on-error
[2]: #using---pdb
[3]: https://images.waylonwalker.com/using-pdb-flag-from-cli.png
Converting markdown posts to pdf on ubuntu takes a few packages from the
standard repos. I had to go through a few stack overflow posts, and
nothing seemed to have all the fonts and packages that I needed to
convert markdown, but this is what ended up working for me.
Installing all the packages # [1]
sudo apt install \
pandoc \
texlive-latex-base \
texlive-fonts-recommended \
texlive-extra-utils \
texlive-latex-extra \
texlive-xetex
Using pandoc to convert markdown to a pdf # [2]
# older versions of pandoc, I needed this one on ubuntu 18.04
pandoc pages/til/convert-markdown-pdf-linux.md -o convert-markdown-pdf.pdf --latex-engine=xelatex
# newer versions of pandoc, I needed this one on ubuntu 21.04
pandoc pages/til/convert-markdown-pdf-linux.md -o convert-markdown-pdf.pdf --pdf-engine=xelatex
[3]
Here is an image of what converting this article over to a pdf looks
like. The raw markdown is
here [4].
References:
[1]: #installing-all-the-packages
[2]: #using-pandoc-to-convert-markdown-to-a-pdf
[3]: https://images.waylonwalker.com/convert-markdown-pdf-linux-result.png
[4]: https://waylonwalker.com/convert-markdown-pdf-linux.md
Python comes with an enum module for creating enums. You can make your
own enum by inheriting importing and inheriting from Enum.
from enum import Enum
class LifeCycle(Enum):
configure = 1
glob = 2
pre_render = 3
render = 4
post_render = 5
save = 6
auto incrementing # [1]
Enum values can be auto incremented by importing auto, and calling
auto() as their value.
from enum import Enum, auto
class LifeCycle(Enum):
configure = auto()
glob = auto()
pre_render = auto()
render = auto()
post_render = auto()
save = auto()
using the enum # [2]
Enum’s are accessed directy under the class itself, and have primarily
two methods underneath each thing you make, .name and .value.
Lifecycle.glob
Lifecycle.glob.value
Lifecycle.glob.name
[3]
References:
[1]: #auto-incrementing
[2]: #using-the-enum
[3]: https://images.waylonwalker.com/using-lifecycle-enum.png
I recently paired up with another dev running windows with Ubuntu running in
wsl, and we had a bit of a stuggle to get our project off the ground because
they were missing com system dependencies to get going.
Straight in the terminal # [1]
Open up a terminal and get your required system dependencies using the apt
package manager and the standard ubuntu repos.
sudo apt update
sudo apt upgrade
sudo apt install \
python3-dev \
python3-pip \
python3-venv \
python3-virtualenv
pip install pipx
Using an Ansible-Playbook # [2]
I like running things like this through an ansible-playbook as it give me some
extra control and repeatability next time I have a new machine to setup.
- hosts: localhost
gather_facts: true
become: true
become_user: "{{ lookup('env', 'USER') }}"
pre_tasks:
- name: update repositories
apt: update_cache=yes
become_user: root
changed_when: False
vars:
user: "{{ ansible_user_id }}"
tasks:
- name: Install System Packages 1 (terminal)
become_user: root
apt:
name:
- build-essential
- python3-dev
- python3-pip
- python3-venv
- python3-virtualenv
- name: check is pipx installed
shell: command -v pipx
register: pipx_exists
ignore_errors: y...
Stow is an incredible way to manage your dotfiles. It works by managing
symlinks between your dotfiles directory and the rest of the system. You can
then make your dotfiles directory a git [1] repo and have it version controlled. In
my honest opinion, when I was trying to get started the docs straight into deep
detail of things I frankly don’t really care about and jumped right over how to
use it.
When using stow its easiest to keep your dotfiles directory (you may name it
what you want) in your home directory, with application directories inside of
it.
Then each application directory should reflet the same diretory structure as you
want in your home directory.
zsh # [2]
Here is a simple example with my zshrc.
mkdir ~/dotfiles
cd ~/dotfiles
mkdir zsh
mv ~/.zshrc zsh
stow --simulate zsh
You can pass in the –simulate if you wish, it will tell you if there are going
to be any more errors or not, but it wont give much more than that.
WARNING: in simulation mode so not modifying filesystem.
Once your ready you can stow your zsh application.
stow zsh
nvim # [3]
A slightly more complicated example is neovim since its diretory structure does
not put configuration files directl...
Check out sharkdp [1] and their project pastel [2].
A command-line tool to generate, analyze, convert and manipulate colors
References:
[1]: https://github.com/sharkdp
[2]: https://github.com/sharkdp/pastel
If you’re into interesting projects, don’t miss out on asdf [1], created by asdf-vm [2].
Extendable version manager with support for Ruby, Node.js, Elixir, Erlang & more
References:
[1]: https://github.com/asdf-vm/asdf
[2]: https://github.com/asdf-vm