diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 26d3352..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index b218c30..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 688e9bc..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/typstar.iml b/.idea/typstar.iml deleted file mode 100644 index 13f2b0f..0000000 --- a/.idea/typstar.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 5488179..294eea4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Typstar -Neovim plugin for efficient note taking in Typst +Neovim plugin for efficient (mathematical) note taking in Typst ## Features - Powerful autosnippets using [LuaSnip](https://github.com/L3MON4D3/LuaSnip/) and [Tree-sitter](https://tree-sitter.github.io/) (inspired by [fastex.nvim](https://github.com/lentilus/fastex.nvim)) -- Easy insertion of drawings using [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) +- Easy insertion of drawings using [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) or [Rnote](https://github.com/flxzt/rnote) - Export of [Anki](https://apps.ankiweb.net/) flashcards \[No Neovim required\] ## Usage @@ -14,6 +14,14 @@ To efficiently navigate insert nodes and avoid overlapping ones, use `:TypstarSmartJump` and `:TypstarSmartJumpBack`. Available snippets can mostly be intuitively derived from [here](././lua/typstar/snippets), they include: +Universal snippets: +- Alphanumeric characters: `:` → `$$ ` in markup (e.g. `:X` → `$X$ `, `:5` → `$5$ `) +- Greek letters: `;` → `` in math and `$$ ` in markup (e.g. `;a` → `alpha`/`$alpha$ `) +- Common indices (numbers and letters `i-n`): ` ` → `_ ` in math and `$$ ` → `$_$ ` in markup (e.g `A314 ` → `A_314 `, `$alpha$n ` → `$alpha_n$ `) + +You can find a complete map of latin to greek letters including reasons for the less intuitive ones [here](./lua/typstar/snippets/letters.lua). +Note that some greek letters have multiple latin ones mapped to them. + Markup snippets: - Begin inline math with `ll` and multiline math with `dm` - [Markup shorthands](./lua/typstar/snippets/markup.lua) (e.g. `HIG` → `#highlight[]`, `IMP` → `$==>$ `) @@ -23,18 +31,20 @@ Markup snippets: Math snippets: - [Many shorthands](./lua/typstar/snippets/math.lua) for mathematical expressions -- Alphanumeric characters: `:` → `$$ ` in markup (e.g. `:X` → `$X$ `, `:5` → `$5$ `) -- Greek letters: `;` → `` in math and `$$ ` in markup (e.g. `;a` → `alpha`/`$alpha$ `) -- Common indices (numbers and letters `i-n`): ` ` → `_ ` in math and `$$ ` → `$_$ ` in markup (e.g `A314 ` → `A_314 `, `$alpha$n ` → `$alpha_n$ `) - Series of numbered letters: ` ot ` → `_1, _2, ... ` (e.g. `a ot ` → `a_1, a_2, ... `, `a ot4 ` → `a_1, a_2, a_3, a_4 `, `alpha otk ` → `alpha_1, alpha_2, ..., alpha_k `) - Wrapping of any mathematical expression (see [operations](./lua/typstar/snippets/visual.lua), works nested, multiline and in visual mode via the [selection key](#installation)): `` → `()` (e.g. `(a^2+b^2)rt` → `sqrt(a^2+b^2)`, `lambdatd` → `tilde(lambda)`, `(1+1)sQ` → `[1+1]`, `(1+1)sq` → `[(1+1)]`) +- Simple functions: `fo ` → `f() ` (e.g. `fox ` → `f(x) `, `ao5 ` → `a(5) `) - Matrices: `ma` and `lma` (e.g. `23ma` → 2x3 matrix) Note that you can [customize](#custom-snippets) (enable, disable and modify) every snippet. -### Excalidraw -- Use `:TypstarInsertExcalidraw` to create a new drawing using the configured template, insert a figure displaying it and open it in Obsidian. -- To open an inserted drawing in Obsidian, simply run `:TypstarOpenExcalidraw` while your cursor is on a line referencing the drawing. +### Excalidraw/Rnote +- Use `:TypstarInsertExcalidraw`/`:TypstarInsertRnote` to + create a new drawing using the [configured](#configuration) template, + insert a figure displaying it and open it in Obsidian/Rnote. +- To open an inserted drawing in Obsidian/Rnote, + simply run `:TypstarOpenDrawing` (or `:TypstarOpenExcalidraw`/`:TypstarOpenRnote` if you are using the same file extension for both) + while your cursor is on a line referencing the drawing. ### Anki Use the `flA` snippet to create a new flashcard @@ -156,7 +166,13 @@ require('typstar').setup({ -- depending on your neovim plugin system 1. Install [Obsidian](https://obsidian.md/) and create a vault in your typst note taking directory 2. Install the [obsidian-excalidraw-plugin](https://github.com/zsviczian/obsidian-excalidraw-plugin) and enable `Auto-export SVG` (in plugin settings at `Embedding Excalidraw into your Notes and Exporting > Export Settings > Auto-export Settings`) 3. Have the `xdg-open` command working or set a different command at `uriOpenCommand` in the [config](#configuration) -4. If you encounter issues try cloning the repo into `~/typstar` or setting the `typstarRoot` config accordingly, feel free to open an issue +4. If you encounter issues with the file creation of drawings, try cloning the repo into `~/typstar` or setting the `typstarRoot` config accordingly; feel free to open an issue + +### Rnote +1. Install [Rnote](https://github.com/flxzt/rnote?tab=readme-ov-file#installation); I recommend not using flatpak as that might cause issues with file permissions. +2. Make sure `rnote-cli` is available in your `PATH` or set a different command at `exportCommand` in the [config](#configuration) +3. Have the `xdg-open` command working with Rnote files or set a different command at `uriOpenCommand` in the [config](#configuration) +4. See comment 4 above at Excalidraw ### Anki 0. Typst version `0.12.0` or higher is required @@ -195,6 +211,16 @@ with pkgs; [ ## Configuration Configuration options can be intuitively derived from the table [here](./lua/typstar/config.lua). +### Excalidraw/Rnote templates +The `templatePath` option expects a table that maps file patterns to template locations. +To for example have a specific template for lectures, you could configure it like this +```Lua +templatePath = { + { 'lectures/.*%.excalidraw%.md$', '~/Templates/lecture_excalidraw.excalidraw.md' }, -- path contains "lectures" + { '%.excalidraw%.md$', '~/Templates/default_excalidraw.excalidraw.md' }, -- fallback +}, +``` + ### Custom snippets The [config](#configuration) allows you to - disable all snippets via `snippets.enable = false` diff --git a/flake.nix b/flake.nix index 803b40d..f2e7871 100644 --- a/flake.nix +++ b/flake.nix @@ -42,6 +42,8 @@ lua << EOF print("Welcome to Typstar! This is just a demo.") + vim.g.mapleader = " " + require('nvim-treesitter.configs').setup { highlight = { enable = true }, } @@ -58,6 +60,12 @@ vim.keymap.set({'n', 'i'}, '', 'TypstarToggleSnippets', { silent = true, noremap = true }) vim.keymap.set({'s', 'i'}, '', 'TypstarSmartJump', { silent = true, noremap = true }) vim.keymap.set({'s', 'i'}, '', 'TypstarSmartJumpBack', { silent = true, noremap = true }) + + vim.keymap.set('n', 'e', 'TypstarInsertExcalidraw', { silent = true, noremap = true }) + vim.keymap.set('n', 'r', 'TypstarInsertRnote', { silent = true, noremap = true }) + vim.keymap.set('n', 'o', 'TypstarOpenDrawing', { silent = true, noremap = true }) + + vim.keymap.set('n', 'a', 'TypstarAnkiScan', { silent = true, noremap = true }) EOF ''; plugins = [ diff --git a/lua/typstar/config.lua b/lua/typstar/config.lua index 0e8d344..0b283bf 100644 --- a/lua/typstar/config.lua +++ b/lua/typstar/config.lua @@ -1,7 +1,7 @@ local M = {} local default_config = { - typstarRoot = nil, + typstarRoot = nil, -- typstar installation location required to use default drawing templates (usually determined automatically) anki = { typstarAnkiCmd = 'typstar-anki', typstCmd = 'typst', @@ -13,8 +13,18 @@ local default_config = { filename = 'drawing-%Y-%m-%d-%H-%M-%S', fileExtension = '.excalidraw.md', fileExtensionInserted = '.excalidraw.svg', - uriOpenCommand = 'xdg-open', -- set depending on OS - templatePath = nil, + uriOpenCommand = 'xdg-open', -- set depending on OS; try setting it to "obsidian" directly if you encounter problems and have it in your PATH + templatePath = {}, + }, + rnote = { + assetsDir = 'assets', + -- can be modified to e.g. export full pages; default is to try to export strokes only and otherwise export the entire document + exportCommand = 'rnote-cli export selection --no-background --no-pattern --on-conflict overwrite --output-file %s all %s || rnote-cli export doc --no-background --no-pattern --on-conflict overwrite --output-file %s %s', + filename = 'drawing-%Y-%m-%d-%H-%M-%S', + fileExtension = '.rnote', + fileExtensionInserted = '.rnote.svg', -- valid rnote export type + uriOpenCommand = 'xdg-open', -- see comment above for excalidraw + templatePath = {}, }, snippets = { enable = true, @@ -38,10 +48,12 @@ function M.merge_config(args) M.config.typstarRoot = M.config.typstarRoot or debug.getinfo(1).source:match('^@(.*)/lua/typstar/config%.lua$') or '~/typstar' - M.config.excalidraw.templatePath = M.config.excalidraw.templatePath - or { - ['%.excalidraw%.md$'] = M.config.typstarRoot .. '/res/excalidraw_template.excalidraw.md', - } + vim.list_extend(M.config.excalidraw.templatePath, { + { '%.excalidraw%.md$', M.config.typstarRoot .. '/res/excalidraw_template.excalidraw.md' }, + }) + vim.list_extend(M.config.rnote.templatePath, { + { '%.rnote$', M.config.typstarRoot .. '/res/rnote_template.rnote' }, + }) end M.merge_config(nil) diff --git a/lua/typstar/drawings.lua b/lua/typstar/drawings.lua new file mode 100644 index 0000000..8825b86 --- /dev/null +++ b/lua/typstar/drawings.lua @@ -0,0 +1,113 @@ +local M = {} +local config = require('typstar.config') +local utils = require('typstar.utils') + +local affix = [[ +#figure( + image("%s"), +) +]] +local config_excalidraw = config.config.excalidraw +local config_rnote = config.config.rnote + +local function launch_excalidraw(path, path_export) + print(string.format('Opening %s in Obsidian Excalidraw', path)) + utils.run_shell_command( + string.format('%s "obsidian://open?path=%s"', config_excalidraw.uriOpenCommand, utils.urlencode(path)), + false + ) +end + +local rnote_watched = {} + +local function auto_export_rnote(path, path_export) + if rnote_watched[path] then return end + rnote_watched[path] = true + local job_id = -1 + local last_export = 0 + + local run_export = function(err, filename) + local time = vim.uv.now() + if err ~= nil or time - last_export < 800 then return end + + if job_id == -1 then + last_export = time + local cmd = string.format(config_rnote.exportCommand, path_export, path, path_export, path) + job_id = utils.run_shell_command(cmd, false, nil, { on_exit = function() job_id = -1 end }) + end + end + local watcher = vim.uv.new_fs_event() + watcher:start(path, {}, vim.schedule_wrap(run_export)) +end + +local function launch_rnote(path, path_export) + print(string.format('Opening %s in Rnote', path)) + utils.run_shell_command(string.format('%s %s', config_rnote.uriOpenCommand, path), false) + auto_export_rnote(path, path_export) +end + +local function insert_drawing(provider) + local cfg = provider[1] + local assets_dir = vim.fn.expand('%:p:h') .. '/' .. cfg.assetsDir + local filename = os.date(cfg.filename) + local path = assets_dir .. '/' .. filename .. cfg.fileExtension + local path_export = assets_dir .. '/' .. filename .. cfg.fileExtensionInserted + local path_insert = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted -- local relative path + + if vim.fn.isdirectory(assets_dir) == 0 then vim.fn.mkdir(assets_dir, 'p') end + local found_match = false + for _, template_config in ipairs(cfg.templatePath) do + local pattern = template_config[1] + local template_path = template_config[2] + if string.match(path, pattern) then + found_match = true + -- use cat as we don't want to copy file metadata + utils.run_shell_command(string.format('cat %s > %s', template_path, path), false, nil, { + on_exit = function() + -- insert text and launch program + utils.insert_text_block(string.format(provider[2], path_insert)) + provider[3](path, path_export) + end, + }) + break + end + end + if not found_match then + print('No matching template found for path: ' .. path) + return + end +end + +local excalidraw = { + config_excalidraw, + affix, + launch_excalidraw, +} +local rnote = { + config_rnote, + affix, + launch_rnote, +} +local providers = { excalidraw, rnote } + +local open_drawing = function(prov) + for _, provider in ipairs(prov) do + local cfg = provider[1] + local line = vim.api.nvim_get_current_line() + local filename = line:match('"(.*)' .. string.gsub(cfg.fileExtensionInserted, '%.', '%%%.')) + if filename ~= nil and filename:match('^%s*$') == nil then + local path = vim.fn.expand('%:p:h') .. '/' .. filename .. cfg.fileExtension + local path_export = vim.fn.expand('%:p:h') .. '/' .. filename .. cfg.fileExtensionInserted + provider[3](path, path_export) -- launch program + break + end + end +end + +function M.insert_obsidian_excalidraw() insert_drawing(excalidraw) end +function M.insert_rnote() insert_drawing(rnote) end +function M.open_obsidian_excalidraw() open_drawing({ excalidraw }) end +function M.open_rnote() open_drawing({ rnote }) end +function M.open_drawing() open_drawing(providers) end + +return M diff --git a/lua/typstar/engine.lua b/lua/typstar/engine.lua index f09bb8a..54d1dff 100644 --- a/lua/typstar/engine.lua +++ b/lua/typstar/engine.lua @@ -15,7 +15,7 @@ local ts_string_query = ts.query.parse('typst', '(string) @string') utils.generate_bool_set(cfg.exclude, exclude_triggers_set) vim.api.nvim_create_autocmd('TextChangedI', { - callback = function() last_keystroke_time = vim.loop.now() end, + callback = function() last_keystroke_time = vim.uv.now() end, }) M.in_math = function() diff --git a/lua/typstar/excalidraw.lua b/lua/typstar/excalidraw.lua deleted file mode 100644 index fb6a8ab..0000000 --- a/lua/typstar/excalidraw.lua +++ /dev/null @@ -1,53 +0,0 @@ -local M = {} -local config = require('typstar.config') -local utils = require('typstar.utils') - -local cfg = config.config.excalidraw -local affix = [[ -#figure( - image("%s"), -) -]] - -local function launch_obsidian(path) - print(string.format('Opening %s in Excalidraw', path)) - utils.run_shell_command( - string.format('%s "obsidian://open?path=%s"', cfg.uriOpenCommand, utils.urlencode(path)), - false - ) -end - -function M.insert_drawing() - local assets_dir = vim.fn.expand('%:p:h') .. '/' .. cfg.assetsDir - local filename = os.date(cfg.filename) - local path = assets_dir .. '/' .. filename .. cfg.fileExtension - local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted - - if vim.fn.isdirectory(assets_dir) == 0 then vim.fn.mkdir(assets_dir, 'p') end - local found_match = false - for pattern, template_path in pairs(cfg.templatePath) do - if string.match(path, pattern) then - found_match = true - utils.run_shell_command(string.format('cat %s > %s', template_path, path), false) -- don't copy file metadata - break - end - end - if not found_match then - print('No matching template found for the path: ' .. path) - return - end - - utils.insert_text_block(string.format(affix, path_inserted)) - launch_obsidian(path) -end - -function M.open_drawing() - local line = vim.api.nvim_get_current_line() - local path = vim.fn.expand('%:p:h') - .. '/' - .. string.match(line, '"(.*)' .. string.gsub(cfg.fileExtensionInserted, '%.', '%%%.')) - .. '.excalidraw.md' - launch_obsidian(path) -end - -return M diff --git a/lua/typstar/init.lua b/lua/typstar/init.lua index 5bfe148..058dfdc 100644 --- a/lua/typstar/init.lua +++ b/lua/typstar/init.lua @@ -6,15 +6,18 @@ local luasnip = nil M.setup = function(args) config.merge_config(args) local autosnippets = require('typstar.autosnippets') - local excalidraw = require('typstar.excalidraw') + local drawings = require('typstar.drawings') local anki = require('typstar.anki') vim.api.nvim_create_user_command('TypstarToggleSnippets', autosnippets.toggle_autosnippets, {}) vim.api.nvim_create_user_command('TypstarSmartJump', function() M.smart_jump(1) end, {}) vim.api.nvim_create_user_command('TypstarSmartJumpBack', function() M.smart_jump(-1) end, {}) - vim.api.nvim_create_user_command('TypstarInsertExcalidraw', excalidraw.insert_drawing, {}) - vim.api.nvim_create_user_command('TypstarOpenExcalidraw', excalidraw.open_drawing, {}) + vim.api.nvim_create_user_command('TypstarInsertExcalidraw', drawings.insert_obsidian_excalidraw, {}) + vim.api.nvim_create_user_command('TypstarInsertRnote', drawings.insert_rnote, {}) + vim.api.nvim_create_user_command('TypstarOpenExcalidraw', drawings.open_obsidian_excalidraw, {}) + vim.api.nvim_create_user_command('TypstarOpenRnote', drawings.open_rnote, {}) + vim.api.nvim_create_user_command('TypstarOpenDrawing', drawings.open_drawing, {}) vim.api.nvim_create_user_command('TypstarAnkiScan', anki.scan, {}) vim.api.nvim_create_user_command('TypstarAnkiReimport', anki.scan_reimport, {}) diff --git a/lua/typstar/snippets/letters.lua b/lua/typstar/snippets/letters.lua index 8728d81..7eaed12 100644 --- a/lua/typstar/snippets/letters.lua +++ b/lua/typstar/snippets/letters.lua @@ -16,31 +16,32 @@ local greek_letters_map = { ['c'] = 'chi', ['d'] = 'delta', ['e'] = 'epsilon', - ['f'] = 'phi', + ['f'] = 'phi', -- sound ['g'] = 'gamma', - ['h'] = 'eta', + ['h'] = 'eta', -- look ['i'] = 'iota', - ['j'] = 'theta', ['k'] = 'kappa', ['l'] = 'lambda', ['m'] = 'mu', ['n'] = 'nu', - ['o'] = 'omega', + ['o'] = 'omikron', ['p'] = 'psi', - ['q'] = 'eta', + ['q'] = 'theta', -- look? ['r'] = 'rho', ['s'] = 'sigma', ['t'] = 'tau', - ['v'] = 'nu', - ['w'] = 'omega', + ['u'] = 'upsilon', + ['v'] = 'nu', -- look + ['w'] = 'omega', -- look ['x'] = 'xi', - ['y'] = 'upsilon', + ['y'] = 'upsilon', -- look ['z'] = 'zeta', } + local greek_keys = {} local greek_letters_set = {} local common_indices = { '\\d+', '[i-n]' } --- buitins and caligraphic letters from github.com/lentilus/readable-typst +-- builtins and calligraphic letters from github.com/lentilus/typst-scribe local index_conflicts = { 'Im', 'in', 'ln', 'Pi', 'pi', 'Xi', 'xi', 'Ii', 'Jj', 'Kk', 'Ll', 'Mm', 'Nn' } local index_conflicts_set = {} local punctuation_prepend_space = { ',', ';' } diff --git a/lua/typstar/snippets/math.lua b/lua/typstar/snippets/math.lua index c885cc5..5799f07 100644 --- a/lua/typstar/snippets/math.lua +++ b/lua/typstar/snippets/math.lua @@ -73,9 +73,9 @@ return { snip('Oo', 'compose ', {}, math), snip('iso', 'tilde.equiv ', {}, math), snip('cc', 'cases(\n\t<>\n)\\', { i(1, '1') }, math), - snip('([A-Za-z])o([A-Za-z0-9])', '<>(<>) ', { cap(1), cap(2) }, math, 100, { - maxTrigLength = 3, - blacklist = { 'bot', 'col', 'com', 'con', 'cos', 'cot', 'dol', 'dot', 'log', 'loz', 'mod', 'roo', 'top', 'won', 'xor' }, + snip('([A-Za-z])o([A-Za-z0-9]) ', '<>(<>) ', { cap(1), cap(2) }, math, 100, { + maxTrigLength = 4, + blacklist = { 'bot ', 'cos ', 'cot ', 'dot ', 'log ', 'mod ', 'top ', 'won ', 'xor ' }, }), snip('(K|M|N|Q|R|S|Z)([\\dn]) ', '<><>^<> ', { cap(1), cap(1), cap(2) }, math), diff --git a/lua/typstar/utils.lua b/lua/typstar/utils.lua index d83bdac..0addcad 100644 --- a/lua/typstar/utils.lua +++ b/lua/typstar/utils.lua @@ -26,8 +26,9 @@ function M.insert_text_block(snip) vim.api.nvim_buf_set_lines(vim.api.nvim_get_current_buf(), line_num, line_num, false, lines) end -function M.run_shell_command(cmd, show_output, extra_handler) +function M.run_shell_command(cmd, show_output, extra_handler, opts) extra_handler = extra_handler or function(msg) end + opts = opts or { on_exit = function() end } local handle_output = function(data, err) local msg = table.concat(data, '\n') if not string.match(msg, '^%s*$') then @@ -37,14 +38,17 @@ function M.run_shell_command(cmd, show_output, extra_handler) end end if show_output then - vim.fn.jobstart(cmd, { - on_stdout = function(_, data, _) handle_output(data, false) end, - on_stderr = function(_, data, _) handle_output(data, true) end, - stdout_buffered = false, - stderr_buffered = true, - }) + return vim.fn.jobstart( + cmd, + vim.tbl_deep_extend('force', { + on_stdout = function(_, data, _) handle_output(data, false) end, + on_stderr = function(_, data, _) handle_output(data, true) end, + stdout_buffered = false, + stderr_buffered = true, + }, opts) + ) else - vim.fn.jobstart(cmd) + return vim.fn.jobstart(cmd, opts) end end diff --git a/pyproject.toml b/pyproject.toml index 067f1f9..83ed06d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "pdm.backend" [project] name = "typstar" -version = "1.3.4" +version = "1.4.0" description = "Neovim plugin for efficient note taking in Typst" authors = [ { name = "arne314" } diff --git a/res/rnote_template.rnote b/res/rnote_template.rnote new file mode 100644 index 0000000..6a3d6cb Binary files /dev/null and b/res/rnote_template.rnote differ diff --git a/uv.lock b/uv.lock index 5a54c4a..fc49898 100644 --- a/uv.lock +++ b/uv.lock @@ -438,7 +438,7 @@ wheels = [ [[package]] name = "typstar" -version = "1.3.4" +version = "1.4.0" source = { editable = "." } dependencies = [ { name = "aiohttp" },