🐍 pathlib.nvim
OS independent, ultimate solution to path handling in neovim.
[![Neovim](https://img.shields.io/badge/NeoVim-%2357A143.svg?&style=for-the-badge&logo=neovim&logoColor=white)](https://neovim.io/) [![Lua](https://img.shields.io/badge/lua-%232C2D72.svg?style=for-the-badge&logo=lua&logoColor=white)](https://www.lua.org/)
[![MLP-2.0](https://img.shields.io/github/license/pysan3/pathlib.nvim.svg?style=for-the-badge)](https://github.com/pysan3/pathlib.nvim/blob/master/LICENSE) [![Issues](https://img.shields.io/github/issues/pysan3/pathlib.nvim.svg?style=for-the-badge)](https://github.com/pysan3/pathlib.nvim/issues) [![Build Status](https://img.shields.io/github/actions/workflow/status/pysan3/pathlib.nvim/lua_ls-typecheck.yml?style=for-the-badge)](https://github.com/pysan3/pathlib.nvim/actions/workflows/lua_ls-typecheck.yml) [![LuaRocks](https://img.shields.io/luarocks/v/pysan3/pathlib.nvim?logo=lua&color=purple&style=for-the-badge)](https://luarocks.org/modules/pysan3/pathlib.nvim)
# 🐍 `pathlib.nvim`
This plugin aims to decrease the difficulties of path management across mutliple OSs in neovim. The plugin API is heavily inspired by Python's `pathlib.Path` with tweaks to fit neovim usage.
- [Documentation](https://pysan3.github.io/pathlib.nvim/)
- Module References
- [`PathlibPath`](https://pysan3.github.io/pathlib.nvim/doc/PathlibPath.html): base class with operations.
- [`PathlibPosixPath`](https://pysan3.github.io/pathlib.nvim/doc/PathlibPosixPath.html): posix system specific.
- [`PathlibWindowsPath`](https://pysan3.github.io/pathlib.nvim/doc/PathlibWindowsPath.html): posix system specific.
- 🔎 Search for Keyword
- [Search](https://pysan3.github.io/pathlib.nvim/search.html)
- [Index](https://pysan3.github.io/pathlib.nvim/genindex.html)
# ✨ Benefits
## 📦 Intuitive and Useful Methods
``` lua
local Path = require("pathlib")
local dir = Path("~/Documents") -- Same as `Path.home() / "Documents"`
local foo = dir / "foo.txt"
print(foo:basename(), foo:stem(), foo:suffix()) -- foo.txt, foo, .txt
print(foo:parent()) -- "/home/user/Documents"
```
## 📋 Git Integration
``` lua
local git_root = Path("/path/to/git/workdir")
assert(git_root:child(".git"):exists(), string.format("%s is not a git repo.", git_root))
require("pathlib.git").fill_git_state({ file_a, file_b, ... })
file_a.git_state.ignored -- is git ignored
file_a.git_state.status -- git status (modified, added, staged, ...)
file_a.git_state.git_root -- root directory of the repo
```
## ⏱️ Sync / Async Operations
The API is designed so it is very easy to switch between sync and async operations. Call them inside a [nvim-nio async context](https://github.com/nvim-neotest/nvim-nio) without any change, and the operations are converted to be async (does not block the main thread).
``` lua
local foo = Path("~/Documents/foo.txt")
local content = "File Content\n"
-- # sync
local sync_bytes = foo:fs_write(content)
assert(sync_bytes == content:len(), foo.error_msg)
-- # async
require("nio").run(function()
local async_bytes = foo:fs_write(content)
assert(async_bytes == content:len(), foo.error_msg)
end)
```
# 🚀 Usage Example
## Create Path Object
``` lua
local Path = require("pathlib")
local cwd = Path.cwd()
vim.print(string.format([[cwd: %s]], cwd))
-- Use __div to chain file tree!
local folder = Path(".") / "folder"
local foo = folder / "foo.txt"
assert(tostring(foo) == "folder/foo.txt") -- $PWD/folder/foo.txt
assert(tostring(foo:parent()) == "folder")
-- Path object is comparable
assert(foo == Path("./folder/foo.txt")) -- Path object can be created with arguments
assert(foo == Path(folder, "foo.txt")) -- Unpack any of them if you want!
-- Calculate relativily
assert(foo:is_relative_to(Path("folder")))
assert(not foo:is_relative_to(Path("./different folder")))
assert(foo:relative_to(folder) == Path("foo.txt"))
```
### Path object is stored with `string[]`.
- Very fast operations to work with parents / children / siblings.
- No need to worry about path separator =\> OS Independent.
- `/`: Unix, `\`: Windows
### Nicely integrated with vim functions.
There are wrappers around vim functions such as `fnamemodify`, `stdpath` and `getcwd`.
``` lua
path:modify(":p:t:r") -- vim.fn.fnamemodify
-- Define child directory of stdpaths
Path.stdpath("data", "mason", "bin") -- vim.fn.stdpath("data") .. "/mason/bin"
```
## Create and Manipulate Files / Directories
``` lua
local luv = vim.loop
local Path = require("pathlib")
-- Create new folder
local new_file = Path.new("./new/folder/foo.txt")
new_file:parent_assert():mkdir(Path.permission("rwxr-xr-x"), true) -- (permission, recursive)
-- Create new file and write to it
local fd = new_file:fs_open("w", Path.permission("rw-r--r--"), true)
assert(fd ~= nil, "File creation failed. " .. new_file.error_msg)
luv.fs_write(fd, "File Content\n")
luv.fs_close(fd)
-- HINT: new_file:fs_write(...) does this all at once.
-- SHORTHAND: read file content with `io.read`
local content = new_file:io_read()
assert(content == "File Content\n")
-- SHORTHAND: write to file
new_file:io_write("File Content\n")
new_file:copy(new_file:with_basename("bar.txt")) -- copy `foo.txt` to `bar.txt`
new_file:symlink_to(new_file:with_basename("baz.txt")) -- create symlink of `foo.txt` named `baz.txt`
```
## Scan Directories
``` lua
-- Continue from above
for path in new_file:parent_assert():fs_iterdir() do
-- loop: [Path("./new/folder/foo.txt"), Path("./new/folder/bar.txt"), Path("./new/folder/baz.txt")]
end
```
## Async Execution
This library uses [nvim-nio](https://github.com/nvim-neotest/nvim-nio) under the hood to run async calls. Supported methods will turn into async calls inside a `nio.run` async context and has the **EXACT SAME INTERFACE**.
``` lua
local nio = require("nio")
local path = Path("foo.txt")
nio.run(function() -- async run (does not block the main thread)
vim.print(path:fs_stat()) -- coroutine (async)
path:fs_write("File Content\n") -- coroutine (async)
vim.print(path:fs_read()) -- coroutine (async)
vim.print("async done") -- prints last
end)
vim.print("sync here") -- prints first (maybe not if above functions end very fast)
```
When execution fails, function will return `nil` and the error message is captured into `self.error_msg`. This property holds the error message of the latest async function call.
``` lua
nio.run(function ()
local path = Path("./does/not/exist.txt")
local fd = path:fs_open("r")
assert(fd, "ERROR: " .. path.error_msg)
-- fd will be nil when `:fs_open` fails. Check `self.error_msg` for the error message.
end)
```
# TODO
- [x] API documentation.
- [x] PathlibPath
- [x] PathlibPosixPath
- [x] PathlibWindowsPath
- [x] Git
- [x] Git operation integration.
- [ ] Git test suite.
- [ ] List out every possible git state: ignored, staged etc.
- [ ] Create file for each state.
- [ ] Add docs for each state: `man git-diff -> RAW OUTPUT FORMAT`
- [ ] Windows implementation, test environment.
- [ ] Create a CI/CD action to run on windows.
- [ ] Prepare windows specific test suite.
# Contributions
I'll happily accept any [feature request](https://github.com/pysan3/pathlib.nvim/issues/new?assignees=&labels=feature&projects=&template=feature_request.yml) Feel free to ask for any functionality :)
# Other Projects
- Python `pathlib`
-