What?
I wrote a Nushell script that updates My Logseq Workflow with a current list of installed plugins.
Why?
Logseq plugins are installed at the user level, and do not currently sync across machines with a Git-based workflow. If I want a consistent workflow — and I do — I need to maintain this list myself, ideally somewhere that I can reference later. Also, I could work this on-demand script into other workflows, processing elements of my Logseq setup without needing to interact with the application itself.
How?
Everything we need to know can be determined by looking at the plugin files and reading some JSON. I’m going to use Nushell for this task, but just about anything should do the job. The same basic logic will work in any language with support for loading and processing JSON.
The ~/.logseq/
Folder
User-level Logseq config, including installed plugins, is at ~/.logseq
. This is also true on Windows, where the equivalent for me would be C:\Users\brian\.logseq
. path expand
turns that tilde into my $HOME
directory regardless of platform, so I won’t need to specify a different $logseq_folder
on every machine.
Load Installed Plugins and Themes
Logseq installs its plugins to ~/.logseq/plugins/
. Each plugin’s package.json
holds important project information such as title, repo, description, and theme hooks if any. Installation-specific settings, such as whether the plugin is disabled, are under ~/.logseq/settings
.
I need to use all those details to generate summary strings for every plugin. Something like this:
NOTE
I use the
heading
property rather than Markdown header markers. That way a section header looks like a level one header when I zoom into that section.
Because I only have a couple dozen plugins installed and I’m only working with local data, I won’t worry about optimizing performance. I can set up everything I need with a few loops.
That’s the secret of success: make every problem small enough that you don’t have to care.
Breaking down that series of pipes
A piped sequence felt like the clearest way to assemble this, but let’s look at it piecemeal so we understand what’s going on at each step of the process.
Ordinarily, ls
includes path information in the name. The -s
flag requests filenames without that path information, which simplifies later processing.
name | type | size | modified |
---|---|---|---|
logseq-agenda | dir | 4.1 KB | Thu, 29 Feb 2024 10:19:27 -0800 (2 months ago) |
logseq-awesome-content | dir | 4.1 KB | Mon, 1 Jan 2024 15:54:13 -0800 (3 months ago) |
… | … | … | … |
logseq-tags | dir | 4.1 KB | Tue, 2 Jan 2024 06:07:29 -0800 (3 months ago) |
logseq-webpage-title | dir | 4.1 KB | Mon, 1 Jan 2024 15:54:57 -0800 (3 months ago) |
We only care about the filename here, so we specifically select
it, producing a new single-table column.
name |
---|
logseq-agenda |
logseq-awesome-content |
logseq-awesome-links |
… |
logseq-split-block |
logseq-tags |
logseq-webpage-title |
We insert
new settings
and package
columns to our table. These columns hold the full records generated by Nushell’s automatic JSON handler after we open
project package and local settings files for each entry in our list.
With the nested structures, it’s easier to show that in a screenshot.
I want our summary string generation and filtering logic clear and easy to read, without deeply nested accessors. get
each of the details I care about and add a column for it.
Nushell ordinarily throws an error when you try to get
an undefined field. Work around that by providing a default
and using -i
to ignore errors in fields that we know could be absent.
We’re relying the presence or absence of a themes
field in package info to determine whether we’re looking at a theme. is-not-empty
manages that, returning true if there’s a logseq.themes
field with content in it, and false any other time.
What’s that look like? Wait — in order to make sense of this view, we should grab the last
row and transpose
it so that each column becomes a record field.
This is still messy. Nushell doesn’t care — I went through a few iterations pretty much ignoring the extra bulk — but I’m trying to write coherent code for you. Let’s select only those new columns for extracted details.
Now we can go back to a regular table view.
title | url | description | is-disabled | is-theme |
---|---|---|---|---|
Agenda | https://github.comhaydenull/logseq-plugin-agenda | An agenda manager plugin for logseq | true | false |
Awesome Content | https://github.comyoyurec/logseq-awesome-content | Enhanced content blocks (tasks, quotes, flashcards, headers, queries, diagrams, etc…) | false | false |
Awesome Links | https://github.comyoyurec/logseq-awesome-links | Favicons for external links, page icons for internal | false | false |
.. | .. | .. | … | … |
Split block | https://github.comhyrijk/logseq-plugin-split-block | Splitting multi-line text into blocks | false | false |
Tags | https://github.comgidongkwon/logseq-plugin-tags | A plugin that lets you find and search all of your #tags. | false | false |
Get webpage title | https://github.compaulkinlan/logseq-webpage-title | A neat little tool to fetch the title of a link and wrap it in markdown syntax. | false | false |
How about the summary string block
column? I’ll need to throw a few tabs and newlines in there for the Logseq page structure I already have in mind. In order to keep the whole thing readable, I’ll interpolate row cells into a list of strings, then join
with newlines.
That’ll look a little clunky as a table. Let’s transpose the last row again.
Now we know what $everything
looks like, or at least the structure we end up with for each row. Let’s move on.
Transform to Active Plugin and Theme Lists
I only want to summarize the themes and plugins I currently have enabled. where
helps us filter out those that have the disabled
flag turned on in their settings file.
I want to show themes and plugins in different sections, so let’s make different tables for each.
Save to a New Page
I’ll want to know when this page was generated. Determine the current time with date now
and use format date
to match my graph’s journal day format.
Now, generate a page string for the collection. I need to keep it consistent with my other note pages in Logseq, with indentation for page sections and a specific section to describe the page.
Next I write it to disk for the Logseq graph. Establish what file we’re saving to, and save it.
We set the --force
flag on save
, otherwise it won’t update an existing file.
Done!
Is It Fast Enough?
Yes.
Anything up to a second would have been good enough in a script I run only for myself. I still might’ve tried tweaking it, just for practice. But 29.5 milliseconds from start to finish? There’s no point optimizing that.
Got a comment? A question? More of a comment than a question?
Talk to me about this page on: mastodon
Added to vault 2024-04-26. Updated on 2024-04-30