<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: ripgrep</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/ripgrep.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-02-14T02:40:11+00:00</updated><author><name>Simon Willison</name></author><entry><title>How to add a directory to your PATH</title><link href="https://simonwillison.net/2025/Feb/14/how-to-add-a-directory-to-your-path/#atom-tag" rel="alternate"/><published>2025-02-14T02:40:11+00:00</published><updated>2025-02-14T02:40:11+00:00</updated><id>https://simonwillison.net/2025/Feb/14/how-to-add-a-directory-to-your-path/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://jvns.ca/blog/2025/02/13/how-to-add-a-directory-to-your-path/"&gt;How to add a directory to your PATH&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;em&gt;Classic&lt;/em&gt; Julia Evans piece here, answering a question which you might assume is obvious but very much isn't.&lt;/p&gt;
&lt;p&gt;Plenty of useful tips in here, plus the best explanation I've ever seen of the three different Bash configuration options:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Bash has three possible config files: &lt;code&gt;~/.bashrc&lt;/code&gt;, &lt;code&gt;~/.bash_profile&lt;/code&gt;, and &lt;code&gt;~/.profile&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you're not sure which one your system is set up to use, I'd recommend testing this way:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;add &lt;code&gt;echo hi there&lt;/code&gt; to your &lt;code&gt;~/.bashrc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Restart your terminal&lt;/li&gt;
&lt;li&gt;If you see "hi there", that means &lt;code&gt;~/.bashrc&lt;/code&gt; is being used! Hooray!&lt;/li&gt;
&lt;li&gt;Otherwise remove it and try the same thing with &lt;code&gt;~/.bash_profile&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;You can also try &lt;code&gt;~/.profile&lt;/code&gt; if the first two options don't work.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;This article also reminded me to &lt;a href="https://simonwillison.net/2024/Oct/15/path-tips-on-wizard-zines/"&gt;try which -a again&lt;/a&gt;, which gave me this confusing result for &lt;code&gt;datasette&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;% which -a datasette
/opt/homebrew/Caskroom/miniconda/base/bin/datasette
/Users/simon/.local/bin/datasette
/Users/simon/.local/bin/datasette
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Why is the second path in there twice? I figured out how to use &lt;code&gt;rg&lt;/code&gt; to search just the dot-files in my home directory:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rg local/bin -g '/.*' --max-depth 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And found that I have both a &lt;code&gt;.zshrc&lt;/code&gt; and &lt;code&gt;.zprofile&lt;/code&gt; file that are adding that to my path:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.zshrc.backup
4:export PATH="$PATH:/Users/simon/.local/bin"

.zprofile
5:export PATH="$PATH:/Users/simon/.local/bin"

.zshrc
7:export PATH="$PATH:/Users/simon/.local/bin"
&lt;/code&gt;&lt;/pre&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/bash"&gt;bash&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/julia-evans"&gt;julia-evans&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ripgrep"&gt;ripgrep&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/zsh"&gt;zsh&lt;/a&gt;&lt;/p&gt;



</summary><category term="bash"/><category term="julia-evans"/><category term="ripgrep"/><category term="zsh"/></entry><entry><title>Weeknotes: Datasette 0.63.3, datasette-ripgrep</title><link href="https://simonwillison.net/2022/Dec/20/weeknotes/#atom-tag" rel="alternate"/><published>2022-12-20T14:54:35+00:00</published><updated>2022-12-20T14:54:35+00:00</updated><id>https://simonwillison.net/2022/Dec/20/weeknotes/#atom-tag</id><summary type="html">
    &lt;p&gt;We're back in the UK to see family over Christmas (our first trip back since 2019). Here are a few notes from the past couple of weeks.&lt;/p&gt;
&lt;h4&gt;Datasette 0.63.3&lt;/h4&gt;
&lt;p&gt;In addition to the Datasette 1.02a2 alpha (described &lt;a href="https://simonwillison.net/2022/Dec/15/datasette-1a2/"&gt;in detail here&lt;/a&gt;) I also published a small bug fix release for the 0.63.x stable branch. Quoting &lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-63-3"&gt;the release notes&lt;/a&gt; in full:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Fixed a bug where &lt;code&gt;datasette --root&lt;/code&gt;, when running in Docker, would only output the URL to sign in as root when the server shut down, not when it started up. (&lt;a href="https://github.com/simonw/datasette/issues/1958"&gt;#1958&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;You no longer need to ensure &lt;code&gt;await datasette.invoke_startup()&lt;/code&gt; has been called in order for Datasette to start correctly serving requests - this is now handled automatically the first time the server receives a request. This fixes a bug experienced when Datasette is served directly by an ASGI application server such as Uvicorn or Gunicorn. It also fixes a bug with the &lt;a href="https://datasette.io/plugins/datasette-gunicorn"&gt;datasette-gunicorn&lt;/a&gt; plugin. (&lt;a href="https://github.com/simonw/datasette/issues/1955"&gt;#1955&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;That second fix ended up taking longer than expected.&lt;/p&gt;
&lt;p&gt;The root of that fix was that back in Datasette 0.63 I introduced the need to call &lt;code&gt;await datasette.invoke_startup()&lt;/code&gt; as part of Datasette's setup process - mainly to trigger plugins that might need to run their own async setup code.&lt;/p&gt;
&lt;p&gt;This turned out to break a bunch of unexpected things - most notably, it affected any time people wanted to run Datasette using an ASGI handler such us Gunicorn or Uvicorn.&lt;/p&gt;
&lt;p&gt;It broke my own &lt;a href="https://datasette.io/plugins/datasette-gunicorn"&gt;datasette-gunicorn&lt;/a&gt; plugin too.&lt;/p&gt;
&lt;p&gt;The core problem was that the &lt;code&gt;Datasette()&lt;/code&gt; class constructor can be called synchronously, but needed a subsequent &lt;code&gt;await ...&lt;/code&gt; call to run those &lt;code&gt;async def&lt;/code&gt; setup methods.&lt;/p&gt;
&lt;p&gt;I realized that a neater way to handle this would be to introduce a mechanism such that the first time anyone attempted to run an HTTP request through Datasette - an operation that always involved an &lt;code&gt;await&lt;/code&gt; - the &lt;code&gt;invoke_startup()&lt;/code&gt; method would be called automatically.&lt;/p&gt;
&lt;p&gt;I got that working, but in doing so I ran into a longer-running set of problems.&lt;/p&gt;
&lt;p&gt;Datasette has around 1,200 tests at this point, and parts of the test suite date back to the start of the project and no longer reflect my preferred way of writing tests.&lt;/p&gt;
&lt;p&gt;I've started running into "too many open files" errors running the test suite on macOS, and have so far not quite tracked down the best way to keep open file handles under control.&lt;/p&gt;
&lt;p&gt;Test failures were hampering my efforts to fix the issue, so I used this as the impetus to refactor a large chunk of the test suite.&lt;/p&gt;
&lt;p&gt;Several hundred of Datasette's tests &lt;a href="https://github.com/simonw/datasette/issues/1959"&gt;now share&lt;/a&gt; a single in-memory fixtures database - previously, these tests were using a &lt;code&gt;fixtures.db&lt;/code&gt; database file created in a temporary directory.&lt;/p&gt;
&lt;p&gt;There's still more test refactoring that I want to do, described &lt;a href="https://github.com/simonw/datasette/issues/1962"&gt;in this issue&lt;/a&gt;, but I'm happy with the progress I've made so far.&lt;/p&gt;
&lt;h4&gt;datasette-ripgrep, cosmetic upgrade&lt;/h4&gt;
&lt;p&gt;I built &lt;a href="https://simonwillison.net/2020/Nov/28/datasette-ripgrep/"&gt;datasette-ripgrep a couple of years ago&lt;/a&gt; - it's a Datasette plugin that provides a UI for running &lt;code&gt;ripgrep&lt;/code&gt; code search queries - and linking to the results. It's very handy for finding uses of APIs that I might want to deprecate.&lt;/p&gt;
&lt;p&gt;In using it to &lt;a href="https://github.com/simonw/datasette/issues/1949"&gt;investigate Datasette's error output&lt;/a&gt; I spotted that the results would be more readable if they &lt;a href="https://github.com/simonw/datasette-ripgrep/issues/27"&gt;included a gap between non-consecutive line numbers&lt;/a&gt;, so I shipped an update with that improvement.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2022/datasette-ripgrep.jpg" alt="Screenshot of some code search results - matching lines are highlighted in yellow and there are small gaps between non-consecutive groups of line numbers" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-gunicorn"&gt;datasette-gunicorn&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-gunicorn/releases/tag/0.1.1"&gt;0.1.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-gunicorn/releases"&gt;2 releases total&lt;/a&gt;) - 2022-12-18
&lt;br /&gt;Plugin for running Datasette using Gunicorn&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette"&gt;datasette&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette/releases/tag/0.63.3"&gt;0.63.3&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette/releases"&gt;122 releases total&lt;/a&gt;) - 2022-12-18
&lt;br /&gt;An open source multi-tool for exploring and publishing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-ripgrep"&gt;datasette-ripgrep&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-ripgrep/releases/tag/0.8"&gt;0.8&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-ripgrep/releases"&gt;13 releases total&lt;/a&gt;) - 2022-12-15
&lt;br /&gt;Web interface for searching your code using ripgrep, built as a Datasette plugin&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-media"&gt;datasette-media&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-media/releases/tag/0.5.1"&gt;0.5.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-media/releases"&gt;7 releases total&lt;/a&gt;) - 2022-12-13
&lt;br /&gt;Datasette plugin for serving media based on a SQL query&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-secret-santa"&gt;datasette-secret-santa&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-secret-santa/releases/tag/0.1"&gt;0.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-secret-santa/releases"&gt;2 releases total&lt;/a&gt;) - 2022-12-11
&lt;br /&gt;Run secret santa gift circles using Datasette&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-render-binary"&gt;datasette-render-binary&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-render-binary/releases/tag/0.3.1"&gt;0.3.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-render-binary/releases"&gt;4 releases total&lt;/a&gt;) - 2022-12-10
&lt;br /&gt;Datasette plugin for rendering binary data&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/github/github-code-search-api-uses"&gt;Finding uses of an API with the new GitHub Code Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/gpt3/reformatting-text-with-copilot"&gt;Reformatting text with Copilot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/pytest/show-files-opened-by-tests"&gt;Show files opened by pytest tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/spatialite/viewing-geopackage-data-with-spatialite-and-datasette"&gt;Viewing GeoPackage data with SpatiaLite and Datasette&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/sqlite/multiple-indexes"&gt;SQLite can use more than one index for a query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/sqlite/compare-before-after-json"&gt;Comparing database rows before and after with SQLite JSON functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/bash/start-test-then-stop-server"&gt;Start, test, then stop a localhost web server in a Bash script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ripgrep"&gt;ripgrep&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/><category term="weeknotes"/><category term="ripgrep"/></entry><entry><title>Weeknotes: github-to-sqlite workflows, datasette-ripgrep enhancements, Datasette 0.52</title><link href="https://simonwillison.net/2020/Dec/6/weeknotes/#atom-tag" rel="alternate"/><published>2020-12-06T05:46:11+00:00</published><updated>2020-12-06T05:46:11+00:00</updated><id>https://simonwillison.net/2020/Dec/6/weeknotes/#atom-tag</id><summary type="html">
    &lt;p&gt;This week: Improvements to &lt;code&gt;datasette-ripgrep&lt;/code&gt;, &lt;code&gt;github-to-sqlite&lt;/code&gt; and &lt;code&gt;datasette-graphql&lt;/code&gt;, plus Datasette 0.52 and a flurry of dot-releases.&lt;/p&gt;
&lt;h4&gt;datasette-ripgrep 0.5 and 0.6&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/datasette-ripgrep"&gt;datasette-ripgrep&lt;/a&gt; (introduced &lt;a href="https://simonwillison.net/2020/Nov/28/datasette-ripgrep/"&gt;last week&lt;/a&gt;) landed &lt;a href="https://news.ycombinator.com/item?id=25236636"&gt;on Hacker News&lt;/a&gt;, and the comments there inspired me to build a few new features. The interface looks like this now:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ripgrep.datasette.io/-/ripgrep?pattern=client.*cookies%3D&amp;amp;glob=test*.py"&gt;&lt;img src="https://static.simonwillison.net/static/2020/ripgrep__client__cookies_.png" alt="Screenshot showing search results for a client.cookies= against files matching test.py" style="max-width:100%;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I added options for literal searches (as opposed to a regex match) and ignoring case, and a field that lets you filter to just a specific file pattern, for example &lt;code&gt;test*.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These are already features of &lt;a href="https://github.com/BurntSushi/ripgrep/blob/master/GUIDE.md"&gt;ripgrep&lt;/a&gt; so adding them was a case of hooking up the interface and using it to modify the command-line arguments passed to the underlying tool.&lt;/p&gt;
&lt;h4&gt;github-to-sqlite workflows&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/dogsheep/github-to-sqlite"&gt;github-to-sqlite&lt;/a&gt; is my command-line tool for importing data from the GitHub API into a SQLite database, for analysis with Datasette.&lt;/p&gt;
&lt;p&gt;I released &lt;a href="https://github.com/dogsheep/github-to-sqlite/releases/tag/2.8"&gt;github-to-sqlite 2.8&lt;/a&gt; this week  with two new commands: &lt;code&gt;github-to-sqlite pull-requests&lt;/code&gt;, contributed by &lt;a href="https://github.com/adamjonas"&gt;Adam Jonas&lt;/a&gt;, and &lt;code&gt;github-to-sqlite workflows&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The new &lt;code&gt;workflows&lt;/code&gt; command can be run against one or more repositories and will fetch their GitHub Actions workflow YAML files, parse them and use them to populate new database tables called &lt;code&gt;workflows&lt;/code&gt;, &lt;code&gt;jobs&lt;/code&gt; and &lt;code&gt;steps&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you run workflows across a bunch of different repositories this means you can analyze your workflow usage using SQL!&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://github-to-sqlite.dogsheep.net/github"&gt;github-to-sqlite demo&lt;/a&gt; now includes workflows from my core Datasette and Dogsheep projects. Some example queries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;My &lt;a href="https://github-to-sqlite.dogsheep.net/github/steps?_facet=uses"&gt;most commonly used action steps&lt;/a&gt; - the top two are &lt;code&gt;actions/checkout@v2&lt;/code&gt; and &lt;code&gt;actions/cache@v2&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;All &lt;a href="https://github-to-sqlite.dogsheep.net/github/steps?_facet=uses&amp;amp;uses=actions%2Fcache%40v1"&gt;steps using actions/cache@v1&lt;/a&gt;, which need to be upgraded to &lt;code&gt;v2&lt;/code&gt; (this link will likely soon stop returning any results as I apply those updates).&lt;/li&gt;
&lt;li&gt;My workflows that &lt;a href="https://github-to-sqlite.dogsheep.net/github/workflows?_sort=id&amp;amp;on__contains=workflow_dispatch"&gt;use the workflow_dispatch&lt;/a&gt; trigger.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;a href="https://github.com/dogsheep/github-to-sqlite/blob/main/github_to_sqlite/utils.py#L787-L858"&gt;implementation&lt;/a&gt; is a good example of my &lt;a href="https://sqlite-utils.readthedocs.io/en/stable/python-api.html"&gt;sqlite-utils&lt;/a&gt; library in action - I pass the extracted YAML data straight to the &lt;code&gt;.insert(data, alter=True)&lt;/code&gt; method which creates the correct table schema automatically, altering it if there are any missing columns.&lt;/p&gt;
&lt;h4&gt;datasette-graphql 1.3&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/datasette-graphql/releases/tag/1.3"&gt;datasette-graphql 1.3&lt;/a&gt; has one tiny feature which I find enormously satisfying.&lt;/p&gt;
&lt;p&gt;The plugin provides a GraphQL interface to any table in Datasette. The latest versions use the new "table actions" menu (accessible through a cog icon in the page heading) to provide a link to an example query for that table.&lt;/p&gt;
&lt;p&gt;I added the example queries in &lt;a href="https://github.com/simonw/datasette-graphql/releases/tag/1.3"&gt;1.2&lt;/a&gt;, but in 1.3 the example has been expanded to include examples of foreign key references. For a table like this one of &lt;a href="https://github-to-sqlite.dogsheep.net/github/commits"&gt;GitHub commits&lt;/a&gt; the example query now looks &lt;a href="https://github-to-sqlite.dogsheep.net/graphql?query=%7B%0A%20%20commits%20%7B%0A%20%20%20%20totalCount%0A%20%20%20%20pageInfo%20%7B%0A%20%20%20%20%20%20hasNextPage%0A%20%20%20%20%20%20endCursor%0A%20%20%20%20%7D%0A%20%20%20%20nodes%20%7B%0A%20%20%20%20%20%20sha%0A%20%20%20%20%20%20message%0A%20%20%20%20%20%20author_date%0A%20%20%20%20%20%20committer_date%0A%20%20%20%20%20%20raw_author%20%7B%0A%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20name%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20raw_committer%20%7B%0A%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20name%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20repo%20%7B%0A%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20name%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20author%20%7B%0A%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20login%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20committer%20%7B%0A%20%20%20%20%20%20%20%20id%0A%20%20%20%20%20%20%20%20login%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D"&gt;like this&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-graphql"&gt;&lt;pre&gt;{
  &lt;span class="pl-v"&gt;commits&lt;/span&gt; {
    &lt;span class="pl-v"&gt;totalCount&lt;/span&gt;
    &lt;span class="pl-v"&gt;pageInfo&lt;/span&gt; {
      &lt;span class="pl-v"&gt;hasNextPage&lt;/span&gt;
      &lt;span class="pl-v"&gt;endCursor&lt;/span&gt;
    }
    &lt;span class="pl-v"&gt;nodes&lt;/span&gt; {
      &lt;span class="pl-v"&gt;sha&lt;/span&gt;
      &lt;span class="pl-v"&gt;message&lt;/span&gt;
      &lt;span class="pl-v"&gt;author_date&lt;/span&gt;
      &lt;span class="pl-v"&gt;committer_date&lt;/span&gt;
      &lt;span class="pl-v"&gt;raw_author&lt;/span&gt; {
        &lt;span class="pl-v"&gt;id&lt;/span&gt;
        &lt;span class="pl-v"&gt;name&lt;/span&gt;
      }
      &lt;span class="pl-v"&gt;raw_committer&lt;/span&gt; {
        &lt;span class="pl-v"&gt;id&lt;/span&gt;
        &lt;span class="pl-v"&gt;name&lt;/span&gt;
      }
      &lt;span class="pl-v"&gt;repo&lt;/span&gt; {
        &lt;span class="pl-v"&gt;id&lt;/span&gt;
        &lt;span class="pl-v"&gt;name&lt;/span&gt;
      }
      &lt;span class="pl-v"&gt;author&lt;/span&gt; {
        &lt;span class="pl-v"&gt;id&lt;/span&gt;
        &lt;span class="pl-v"&gt;login&lt;/span&gt;
      }
      &lt;span class="pl-v"&gt;committer&lt;/span&gt; {
        &lt;span class="pl-v"&gt;id&lt;/span&gt;
        &lt;span class="pl-v"&gt;login&lt;/span&gt;
      }
    }
  }
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The new foreign key references include the repo, author and committer fields. The example query now demonstrates the most interesting feature of &lt;code&gt;datasette-graphql&lt;/code&gt; - its ability to automatically &lt;a href="https://github.com/simonw/datasette-graphql#accessing-nested-objects"&gt;convert foreign key relationships&lt;/a&gt; in your database into nested GraphQL fields.&lt;/p&gt;
&lt;p&gt;It's a small change, but it makes me really happy.&lt;/p&gt;
&lt;h4&gt;Datasette 0.52&lt;/h4&gt;
&lt;p&gt;I shipped &lt;a href=""&gt;Datasette 0.52&lt;/a&gt;, a relatively minor release which mainly kicked off an effort to rebrand "configuration" as "settings".&lt;/p&gt;
&lt;p&gt;I'm doing this as part of my effort to fix Datasette's "metadata" concept. The &lt;code&gt;metadata.json&lt;/code&gt; file started out as a way to add metadata - title, description, license and source information. Over time the file expanded to cover things like default facet displays and sort orders... and then when plugins came along it grew to cover plugin configuration as well.&lt;/p&gt;
&lt;p&gt;This is really confusing. Editing &lt;code&gt;metadata.json&lt;/code&gt; to configure a plugin doesn't make a great deal of sense.&lt;/p&gt;
&lt;p&gt;For Datasette 1.0 I want to clean this up. I'm planning on splitting metadata and configuration into separate mechanisms.&lt;/p&gt;
&lt;p&gt;There's just one problem: Datasette already has a "configuration" concept in the form of the &lt;code&gt;--config&lt;/code&gt; command-line option which can be used to set some very fundamental options for the Datasette server - things like the SQL time limit and the maximum allowed CSV download size.&lt;/p&gt;
&lt;p&gt;I want to call plugin configuration settings "configuration", so I've renamed &lt;code&gt;--config&lt;/code&gt; to &lt;code&gt;--settings&lt;/code&gt; - see the new &lt;a href="https://docs.datasette.io/en/stable/settings.html"&gt;settings documentation&lt;/a&gt; for details.&lt;/p&gt;
&lt;p&gt;This also gave me the chance to clean up a weird design decision. Datasette's configuration options looked like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette data.db --config sql_time_limit_ms:1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The new &lt;code&gt;--setting&lt;/code&gt; replacement instead looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette data.db --setting sql_time_limit_ms 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the lack of a colon here - having an option take two arguments is a perfectly cromulent way of using &lt;a href="https://click.palletsprojects.com/en/7.x/"&gt;Click&lt;/a&gt;, but it's one I wasn't aware of when I first released Datasette.&lt;/p&gt;
&lt;p&gt;The old &lt;code&gt;--config&lt;/code&gt; mechanism continues to work, but it now displays a deprecation warning - it will be removed in Datasette 1.0.&lt;/p&gt;
&lt;h4&gt;Datasette dot-releases&lt;/h4&gt;
&lt;p&gt;0.52 has already had more dot-releases than any other version of Datasette. These are all pure bug fixes, mostly for obscure bugs that are unlikely to have affected anyone. To summarize the &lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-52-4"&gt;release notes&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-52-1"&gt;0.52.1&lt;/a&gt; updated the &lt;a href="https://docs.datasette.io/en/stable/testing_plugins.html#testing-plugins"&gt;testing plugins documentation&lt;/a&gt; to promote &lt;code&gt;datasette.client&lt;/code&gt;, fixed a bug with the display of compound foreign keys and improved the locations searched by the &lt;code&gt;datasette --load-module=spatialite&lt;/code&gt; shortcut.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-52-2"&gt;0.52.2&lt;/a&gt; fixed support for the generated columns feature added in &lt;a href="https://www.sqlite.org/releaselog/3_31_1.html"&gt;SQLite 3.31.0&lt;/a&gt;, fixed a 500 error on &lt;code&gt;OPTIONS&lt;/code&gt; requests, added support for &amp;gt;32MB database file downloads on Cloud Run and shipped a CSS fix to the cog menus contributed by Abdussamet Koçak.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-52-3"&gt;0.52.3&lt;/a&gt; fixed a fun bug with Datasette installed on Amazon Linux running on ARM where static assets would 404. I eventually &lt;a href="https://github.com/simonw/datasette/issues/1124"&gt;tracked that down&lt;/a&gt; to an unexpected symlink in the site-packages directory.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.datasette.io/en/stable/changelog.html#v0-52-4"&gt;0.52.4&lt;/a&gt; now writes errors logged by Datasette to &lt;code&gt;stderr&lt;/code&gt;, not &lt;code&gt;stdout&lt;/code&gt;. It also fixes a startup error on Windows, another contribution from Abdussamet Koçak.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Broken Dogsheep&lt;/h4&gt;
&lt;p&gt;My personal &lt;a href="https://simonwillison.net/2020/Nov/14/personal-data-warehouses/"&gt;Dogsheep&lt;/a&gt; broke this week. I've been running it on an Amazon Lightsail instance, and this week I learned that Lightsail has a &lt;a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-viewing-instance-burst-capacity"&gt;baseline CPU&lt;/a&gt; mechanism which grants your instance burst capacity but shuts it down if it exceeds that capacity too often!&lt;/p&gt;
&lt;p&gt;So I'm moving it to a DigitalOcean droplet which won't do that, and trying to figure out enough &lt;a href="https://docs.ansible.com/ansible/latest/index.html"&gt;Ansible&lt;/a&gt; to completely automate the process.&lt;/p&gt;
&lt;p&gt;My ideal server is one that is configured entirely from files in source control, and updates itself by pulling new configuration from that repository. I plan to use &lt;a href="https://docs.ansible.com/ansible/latest/cli/ansible-pull.html"&gt;ansible-pull&lt;/a&gt; for this, once I've put together the necessary playbooks.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/graphql"&gt;graphql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/dogsheep"&gt;dogsheep&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ripgrep"&gt;ripgrep&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="graphql"/><category term="datasette"/><category term="dogsheep"/><category term="weeknotes"/><category term="ripgrep"/></entry><entry><title>datasette-ripgrep: deploy a regular expression search engine for your source code</title><link href="https://simonwillison.net/2020/Nov/28/datasette-ripgrep/#atom-tag" rel="alternate"/><published>2020-11-28T06:51:06+00:00</published><updated>2020-11-28T06:51:06+00:00</updated><id>https://simonwillison.net/2020/Nov/28/datasette-ripgrep/#atom-tag</id><summary type="html">
    &lt;p&gt;This week I built &lt;a href="https://github.com/simonw/datasette-ripgrep"&gt;datasette-ripgrep&lt;/a&gt; - a web application  for running regular expression searches against source code, built on top of the amazing &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt; command-line tool.&lt;/p&gt;
&lt;h4&gt;datasette-ripgrep demo&lt;/h4&gt;
&lt;p&gt;I've deployed a demo version of the application here:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ripgrep.datasette.io/-/ripgrep?pattern=pytest"&gt;ripgrep.datasette.io/-/ripgrep?pattern=pytest&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The demo runs searches against the source code of every one of my GitHub repositories that start with &lt;code&gt;datasette&lt;/code&gt; - &lt;a href="https://github-to-sqlite.dogsheep.net/github/repos?name__startswith=datasette&amp;amp;owner__exact=9599"&gt;61 repos&lt;/a&gt; right now - so it should include all of my Datasette plugins plus the core Datasette repository itself.&lt;/p&gt;
&lt;p&gt;Since it's running on top of &lt;code&gt;ripgrep&lt;/code&gt;, it supports regular expressions. This is absurdly useful. Some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every usage of the &lt;code&gt;.plugin_config(&lt;/code&gt; method: &lt;a href="https://ripgrep.datasette.io/-/ripgrep?pattern=%5C.plugin_config%5C%28"&gt;plugin_config\(&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Everywhere I use &lt;code&gt;async with httpx.AsyncClient&lt;/code&gt; (usually in tests): &lt;a href="https://ripgrep.datasette.io/-/ripgrep?pattern=async+with.*AsyncClient"&gt;async with.*AsyncClient&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;All places where I use a Jinja &lt;code&gt;|&lt;/code&gt; filter inside a variable: &lt;a href="https://ripgrep.datasette.io/-/ripgrep?pattern=%5C%7B%5C%7B.*%5C%7C.*%5C%7D%5C%7D"&gt;\{\{.*\|.*\}\}&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I usually run ripgrep as &lt;code&gt;rg&lt;/code&gt; on the command-line, or use it within Visual Studio Code (&lt;a href="https://twitter.com/simonw/status/1331381448171929600"&gt;fun fact&lt;/a&gt;: the reason VS Code's "Find in Files" is so good is it's running ripgrep under the hood).&lt;/p&gt;
&lt;p&gt;So why have it as a web application? Because this means I can link to it, bookmark it and use it on my phone.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2020/datasette-ripgrep.png" alt="A screenshot of datasette-ripgrep in action" style="max-width: 100%" /&gt;&lt;/p&gt;
&lt;h4&gt;Why build this?&lt;/h4&gt;
&lt;p&gt;There are plenty of great existing code search tools out there already: I've heard great things about &lt;a href="https://github.com/livegrep/livegrep"&gt;livegrep&lt;/a&gt;, and a quick Google search shows a bunch of other options.&lt;/p&gt;
&lt;p&gt;Aside from being a fun project, &lt;code&gt;datasette-ripgrep&lt;/code&gt; has one key advantage: it gets to benefit from Datasette's publishing mechanism, which means it's really easy to deploy.&lt;/p&gt;
&lt;p&gt;That &lt;a href="https://ripgrep.datasette.io/"&gt;ripgrep.datasette.io&lt;/a&gt; demo is deployed by checking out the source code to be searched into a &lt;code&gt;all&lt;/code&gt; directory and then using the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette publish cloudrun \
    --metadata metadata.json \
    --static all:all \
    --install=datasette-ripgrep \
    --service datasette-ripgrep \
    --apt-get-install ripgrep
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;all&lt;/code&gt; is a folder containing the source code to be searched. &lt;code&gt;metadata.json&lt;/code&gt; contains this:&lt;/p&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;plugins&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;: {
        &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;datasette-ripgrep&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;: {
            &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;path&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;/app/all&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
            &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;time_limit&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;: &lt;span class="pl-c1"&gt;3.0&lt;/span&gt;
        }
    }
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's all there is to it! The result is a deployed code search engine, running on Google Cloud Run.&lt;/p&gt;
&lt;p&gt;(If you want to try this yourself you'll need to be using the just-released Datasette 0.52.)&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/simonw/datasette-ripgrep/blob/main/.github/workflows/deploy_demo.yml"&gt;GitHub Action workflow&lt;/a&gt; that deploys the demo also uses my &lt;a href="https://github.com/dogsheep/github-to-sqlite"&gt;github-to-sqlite&lt;/a&gt; tool to fetch my repos and then shallow-clones the ones that begin with &lt;code&gt;datasette&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you have &lt;a href="https://docs.datasette.io/en/stable/publish.html#publishing-to-google-cloud-run"&gt;your own Google Cloud Run credentials&lt;/a&gt;, you can run your own copy of that workflow against your own repositories.&lt;/p&gt;
&lt;h4&gt;A different kind of Datasette plugin&lt;/h4&gt;
&lt;p&gt;Datasette is a tool for publishing SQLite databases, so most Datasette plugins integrate with SQLite in some way.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;datasette-ripgrep&lt;/code&gt; is different: it makes no use of SQLite at all, but instead takes advantage of Datasette's URL routing, &lt;code&gt;datasette publish&lt;/code&gt; deployments and permissions system.&lt;/p&gt;
&lt;p&gt;The plugin implementation is currently &lt;a href="https://github.com/simonw/datasette-ripgrep/blob/07b9ced2935b0b6080c1c42fcaf6ab9e8003d186/datasette_ripgrep/__init__.py"&gt;134 lines of code&lt;/a&gt;, excluding tests and templates.&lt;/p&gt;
&lt;p&gt;While the plugin doesn't use SQLite, it does share a common philosophy with Datasette: the plugin bundles the source code that it is going to search as part of the deployed application, in a similar way to how Datasette usually bundles one or more SQLite database files.&lt;/p&gt;
&lt;p&gt;As such, it's extremely inexpensive to run and can be deployed to serverless hosting. If you need to scale it, you can run more copies.&lt;/p&gt;
&lt;p&gt;This does mean that the application needs to be re-deployed to pick up changes to the searchable code. I'll probably set my demo to do this on a daily basis.&lt;/p&gt;
&lt;h4&gt;Controlling processes from asyncio&lt;/h4&gt;
&lt;p&gt;The trickiest part of the implementation was figuring out how to use Python's &lt;code&gt;asyncio.create_subprocess_exec()&lt;/code&gt; method to safely run the &lt;code&gt;rg&lt;/code&gt; process in response to incoming requests.&lt;/p&gt;
&lt;p&gt;I don't want expensive searches to tie up the server, so I implemented two limits here. The first is a time limit: by default, searches have a second to run after which the &lt;code&gt;rg&lt;/code&gt; process will be terminated and only results recieved so far will be returned. This is achieved using the &lt;a href="https://docs.python.org/3/library/asyncio-task.html#asyncio.wait_for"&gt;asyncio.wait_for()&lt;/a&gt; function.&lt;/p&gt;
&lt;p&gt;I also implemented a limit on the number of matching lines that can be returned, defaulting to 2,000. Any more than that and the process is terminated early.&lt;/p&gt;
&lt;p&gt;Both of these limits can be customized using plugin settings (documented in &lt;a href="https://github.com/simonw/datasette-ripgrep/blob/main/README.md"&gt;the README&lt;/a&gt;). You can see how they are implemented in the &lt;a href="https://github.com/simonw/datasette-ripgrep/blob/0.2/datasette_ripgrep/__init__.py#L9-L55"&gt;async def run_ripgrep(pattern, path, time_limit=1.0, max_lines=2000)&lt;/a&gt; function.&lt;/p&gt;
&lt;h4&gt;Highlighted linkable line numbers&lt;/h4&gt;
&lt;p&gt;The other fun implementation detail is the way the source code listings are displayed. I'm using CSS to display the line numbers in a way that makes them visible without them breaking copy-and-paste (inspired by &lt;a href="https://www.sylvaindurand.org/using-css-to-add-line-numbering/"&gt;this article by Sylvain Durand&lt;/a&gt;).&lt;/p&gt;
&lt;div class="highlight highlight-source-css"&gt;&lt;pre&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt;:&lt;span class="pl-c1"&gt;before&lt;/span&gt; {
    &lt;span class="pl-c1"&gt;content&lt;/span&gt;: &lt;span class="pl-en"&gt;attr&lt;/span&gt;(data-line);
    &lt;span class="pl-c1"&gt;display&lt;/span&gt;: inline-block;
    &lt;span class="pl-c1"&gt;width&lt;/span&gt;: &lt;span class="pl-c1"&gt;3.5&lt;span class="pl-smi"&gt;ch&lt;/span&gt;&lt;/span&gt;;
    &lt;span class="pl-c1"&gt;-webkit-user-select&lt;/span&gt;: none;
    &lt;span class="pl-c1"&gt;color&lt;/span&gt;: &lt;span class="pl-pds"&gt;&lt;span class="pl-kos"&gt;#&lt;/span&gt;666&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The HTML looks like this:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;pre&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt; &lt;span class="pl-c1"&gt;id&lt;/span&gt;="&lt;span class="pl-s"&gt;L1&lt;/span&gt;" &lt;span class="pl-c1"&gt;data-line&lt;/span&gt;="&lt;span class="pl-s"&gt;1&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;from setuptools import setup&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt; &lt;span class="pl-c1"&gt;id&lt;/span&gt;="&lt;span class="pl-s"&gt;L2&lt;/span&gt;" &lt;span class="pl-c1"&gt;data-line&lt;/span&gt;="&lt;span class="pl-s"&gt;2&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;import os&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt; &lt;span class="pl-c1"&gt;id&lt;/span&gt;="&lt;span class="pl-s"&gt;L3&lt;/span&gt;" &lt;span class="pl-c1"&gt;data-line&lt;/span&gt;="&lt;span class="pl-s"&gt;3&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;&amp;amp;nbsp;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt; &lt;span class="pl-c1"&gt;id&lt;/span&gt;="&lt;span class="pl-s"&gt;L4&lt;/span&gt;" &lt;span class="pl-c1"&gt;data-line&lt;/span&gt;="&lt;span class="pl-s"&gt;4&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;VERSION = &amp;amp;#34;0.1&amp;amp;#34;&lt;span class="pl-kos"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="pl-ent"&gt;code&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
...&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I wanted to imitate GitHub's handling of line links, where adding &lt;code&gt;#L23&lt;/code&gt; to the URL both jumps to that line and causes the line to be highlighted. Here's &lt;a href="https://ripgrep.datasette.io/-/ripgrep/view/datasette-allow-permissions-debug/setup.py#L23"&gt;a demo of that&lt;/a&gt; - I use the following JavaScript to update the contents of a &lt;code&gt;&amp;lt;style id="highlightStyle"&amp;gt;&amp;lt;/style&amp;gt;&lt;/code&gt; element in the document head any time the URL fragment changes:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;
var highlightStyle = document.getElementById('highlightStyle');
function highlightLineFromFragment() &lt;span class="pl-kos"&gt;{&lt;/span&gt;
    &lt;span class="pl-en"&gt;if&lt;/span&gt; &lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-pds"&gt;/&lt;span class="pl-cce"&gt;^&lt;/span&gt;#L&lt;span class="pl-cce"&gt;\d&lt;/span&gt;&lt;span class="pl-c1"&gt;+&lt;/span&gt;&lt;span class="pl-cce"&gt;$&lt;/span&gt;/&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;exec&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;location&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;hash&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
        &lt;span class="pl-s1"&gt;highlightStyle&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;innerText&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;`&lt;span class="pl-s1"&gt;&lt;span class="pl-kos"&gt;${&lt;/span&gt;&lt;span class="pl-s1"&gt;location&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-c1"&gt;hash&lt;/span&gt;&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;/span&gt; { background-color: yellow; }`&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;
&lt;span class="pl-en"&gt;highlightLineFromFragment&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-smi"&gt;window&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;addEventListener&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"hashchange"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;highlightLineFromFragment&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-c1"&gt;&amp;lt;&lt;/span&gt;/&lt;span class="pl-ent"&gt;script&lt;/span&gt;&lt;span class="pl-c1"&gt;&amp;gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It's the simplest way I could think of to achieve this effect.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 28th November 2020&lt;/strong&gt;: Louis Lévêque on Twitter suggested using the CSS &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/:target"&gt;:target selector&lt;/a&gt; instead, which is indeed MUCH simpler - I deleted the above JavaScript and replaced it with this CSS:&lt;/p&gt;
&lt;div class="highlight highlight-source-css"&gt;&lt;pre&gt;:&lt;span class="pl-c1"&gt;target&lt;/span&gt; {
    &lt;span class="pl-c1"&gt;background-color&lt;/span&gt;: &lt;span class="pl-pds"&gt;&lt;span class="pl-kos"&gt;#&lt;/span&gt;FFFF99&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;
&lt;h4&gt;Next steps for this project&lt;/h4&gt;
&lt;p&gt;I'm pleased to have got &lt;a href=""&gt;datasette-ripgrep&lt;/a&gt; to a workable state, and I'm looking forward to using it to answer questions about the growing Datasette ecosystem. I don't know how much more time I'll invest in this - if it proves useful then I may well expand it.&lt;/p&gt;
&lt;p&gt;I do think there's something really interesting about being able to spin up this kind of code search engine on demand using &lt;code&gt;datasette publish&lt;/code&gt;. It feels like a very useful trick to have access to.&lt;/p&gt;
&lt;h4&gt;Better URLs for my TILs&lt;/h4&gt;
&lt;p&gt;My other project this week was an upgrade to &lt;a href="https://til.simonwillison.net/"&gt;til.simonwillison.net&lt;/a&gt;: I finally spent the time to &lt;a href="https://github.com/simonw/til/issues/34"&gt;design nicer URLs&lt;/a&gt; for the site.&lt;/p&gt;
&lt;p&gt;Before:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;til.simonwillison.net/til/til/javascript_manipulating-query-params.md&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;til.simonwillison.net/javascript/manipulating-query-params&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;The implementation for this takes advantage of a feature I sneaked into Datasette 0.49: &lt;a href="https://simonwillison.net/2020/Sep/15/datasette-0-49#path-parameters-custom-page-templates"&gt;Path parameters for custom page templates&lt;/a&gt;. I can create a template file called &lt;code&gt;pages/{topic}/{slug}.html&lt;/code&gt; and Datasette use that template to handle 404 errors that match that pattern.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/simonw/til/blob/main/templates/pages/%7Btopic%7D/%7Bslug%7D.html"&gt;the new pages/{topic}/{slug}.html&lt;/a&gt; template for my TIL site. It uses the &lt;code&gt;sql()&lt;/code&gt; template function from the &lt;a href="https://github.com/simonw/datasette-template-sql"&gt;datasette-template-sql&lt;/a&gt; plugin to retrieve and render the matching TIL, or raises a 404 if no TIL can be found.&lt;/p&gt;
&lt;p&gt;I also needed to setup redirects from the old pages to the new ones. I wrote a &lt;a href="https://til.simonwillison.net/til/til/datasette_redirects-for-datasette.md"&gt;TIL on edirects for Datasette&lt;/a&gt; explaining how I did that.&lt;/p&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/til/til/datasette_redirects-for-datasette.md"&gt;Redirects for Datasette&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/datasette-ripgrep/releases/tag/0.2"&gt;datasette-ripgrep 0.2&lt;/a&gt; - 2020-11-27&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/datasette-ripgrep/releases/tag/0.1"&gt;datasette-ripgrep 0.1&lt;/a&gt; - 2020-11-26&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/datasette-atom/releases/tag/0.8.1"&gt;datasette-atom 0.8.1&lt;/a&gt; - 2020-11-25&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/datasette-ripgrep/releases/tag/0.1a1"&gt;datasette-ripgrep 0.1a1&lt;/a&gt; - 2020-11-25&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/datasette-ripgrep/releases/tag/0.1a0"&gt;datasette-ripgrep 0.1a0&lt;/a&gt; - 2020-11-25&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/datasette-graphql/releases/tag/1.2.1"&gt;datasette-graphql 1.2.1&lt;/a&gt; - 2020-11-24&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/async"&gt;async&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/css"&gt;css&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cloudrun"&gt;cloudrun&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ripgrep"&gt;ripgrep&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/baked-data"&gt;baked-data&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="async"/><category term="css"/><category term="projects"/><category term="python"/><category term="datasette"/><category term="weeknotes"/><category term="cloudrun"/><category term="ripgrep"/><category term="baked-data"/></entry><entry><title>How FZF and ripgrep improved my workflow</title><link href="https://simonwillison.net/2019/Jul/5/fzf/#atom-tag" rel="alternate"/><published>2019-07-05T17:51:09+00:00</published><updated>2019-07-05T17:51:09+00:00</updated><id>https://simonwillison.net/2019/Jul/5/fzf/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://medium.com/@sidneyliebrand/how-fzf-and-ripgrep-improved-my-workflow-61c7ca212861"&gt;How FZF and ripgrep improved my workflow&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I’m already a keen user of ripgrep (a crazy-fast grep alternative) but fzf was new to me: it’s a CLI utility that lets you pipe in a list of strings, then gives you a typeahead search interface to search and select a string before returning the selected string to stdout when you hit enter. This means you can pipe it together with other tools to add a dynamic selection step, which has all kinds of delightful combinations. “vi $(find . | fzf)” for example opens vi against the file you selected.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=20360204"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/unix"&gt;unix&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ripgrep"&gt;ripgrep&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="unix"/><category term="ripgrep"/></entry><entry><title>ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}</title><link href="https://simonwillison.net/2019/Apr/16/ripgrep/#atom-tag" rel="alternate"/><published>2019-04-16T17:52:53+00:00</published><updated>2019-04-16T17:52:53+00:00</updated><id>https://simonwillison.net/2019/Apr/16/ripgrep/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://blog.burntsushi.net/ripgrep/"&gt;ripgrep is faster than {grep, ag, git grep, ucg, pt, sift}&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Andrew Gallant's post from September 2016 introducing &lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt;, the command-line grep tool he wrote using Rust (on top of the Rust regular expression library also written by Andrew). &lt;code&gt;ripgrep&lt;/code&gt; is a beautifully designed CLI interface and is crazy fast, and this post describes how it gets its performance in a huge amount of detail, right down to comparing the different algorithmic approaches used by other similar tools.&lt;/p&gt;
&lt;p&gt;I recently learned that ripgrep ships as part of VS Code, which is why VS Code's search-across-project feature is so fast. In fact, if you dig around in the OS X package you can find the &lt;code&gt;rg&lt;/code&gt; binary already installed on your mac:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find /Applications/Visual* | grep bin/rg
&lt;/code&gt;&lt;/pre&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=19669789"&gt;Ripgrep 11 Released&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ripgrep"&gt;ripgrep&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/vs-code"&gt;vs-code&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/andrew-gallant"&gt;andrew-gallant&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="rust"/><category term="ripgrep"/><category term="vs-code"/><category term="andrew-gallant"/></entry></feed>