Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# pybumper.nvim

## A wrapper around the `poetry` commands for nvim 🔌
## A wrapper around Python package managers for nvim 🔌

</div>

Expand All @@ -27,7 +27,7 @@
- ✨ Add any new valid dependency.
- ✨ Upgrade dependency on a current line.
- ✨ Remove any dependency.
- 🏗 Automatic package manager detection (`poetry` supported at this moment. Support for `requirements.txt` would be added later.).
- 🏗 Automatic package manager detection (`poetry` and `uv` supported. Support for `requirements.txt` would be added later.).
- 🏗 Loading animation hook (to be placed in status bar or anywhere else).

## 🔌 Available commands
Expand Down Expand Up @@ -82,7 +82,8 @@
It should work with any fairly modern python tech stack. I tested that for the following:

- neovim >= 0.9 and nightly 0.10-dev releases
- poetry >= 1.5.1
- poetry >= 1.5.1 (if using poetry)
- uv >= 0.1.0 (if using uv)
- python >= 3.10.8
- pip >= 23.2.1

Expand Down Expand Up @@ -121,7 +122,7 @@ The `pybumper.nvim` comes up with the following configuration. Any of of those c
},
},
autostart = true,
package_manager = constants.PACKAGE_MANAGERS.poetry,
package_manager = constants.PACKAGE_MANAGERS.poetry, -- or constants.PACKAGE_MANAGERS.uv
hide_up_to_date = false,
hide_unstable_versions = false,
},
Expand Down
4 changes: 4 additions & 0 deletions lua/pybumper/actions/change-version.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ local M = {}
M.__get_change_version_command = function(dependency_name, version)
if config.options.package_manager == constants.PACKAGE_MANAGERS.poetry then
return "poetry add " .. dependency_name .. "@" .. version
elseif config.options.package_manager == constants.PACKAGE_MANAGERS.uv then
return "uv add " .. dependency_name .. "==" .. version
end
end

Expand All @@ -29,6 +31,8 @@ end
M.__get_version_list_command = function(dependency_name)
if config.options.package_manager == constants.PACKAGE_MANAGERS.poetry then
return ("pip index versions " .. dependency_name)
elseif config.options.package_manager == constants.PACKAGE_MANAGERS.uv then
return ("uv pip index versions " .. dependency_name)
end
end

Expand Down
2 changes: 2 additions & 0 deletions lua/pybumper/actions/delete.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ local M = {}
M.__get_command = function(dependency_name)
if config.options.package_manager == constants.PACKAGE_MANAGERS.poetry then
return "poetry remove " .. dependency_name
elseif config.options.package_manager == constants.PACKAGE_MANAGERS.uv then
return "uv remove " .. dependency_name
end
end

Expand Down
4 changes: 4 additions & 0 deletions lua/pybumper/actions/install.lua
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ M.__get_command = function(type, dependency_name)
if type == constants.DEPENDENCY_TYPE.development then
if config.options.package_manager == constants.PACKAGE_MANAGERS.poetry then
return "poetry add --dev " .. dependency_name
elseif config.options.package_manager == constants.PACKAGE_MANAGERS.uv then
return "uv add --dev " .. dependency_name
end
end

if type == constants.DEPENDENCY_TYPE.production then
if config.options.package_manager == constants.PACKAGE_MANAGERS.poetry then
return "poetry add " .. dependency_name
elseif config.options.package_manager == constants.PACKAGE_MANAGERS.uv then
return "uv add " .. dependency_name
end
end
end
Expand Down
12 changes: 11 additions & 1 deletion lua/pybumper/actions/show.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ local virtual_text = require("pybumper.virtual_text")
local reload = require("pybumper.helpers.reload")
local loading = require("pybumper.ui.generic.loading-status")
local logger = require("pybumper.utils.logger")
local config = require("pybumper.config")
local constants = require("pybumper.utils.constants")

local M = {}

Expand All @@ -23,9 +25,17 @@ M.run = function(options)
return
end
local id = loading.new("|  Fetching latest versions")

local command
if config.options.package_manager == constants.PACKAGE_MANAGERS.poetry then
command = "poetry show -o | awk -F' +' '{print $1, $2, $3 \" _ \" ;}'"
elseif config.options.package_manager == constants.PACKAGE_MANAGERS.uv then
command = "uv pip list --outdated | tail -n +3 | awk '{print $1, $2, $3 \" _ \" ;}'"
end

job({
json = false,
command = "poetry show -o | awk -F' +' '{print $1, $2, $3 \" _ \" ;}'",
command = command,
ignore_error = false,
on_start = function()
loading.start(id)
Expand Down
2 changes: 2 additions & 0 deletions lua/pybumper/actions/update.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ local M = {}
M.__get_command = function(dependency_name)
if config.options.package_manager == constants.PACKAGE_MANAGERS.poetry then
return "poetry update " .. dependency_name
elseif config.options.package_manager == constants.PACKAGE_MANAGERS.uv then
return "uv lock --upgrade-package " .. dependency_name
end
end

Expand Down
10 changes: 10 additions & 0 deletions lua/pybumper/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,18 @@ end
-- @return nil
-- TODO: Add support for requirements.txt approach with pip
M.__register_package_manager = function()
local uv_lock = io.open("uv.lock", "r")
local poetry_lock = io.open("poetry.lock", "r")

if uv_lock ~= nil then
M.options.package_manager = constants.PACKAGE_MANAGERS.uv

io.close(uv_lock)
state.is_in_project = true

return
end

if poetry_lock ~= nil then
M.options.package_manager = constants.PACKAGE_MANAGERS.poetry

Expand Down
15 changes: 13 additions & 2 deletions lua/pybumper/helpers/get_dependency_name_from_line.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,23 @@ end
-- @return string?
return function(line)
local value = {}
-- Try to match the name and version from the line

-- Try to match Poetry format: name = "version"
local name, version = line:match('^(%S+)%s*=%s*"([^"]+)"')

if name and version then
table.insert(value, name)
table.insert(value, version)
else
-- Try to match UV format: "package==version" or "package>=version", etc.
local pkg_spec = line:match('^%s*"([^"]+)"')
if pkg_spec then
name, version = pkg_spec:match("^([^=<>~!]+)[=<>~!]+(.*)$")
if name and version then
table.insert(value, name)
table.insert(value, version)
end
end
end

-- If no version or name fail
Expand Down
137 changes: 130 additions & 7 deletions lua/pybumper/parser.lua
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
local state = require("pybumper.state")
local clean_version = require("pybumper.helpers.clean_version")
local logger = require("pybumper.utils.logger")
local config = require("pybumper.config")
local constants = require("pybumper.utils.constants")

local function extractDependenciesCurrent(tomlContent)
-- Parse Poetry-style dependencies: [tool.poetry.dependencies]
local function extractPoetryDependencies(tomlContent)
local dependencies = {}
local insideDependenciesSection = false
local insideDevDependenciesSection = false
Expand All @@ -13,14 +16,14 @@ local function extractDependenciesCurrent(tomlContent)

if line:match("^%[tool.poetry.dependencies%]") then
insideDependenciesSection = true
insideDevDependenciesSection = false
elseif line:match("^%[tool.poetry.dev%-dependencies%]") then
insideDevDependenciesSection = true
elseif insideDependenciesSection and line:match("^%[.-%]") then
-- We've reached the end of the dependencies section
break
elseif insideDevDependenciesSection and line:match("^%[.-%]") then
-- We've reached the end of the dev dependencies section
break
insideDependenciesSection = false
elseif line:match("^%[.-%]") then
-- We've reached the end of the current section
insideDependenciesSection = false
insideDevDependenciesSection = false
elseif (insideDependenciesSection or insideDevDependenciesSection) and line ~= "" then
-- Extract key-value pairs within the dependencies section
local key, value = line:match('^(.-)%s*=%s*"(.-)"$')
Expand All @@ -29,6 +32,126 @@ local function extractDependenciesCurrent(tomlContent)
end
end
end
return dependencies
end

-- Parse UV-style dependencies: dependencies = ["package==version", ...]
local function extractUVDependencies(tomlContent)
local dependencies = {}
local inDependenciesArray = false
local inDevArray = false
local inOptionalArray = false
local inOptionalDepArray = false
local currentArrayContent = ""

for line in tomlContent:gmatch("[^\r\n]+") do
-- Trim leading and trailing spaces
line = line:match("^%s*(.-)%s*$")

-- Check for start of dependencies array
if line:match("^dependencies%s*=%s*%[") then
inDependenciesArray = true
currentArrayContent = line
-- Check if array closes on same line
if line:match("%]%s*$") then
inDependenciesArray = false
-- Parse single-line array
for pkg in line:gmatch('"([^"]+)"') do
local name, version = pkg:match("^([^=<>~!]+)[=<>~!]+(.*)$")
if name and version then
dependencies[name] = version
end
end
currentArrayContent = ""
end
-- Check for dev dependencies in [dependency-groups]
elseif line:match("^%[dependency%-groups%]") then
inOptionalArray = false
inDevArray = false
elseif line:match("^dev%s*=%s*%[") then
inDevArray = true
currentArrayContent = line
-- Check if array closes on same line
if line:match("%]%s*$") then
inDevArray = false
-- Parse single-line array
for pkg in line:gmatch('"([^"]+)"') do
local name, version = pkg:match("^([^=<>~!]+)[=<>~!]+(.*)$")
if name and version then
dependencies[name] = version
end
end
currentArrayContent = ""
end
-- Check for optional dependencies in [project.optional-dependencies]
elseif line:match("^%[project%.optional%-dependencies%]") then
inOptionalArray = true
inOptionalDepArray = false
-- Handle optional dependency arrays starting (e.g., data = [...])
elseif inOptionalArray and line:match("^%w+%s*=%s*%[") then
-- This is a new optional dependency array
currentArrayContent = line
-- Check if array closes on same line
if line:match("%]%s*$") then
-- Parse single-line optional array
for pkg in line:gmatch('"([^"]+)"') do
local name, version = pkg:match("^([^=<>~!]+)[=<>~!]+(.*)$")
if name and version then
dependencies[name] = version
end
end
currentArrayContent = ""
else
-- Multi-line optional array, will continue in next elseif
inOptionalDepArray = true
end
-- If we're in a multi-line array, continue parsing
elseif inDependenciesArray or inDevArray or inOptionalDepArray then
currentArrayContent = currentArrayContent .. " " .. line

-- Check if we've reached the end of the array
if line:match("%]") then
-- Parse all packages in the array
for pkg in currentArrayContent:gmatch('"([^"]+)"') do
local name, version = pkg:match("^([^=<>~!]+)[=<>~!]+(.*)$")
if name and version then
dependencies[name] = version
end
end
currentArrayContent = ""
inDependenciesArray = false
inDevArray = false
inOptionalDepArray = false
end
-- Check for new section (stop parsing optional deps)
elseif line:match("^%[.-%]") then
inOptionalArray = false
inDependenciesArray = false
inDevArray = false
inOptionalDepArray = false
currentArrayContent = ""
end
end

return dependencies
end

local function extractDependenciesCurrent(tomlContent)
local dependencies = {}

-- Try to determine which package manager format to use
if config.options.package_manager == constants.PACKAGE_MANAGERS.uv then
dependencies = extractUVDependencies(tomlContent)
elseif config.options.package_manager == constants.PACKAGE_MANAGERS.poetry then
dependencies = extractPoetryDependencies(tomlContent)
else
-- Fallback: try both formats
dependencies = extractPoetryDependencies(tomlContent)
if not next(dependencies) then
dependencies = extractUVDependencies(tomlContent)
end
end

return next(dependencies) and dependencies or nil, "Dependencies section not found"
end

Expand Down
1 change: 1 addition & 0 deletions lua/pybumper/utils/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ M.HIGHLIGHT_GROUPS = {

M.PACKAGE_MANAGERS = {
poetry = "poetry",
uv = "uv",
}

M.DEPENDENCY_TYPE = {
Expand Down