mirror of https://github.com/sayanarijit/xplr
Compare commits
No commits in common. 'main' and 'v0.19.1' have entirely different histories.
@ -1,6 +0,0 @@
|
|||||||
ratatui
|
|
||||||
crate
|
|
||||||
ser
|
|
||||||
enque
|
|
||||||
noice
|
|
||||||
ans
|
|
@ -1,23 +0,0 @@
|
|||||||
name: "Push Binary Cache for Nix"
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os: [ubuntu-latest, macos-latest]
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- uses: nixbuild/nix-quick-install-action@v19
|
|
||||||
with:
|
|
||||||
nix_conf: experimental-features = nix-command flakes
|
|
||||||
- uses: cachix/cachix-action@v11
|
|
||||||
with:
|
|
||||||
name: xplr
|
|
||||||
authtoken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
|
|
||||||
- run: nix profile install .
|
|
||||||
- name: Run tests
|
|
||||||
run: |
|
|
||||||
xplr --version
|
|
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,7 @@
|
|||||||
# Build
|
Process
|
||||||
|
-------
|
||||||
See [install.md](./docs/en/src/install.md#build-from-source)
|
- [ ] Update `Cargo.toml` version.
|
||||||
|
- [ ] Update `config.yml` version.
|
||||||
Note: xplr ships with vendored luajit. If the platform can't compile this,
|
- [ ] Update `Config::is_compatible()` and `Config::upgrade_notification()`.
|
||||||
you need to compile using `--no-default-features` argument to avoid using
|
- [ ] Publish GitHub tag.
|
||||||
vendored luajit, so that you can static link luajit yourself.
|
- [ ] Update Wiki and docs.
|
||||||
|
|
||||||
# Release
|
|
||||||
|
|
||||||
The final binary `target/release/xplr` can be shipped with the following assets
|
|
||||||
|
|
||||||
- [License](./LICENSE)
|
|
||||||
- [Desktop Entry](./assets/desktop/xplr.desktop)
|
|
||||||
- [Desktop Icons](./assets/icon/)
|
|
||||||
- [Offline Docs](./docs/en/src)
|
|
||||||
- [Lua Configuration Example](./src/init.lua)
|
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
(import
|
|
||||||
(
|
|
||||||
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
|
|
||||||
fetchTarball {
|
|
||||||
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
|
|
||||||
sha256 = lock.nodes.flake-compat.locked.narHash;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
{ src = ./.; }
|
|
||||||
).defaultNix
|
|
@ -0,0 +1,12 @@
|
|||||||
|
# Community
|
||||||
|
|
||||||
|
Building an active community of awesome people and learning stuff together is
|
||||||
|
one of my reasons to publish this tool and maintain it. Hence, please feel free
|
||||||
|
to reach out via your preferred way.
|
||||||
|
|
||||||
|
- Real-time chat lovers can join our [**matrix room**][3] or [**discord channel**][1].
|
||||||
|
- Forum discussion veterans can [**start a new GitHub discussion**][2].
|
||||||
|
|
||||||
|
[1]: https://discord.gg/JmasSPCcz3
|
||||||
|
[2]: https://github.com/sayanarijit/xplr/discussions
|
||||||
|
[3]: https://matrix.to/#/#xplr-pub:matrix.org
|
@ -0,0 +1,32 @@
|
|||||||
|
If you like xplr, and want to contribute, that would be really awesome.
|
||||||
|
|
||||||
|
You can contribute to this project in the following ways
|
||||||
|
|
||||||
|
- Contribute your time and expertise (read [CONTRIBUTING.md][1] for instructions).
|
||||||
|
|
||||||
|
- **Developers:** You can help me improve my code, fix things, implement features etc.
|
||||||
|
- **Repository maintainers:** You can save the users from the pain of managing xplr in their system manually.
|
||||||
|
- **Code Reviewers:** Teach me your ways of code.
|
||||||
|
- **Designers:** You can make the logo even more awesome, donate stickers and blog post worthy pictures.
|
||||||
|
- **Bloggers, YouTubers & broadcasters:** You can help spread the word.
|
||||||
|
|
||||||
|
- Contribute by donating or sponsoring me via any of the following ways.
|
||||||
|
- [GitHub Sponsors][5]
|
||||||
|
- [Open Collective][2]
|
||||||
|
- [ko-fi][3]
|
||||||
|
- [liberapay][6]
|
||||||
|
- [PayPal][7]
|
||||||
|
|
||||||
|
For further queries or concern related to `xplr`, [just ask us][4].
|
||||||
|
|
||||||
|
### Backers
|
||||||
|
|
||||||
|
<a href="https://opencollective.com/xplr#backer"><img src="https://opencollective.com/xplr/tiers/backer.svg?width=890" /></a>
|
||||||
|
|
||||||
|
[1]: https://github.com/sayanarijit/xplr/blob/main/CONTRIBUTING.md
|
||||||
|
[2]: https://opencollective.com/xplr
|
||||||
|
[3]: https://ko-fi.com/sayanarijit
|
||||||
|
[4]: community.md
|
||||||
|
[5]: https://github.com/sponsors/sayanarijit?o=esb
|
||||||
|
[6]: https://liberapay.com/sayanarijit
|
||||||
|
[7]: https://paypal.me/sayanarijit
|
@ -1,77 +0,0 @@
|
|||||||
# Searching
|
|
||||||
|
|
||||||
xplr supports searching paths using different algorithm. The search mechanism
|
|
||||||
generally appears between filters and sorters in the `Sort & filter` panel.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```
|
|
||||||
fzy:foo↓
|
|
||||||
```
|
|
||||||
|
|
||||||
This line means that the nodes visible on the table are being filtered using the
|
|
||||||
[fuzzy matching][1] algorithm on the input `foo`. The arrow means that ranking based
|
|
||||||
ordering is being applied, i.e. [sorters][2] are being ignored.
|
|
||||||
|
|
||||||
## Node Searcher Applicable
|
|
||||||
|
|
||||||
Node Searcher contains the following fields:
|
|
||||||
|
|
||||||
- [pattern][3]
|
|
||||||
- [recoverable_focus][4]
|
|
||||||
- [algorithm][5]
|
|
||||||
- [unordered][7]
|
|
||||||
|
|
||||||
### pattern
|
|
||||||
|
|
||||||
The patterns used to search.
|
|
||||||
|
|
||||||
Type: string
|
|
||||||
|
|
||||||
### recoverable_focus
|
|
||||||
|
|
||||||
Where to focus when search is cancelled.
|
|
||||||
|
|
||||||
Type: nullable string
|
|
||||||
|
|
||||||
### algorithm
|
|
||||||
|
|
||||||
Search algorithm to use. Defaults to the value set in
|
|
||||||
[xplr.config.general.search.algorithm][8].
|
|
||||||
|
|
||||||
It can be one of the following:
|
|
||||||
|
|
||||||
- Fuzzy
|
|
||||||
- Regex
|
|
||||||
|
|
||||||
### unordered
|
|
||||||
|
|
||||||
Whether to skip ordering the search result by algorithm based ranking. Defaults
|
|
||||||
to the value set in [xplr.config.general.search.unordered][9].
|
|
||||||
|
|
||||||
Type: boolean
|
|
||||||
|
|
||||||
## Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local searcher = {
|
|
||||||
pattern = "pattern to search",
|
|
||||||
recoverable_focus = "/path/to/focus/on/cancel",
|
|
||||||
algorithm = "Fuzzy",
|
|
||||||
unordered = false,
|
|
||||||
}
|
|
||||||
|
|
||||||
xplr.util.explore({ searcher = searcher })
|
|
||||||
```
|
|
||||||
|
|
||||||
See [xplr.util.explore][6].
|
|
||||||
|
|
||||||
[1]: https://en.wikipedia.org/wiki/Approximate_string_matching
|
|
||||||
[2]: sorting.md
|
|
||||||
[3]: #pattern
|
|
||||||
[4]: #recoverable_focus
|
|
||||||
[5]: #algorithm
|
|
||||||
[6]: xplr.util.md#xplrutilexplore
|
|
||||||
[7]: #unordered
|
|
||||||
[8]: general-config.md#xplrconfiggeneralsearchalgorithm
|
|
||||||
[9]: general-config.md#xplrconfiggeneralsearchunordered
|
|
@ -1,96 +0,0 @@
|
|||||||
# Sum Type
|
|
||||||
|
|
||||||
> This section isn't specific to xplr. However, since xplr configuration makes
|
|
||||||
> heavy use of this particular data type, even though it isn't available in
|
|
||||||
> most of the mainstream programming languages (yet), making it a wild or
|
|
||||||
> unfamiliar concept for many, it's worth doing a quick introduction here.
|
|
||||||
>
|
|
||||||
> If you're already familiar with [Sum Type / Tagged Union][1] (e.g. Rust's
|
|
||||||
> enum), you can skip ahead.
|
|
||||||
|
|
||||||
While reading this doc, you'll come across some data types like [Layout][2],
|
|
||||||
[Color][4], [Message][3] etc. that says something like "x is a sum type that
|
|
||||||
can be any of the following", and then you'll see a list of strings and/or lua
|
|
||||||
tables just below.
|
|
||||||
|
|
||||||
Yes, they are actually sum types, i.e. they can be any of the given set of
|
|
||||||
tagged variants listed there.
|
|
||||||
|
|
||||||
Notice the word "be". Unlike classes or structs (aka product types), they can't
|
|
||||||
"have" values, they can only "be" the value, or rather, be one of the possible
|
|
||||||
set of values.
|
|
||||||
|
|
||||||
Also notice the word "tagged". Unlike the single variant `null`, or the dual
|
|
||||||
variant `boolean` types, the variants of sum types are tagged (i.e. named), and
|
|
||||||
may further have, or be, value or values of any data type.
|
|
||||||
|
|
||||||
A simple example of a sum type is an enum. Many programming languages have
|
|
||||||
them, but only a few modern programming languages allow nesting other types
|
|
||||||
into a sum type.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
enum Color {
|
|
||||||
Red,
|
|
||||||
Green,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, `Color` can be one of two possible set of values: `Red` and `Green`, just
|
|
||||||
like `boolean`, but unlike `boolean`, being tagged allows `Color` to have more
|
|
||||||
than two variants if required, by changing the definition.
|
|
||||||
|
|
||||||
e.g.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
enum Color {
|
|
||||||
Red,
|
|
||||||
Green,
|
|
||||||
Blue,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We'd document it here as:
|
|
||||||
|
|
||||||
> Result is a sum type that can be one of the following:
|
|
||||||
>
|
|
||||||
> - "Red"
|
|
||||||
> - "Green"
|
|
||||||
> - "Blue"
|
|
||||||
|
|
||||||
But some languages (like Rust, Haskell, Elm etc.) go even further, allowing us
|
|
||||||
to associate each branch of the enum with further nested types like:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
enum Layout {
|
|
||||||
Table,
|
|
||||||
HelpMenu,
|
|
||||||
Horizontal {
|
|
||||||
config: LayoutConfig, // A product type (similar to class/struct)
|
|
||||||
splits: Vec<Layout> // A list of "Layout"s (i.e. list of sum types)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here, as we can see, unlike the first example, some of `Layout`'s possible
|
|
||||||
variants can have further nested types associated with them. Note that
|
|
||||||
`Horizontal` here can have a sum type (e.g. enum), or a product type (e.g.
|
|
||||||
class/struct), or both (any number of them actually) nested in it. But the
|
|
||||||
nested values will only exist when `Layout` is `Horizontal`.
|
|
||||||
|
|
||||||
We'd document it here as:
|
|
||||||
|
|
||||||
> Layout is a sum type that can be one of the following:
|
|
||||||
>
|
|
||||||
> - "Table"
|
|
||||||
> - "HelpMenu"
|
|
||||||
> - { Horizontal = { config = Layout Config, splits = { Layout, ... } }
|
|
||||||
|
|
||||||
And then we'd go on documenting whatever `Layout Config` is.
|
|
||||||
|
|
||||||
So, there you go. This is exactly what sum types are - glorified enums that can
|
|
||||||
have nested types in each branch.
|
|
||||||
|
|
||||||
[1]: https://en.wikipedia.org/wiki/Tagged_union
|
|
||||||
[2]: layout.md
|
|
||||||
[3]: message.md
|
|
||||||
[4]: style.md#color
|
|
@ -0,0 +1,34 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
- [x] Saner key bindings.
|
||||||
|
- [x] Pipes.
|
||||||
|
- [x] Native search & filter.
|
||||||
|
- [x] Create, copy, move, delete files directly.
|
||||||
|
- [x] logging support.
|
||||||
|
- [x] Version compatibility instructions.
|
||||||
|
- [x] Implement CLI arguments.
|
||||||
|
- ~Add support for tabs and/or panes (non native)~ [hacked][1] | [discussion][2]
|
||||||
|
- ~Implement bookmarks.~ [hacked][3]
|
||||||
|
- [x] Add sorting support.
|
||||||
|
- [x] Add filter support.
|
||||||
|
- [x] File previews.
|
||||||
|
- [x] Implement plugins support (or some way to easily share configuration).
|
||||||
|
- [x] Bigger (and better) help menu.
|
||||||
|
- [x] Offline docs.
|
||||||
|
- [ ] Support for background services
|
||||||
|
- [x] ~Customize~ switch UI at run-time.
|
||||||
|
- [ ] More tests and benchmarks.
|
||||||
|
- [ ] Measure code coverage.
|
||||||
|
- [ ] Improve the [vim plugin][4].
|
||||||
|
- [ ] Cleanup, refactor, optimize.
|
||||||
|
|
||||||
|
[add more][5]
|
||||||
|
|
||||||
|
Like this project so far? **[Please consider contributing][6].**
|
||||||
|
|
||||||
|
[1]: https://github.com/sayanarijit/xplr/wiki/Hacks#spawn-multiple-sessions-in-different-windows
|
||||||
|
[2]: https://github.com/sayanarijit/xplr/discussions/15
|
||||||
|
[3]: https://github.com/sayanarijit/xplr/wiki/Hacks#bookmark
|
||||||
|
[4]: https://github.com/sayanarijit/xplr.vim
|
||||||
|
[5]: https://github.com/sayanarijit/xplr/discussions/2
|
||||||
|
[6]: contribute.md
|
@ -1,528 +0,0 @@
|
|||||||
### xplr.util.version
|
|
||||||
|
|
||||||
Get the xplr version details.
|
|
||||||
|
|
||||||
Type: function() -> { major: number, minor: number, patch: number }
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.version()
|
|
||||||
-- { major = 0, minor = 0, patch = 0 }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.debug
|
|
||||||
|
|
||||||
Print the given value to the console, and return it as a string.
|
|
||||||
Useful for debugging.
|
|
||||||
|
|
||||||
Type: function( value ) -> string
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.debug({ foo = "bar", bar = function() end })
|
|
||||||
-- {
|
|
||||||
-- ["bar"] = function: 0x55e5cebdeae0,
|
|
||||||
-- ["foo"] = "bar",
|
|
||||||
-- }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.clone
|
|
||||||
|
|
||||||
Clone/deepcopy a Lua value. Doesn't work with functions.
|
|
||||||
|
|
||||||
Type: function( value ) -> value
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local val = { foo = "bar" }
|
|
||||||
local val_clone = xplr.util.clone(val)
|
|
||||||
val.foo = "baz"
|
|
||||||
print(val_clone.foo)
|
|
||||||
-- "bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.exists
|
|
||||||
|
|
||||||
Check if the given path exists.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> boolean
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.exists("/foo/bar")
|
|
||||||
-- true
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.is_dir
|
|
||||||
|
|
||||||
Check if the given path is a directory.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> boolean
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.is_dir("/foo/bar")
|
|
||||||
-- true
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.is_file
|
|
||||||
|
|
||||||
Check if the given path is a file.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> boolean
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.is_file("/foo/bar")
|
|
||||||
-- true
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.is_symlink
|
|
||||||
|
|
||||||
Check if the given path is a symlink.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> boolean
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.is_file("/foo/bar")
|
|
||||||
-- true
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.is_absolute
|
|
||||||
|
|
||||||
Check if the given path is an absolute path.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> boolean
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.is_absolute("/foo/bar")
|
|
||||||
-- true
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.path_split
|
|
||||||
|
|
||||||
Split a path into its components.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> boolean
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.path_split("/foo/bar")
|
|
||||||
-- { "/", "foo", "bar" }
|
|
||||||
|
|
||||||
xplr.util.path_split(".././foo")
|
|
||||||
-- { "..", "foo" }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.node
|
|
||||||
|
|
||||||
Get [Node][5] information of a given path.
|
|
||||||
Doesn't check if the path exists.
|
|
||||||
Returns nil if the path is "/".
|
|
||||||
Errors out if absolute path can't be obtained.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> [Node][5]|nil
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.node("./bar")
|
|
||||||
-- { parent = "/pwd", relative_path = "bar", absolute_path = "/pwd/bar", ... }
|
|
||||||
|
|
||||||
xplr.util.node("/")
|
|
||||||
-- nil
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.node_type
|
|
||||||
|
|
||||||
Get the configured [Node Type][6] of a given [Node][5].
|
|
||||||
|
|
||||||
Type: function( [Node][5], [xplr.config.node_types][7]|nil ) -> [Node Type][6]
|
|
||||||
|
|
||||||
If the second argument is missing, global config `xplr.config.node_types`
|
|
||||||
will be used.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.node_type(app.focused_node)
|
|
||||||
-- { style = { fg = "Red", ... }, meta = { icon = "", ... } ... }
|
|
||||||
|
|
||||||
xplr.util.node_type(xplr.util.node("/foo/bar"), xplr.config.node_types)
|
|
||||||
-- { style = { fg = "Red", ... }, meta = { icon = "", ... } ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.dirname
|
|
||||||
|
|
||||||
Get the directory name of a given path.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> path:string|nil
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.dirname("/foo/bar")
|
|
||||||
-- "/foo"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.basename
|
|
||||||
|
|
||||||
Get the base name of a given path.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> path:string|nil
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.basename("/foo/bar")
|
|
||||||
-- "bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.absolute
|
|
||||||
|
|
||||||
Get the absolute path of the given path by prepending $PWD.
|
|
||||||
It doesn't check if the path exists.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> path:string
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.absolute("foo/bar")
|
|
||||||
-- "/tmp/foo/bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.relative_to
|
|
||||||
|
|
||||||
Get the relative path based on the given base path or current working dir.
|
|
||||||
Will error if it fails to determine a relative path.
|
|
||||||
|
|
||||||
Type: function( path:string, options:table|nil ) -> path:string
|
|
||||||
|
|
||||||
Options type: { base:string|nil, with_prefix_dots:bookean|nil, without_suffix_dots:boolean|nil }
|
|
||||||
|
|
||||||
- If `base` path is given, the path will be relative to it.
|
|
||||||
- If `with_prefix_dots` is true, the path will always start with dots `..` / `.`
|
|
||||||
- If `without_suffix_dots` is true, the name will be visible instead of dots `..` / `.`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.relative_to("/present/working/directory")
|
|
||||||
-- "."
|
|
||||||
|
|
||||||
xplr.util.relative_to("/present/working/directory/foo")
|
|
||||||
-- "foo"
|
|
||||||
|
|
||||||
xplr.util.relative_to("/present/working/directory/foo", { with_prefix_dots = true })
|
|
||||||
-- "./foo"
|
|
||||||
|
|
||||||
xplr.util.relative_to("/present/working/directory", { without_suffix_dots = true })
|
|
||||||
-- "../directory"
|
|
||||||
|
|
||||||
xplr.util.relative_to("/present/working")
|
|
||||||
-- ".."
|
|
||||||
|
|
||||||
xplr.util.relative_to("/present/working", { without_suffix_dots = true })
|
|
||||||
-- "../../working"
|
|
||||||
|
|
||||||
xplr.util.relative_to("/present/working/directory", { base = "/present/foo/bar" })
|
|
||||||
-- "../../working/directory"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.shorten
|
|
||||||
|
|
||||||
Shorten the given absolute path using the following rules:
|
|
||||||
|
|
||||||
- either relative to your home dir if it makes sense
|
|
||||||
- or relative to the current working directory
|
|
||||||
- or absolute path if it makes the most sense
|
|
||||||
|
|
||||||
Type: Similar to `xplr.util.relative_to`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.shorten("/home/username/.config")
|
|
||||||
-- "~/.config"
|
|
||||||
|
|
||||||
xplr.util.shorten("/present/working/directory")
|
|
||||||
-- "."
|
|
||||||
|
|
||||||
xplr.util.shorten("/present/working/directory/foo")
|
|
||||||
-- "foo"
|
|
||||||
|
|
||||||
xplr.util.shorten("/present/working/directory/foo", { with_prefix_dots = true })
|
|
||||||
-- "./foo"
|
|
||||||
|
|
||||||
xplr.util.shorten("/present/working/directory", { without_suffix_dots = true })
|
|
||||||
-- "../directory"
|
|
||||||
|
|
||||||
xplr.util.shorten("/present/working/directory", { base = "/present/foo/bar" })
|
|
||||||
-- "../../working/directory"
|
|
||||||
|
|
||||||
xplr.util.shorten("/tmp")
|
|
||||||
-- "/tmp"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.explore
|
|
||||||
|
|
||||||
Explore directories with the given explorer config.
|
|
||||||
|
|
||||||
Type: function( path:string, [ExplorerConfig][1]|nil ) -> { [Node][2], ... }
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
|
|
||||||
xplr.util.explore("/tmp")
|
|
||||||
-- { { absolute_path = "/tmp/a", ... }, ... }
|
|
||||||
|
|
||||||
xplr.util.explore("/tmp", app.explorer_config)
|
|
||||||
-- { { absolute_path = "/tmp/a", ... }, ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.shell_execute
|
|
||||||
|
|
||||||
Execute shell commands safely.
|
|
||||||
|
|
||||||
Type: function( program:string, args:{ string, ... }|nil ) -> { stdout = string, stderr = string, returncode = number|nil }
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.shell_execute("pwd")
|
|
||||||
-- { stdout = "/present/working/directory", stderr = "", returncode = 0 }
|
|
||||||
|
|
||||||
xplr.util.shell_execute("bash", {"-c", "xplr --help"})
|
|
||||||
-- { stdout = "xplr...", stderr = "", returncode = 0 }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.shell_quote
|
|
||||||
|
|
||||||
Quote commands and paths safely.
|
|
||||||
|
|
||||||
Type: function( string ) -> string
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.shell_quote("a'b\"c")
|
|
||||||
-- 'a'"'"'b"c'
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.shell_escape
|
|
||||||
|
|
||||||
Escape commands and paths safely.
|
|
||||||
|
|
||||||
Type: function( string ) -> string
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.shell_escape("a'b\"c")
|
|
||||||
-- "\"a'b\\\"c\""
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.from_json
|
|
||||||
|
|
||||||
Load JSON string into Lua value.
|
|
||||||
|
|
||||||
Type: function( string ) -> any
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.from_json([[{"foo": "bar"}]])
|
|
||||||
-- { foo = "bar" }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.to_json
|
|
||||||
|
|
||||||
Dump Lua value into JSON (i.e. also YAML) string.
|
|
||||||
|
|
||||||
Type: function( value ) -> string
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.to_json({ foo = "bar" })
|
|
||||||
-- [[{ "foo": "bar" }]]
|
|
||||||
|
|
||||||
xplr.util.to_json({ foo = "bar" }, { pretty = true })
|
|
||||||
-- [[{
|
|
||||||
-- "foo": "bar"
|
|
||||||
-- }]]
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.from_yaml
|
|
||||||
|
|
||||||
Load YAML (i.e. also JSON) string into Lua value.
|
|
||||||
|
|
||||||
Type: function( string ) -> value
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.from_yaml([[{foo: bar}]])
|
|
||||||
-- { foo = "bar" }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.to_yaml
|
|
||||||
|
|
||||||
Dump Lua value into YAML string.
|
|
||||||
|
|
||||||
Type: function( value ) -> string
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.to_yaml({ foo = "bar" })
|
|
||||||
-- "foo: bar"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.lscolor
|
|
||||||
|
|
||||||
Get a [Style][3] object for the given path based on the LS_COLORS
|
|
||||||
environment variable.
|
|
||||||
|
|
||||||
Type: function( path:string ) -> [Style][3]
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.lscolor("Desktop")
|
|
||||||
-- { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.paint
|
|
||||||
|
|
||||||
Apply style (escape sequence) to string using a given [Style][3] object.
|
|
||||||
|
|
||||||
Type: function( string, [Style][3]|nil ) -> string
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.paint("Desktop", { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} })
|
|
||||||
-- "\u001b[31mDesktop\u001b[0m"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.style_mix
|
|
||||||
|
|
||||||
Mix multiple [Style][3] objects into one.
|
|
||||||
|
|
||||||
Type: function( { [Style][3], [Style][3], ... } ) -> [Style][3]
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.style_mix({{ fg = "Red" }, { bg = "Blue" }, { add_modifiers = {"Bold"} }})
|
|
||||||
-- { fg = "Red", bg = "Blue", add_modifiers = { "Bold" }, sub_modifiers = {} }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.textwrap
|
|
||||||
|
|
||||||
Wrap the given text to fit the specified width.
|
|
||||||
It will try to not split words when possible.
|
|
||||||
|
|
||||||
Type: function( string, options:number|table ) -> { string, ...}
|
|
||||||
|
|
||||||
Options type: { width = number, initial_indent = string|nil, subsequent_indent = string|nil, break_words = boolean|nil }
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.textwrap("this will be cut off", 11)
|
|
||||||
-- { "this will', 'be cut off" }
|
|
||||||
|
|
||||||
xplr.util.textwrap(
|
|
||||||
"this will be cut off",
|
|
||||||
{ width = 12, initial_indent = "", subsequent_indent = " ", break_words = false }
|
|
||||||
)
|
|
||||||
-- { "this will be", " cut off" }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.layout_replace
|
|
||||||
|
|
||||||
Find the target layout in the given layout and replace it with the replacement layout,
|
|
||||||
returning a new layout.
|
|
||||||
|
|
||||||
Type: function( layout:[Layout][4], target:[Layout][4], replacement:[Layout][4] ) -> layout:[Layout][4]
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local layout = {
|
|
||||||
Horizontal = {
|
|
||||||
splits = {
|
|
||||||
"Table", -- Target
|
|
||||||
"HelpMenu",
|
|
||||||
},
|
|
||||||
config = ...,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
xplr.util.layout_replace(layout, "Table", "Selection")
|
|
||||||
-- {
|
|
||||||
-- Horizontal = {
|
|
||||||
-- splits = {
|
|
||||||
-- "Selection", -- Replacement
|
|
||||||
-- "HelpMenu",
|
|
||||||
-- },
|
|
||||||
-- config = ...
|
|
||||||
-- }
|
|
||||||
-- }
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.permissions_rwx
|
|
||||||
|
|
||||||
Convert [Permission][8] to rwxrwxrwx representation with special bits.
|
|
||||||
|
|
||||||
Type: function( [Permission][8] ) -> string
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.permissions_rwx({ user_read = true })
|
|
||||||
-- "r--------"
|
|
||||||
|
|
||||||
xplr.util.permissions_rwx(app.focused_node.permission)
|
|
||||||
-- "rwxrwsrwT"
|
|
||||||
```
|
|
||||||
|
|
||||||
### xplr.util.permissions_octal
|
|
||||||
|
|
||||||
Convert [Permission][8] to octal representation.
|
|
||||||
|
|
||||||
Type: function( [Permission][8] ) -> { number, number, number, number }
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```lua
|
|
||||||
xplr.util.permissions_octal({ user_read = true })
|
|
||||||
-- { 0, 4, 0, 0 }
|
|
||||||
|
|
||||||
xplr.util.permissions_octal(app.focused_node.permission)
|
|
||||||
-- { 0, 7, 5, 4 }
|
|
||||||
```
|
|
||||||
|
|
||||||
[1]: https://xplr.dev/en/lua-function-calls#explorer-config
|
|
||||||
[2]: https://xplr.dev/en/lua-function-calls#node
|
|
||||||
[3]: https://xplr.dev/en/style
|
|
||||||
[4]: https://xplr.dev/en/layout
|
|
||||||
[5]: https://xplr.dev/en/lua-function-calls#node
|
|
||||||
[6]: https://xplr.dev/en/node-type
|
|
||||||
[7]: https://xplr.dev/en/node_types
|
|
||||||
[8]: https://xplr.dev/en/column-renderer#permission
|
|
@ -0,0 +1 @@
|
|||||||
|
../en/book
|
@ -1,11 +0,0 @@
|
|||||||
v="0.4.40"
|
|
||||||
|
|
||||||
curl -L https://github.com/rust-lang/mdBook/releases/download/v$v/mdbook-v$v-x86_64-unknown-linux-gnu.tar.gz -o mdbook.tgz \
|
|
||||||
&& tar xzvf mdbook.tgz \
|
|
||||||
&& ./mdbook build docs/en \
|
|
||||||
&& mkdir dist \
|
|
||||||
&& mv -v docs/en/book/html dist/en \
|
|
||||||
&& mv -v assets dist \
|
|
||||||
&& mv -v docs/landing/index.html docs/landing/css docs/landing/js dist \
|
|
||||||
&& rm -v mdbook \
|
|
||||||
&& rm -v mdbook.tgz
|
|
@ -1,26 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1704262187,
|
|
||||||
"narHash": "sha256-N4j9qghlp/Eb3Z11WF7Cb9U91AXwpascUbLH7YKMcLc=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "65f0d241783c94a08e4c9a3870736fc8854dd520",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
{
|
|
||||||
description = "xplr - A hackable, minimal, fast TUI file explorer";
|
|
||||||
|
|
||||||
inputs = {
|
|
||||||
nixpkgs.url = "github:nixos/nixpkgs";
|
|
||||||
};
|
|
||||||
|
|
||||||
outputs = inputs@{ self, nixpkgs, ... }:
|
|
||||||
let
|
|
||||||
lib = nixpkgs.lib;
|
|
||||||
|
|
||||||
darwin = [ "x86_64-darwin" "aarch64-darwin" ];
|
|
||||||
linux = [ "x86_64-linux" "x86_64-linux-musl" "aarch64-linux" "aarch64-linux-android" "i86_64-linux" ];
|
|
||||||
allSystems = darwin ++ linux;
|
|
||||||
|
|
||||||
forEachSystem = systems: f: lib.genAttrs systems (system: f system);
|
|
||||||
forAllSystems = forEachSystem allSystems;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
packages = forAllSystems (system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
in
|
|
||||||
rec {
|
|
||||||
# e.g. nix build .#xplr
|
|
||||||
xplr = pkgs.rustPlatform.buildRustPackage rec {
|
|
||||||
name = "xplr";
|
|
||||||
src = ./.;
|
|
||||||
cargoLock = {
|
|
||||||
lockFile = ./Cargo.lock;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# e.g. nix build .#cross.x86_64-linux-musl.xplr --impure
|
|
||||||
cross = forEachSystem (lib.filter (sys: sys != system) allSystems) (targetSystem:
|
|
||||||
let
|
|
||||||
crossPkgs = import nixpkgs { localSystem = system; crossSystem = targetSystem; };
|
|
||||||
in
|
|
||||||
{ inherit (crossPkgs) xplr; }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
defaultPackage = forAllSystems (system: self.packages.${system}.xplr);
|
|
||||||
devShells = forAllSystems (system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
devRequirements = with pkgs; [
|
|
||||||
gcc
|
|
||||||
gnumake
|
|
||||||
clippy
|
|
||||||
rustc
|
|
||||||
cargo
|
|
||||||
rustfmt
|
|
||||||
rust-analyzer
|
|
||||||
];
|
|
||||||
in
|
|
||||||
{
|
|
||||||
default = pkgs.mkShell {
|
|
||||||
RUST_BACKTRACE = 1;
|
|
||||||
|
|
||||||
# For cross compilation
|
|
||||||
NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM = 1;
|
|
||||||
|
|
||||||
buildInputs = devRequirements;
|
|
||||||
packages = devRequirements;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
name: xplr
|
|
||||||
version: git
|
|
||||||
summary: A hackable, minimal, fast TUI file explorer
|
|
||||||
description: |
|
|
||||||
xplr is a terminal UI based file explorer
|
|
||||||
that aims to increase our terminal productivity by being a flexible,
|
|
||||||
interactive orchestrator for the ever growing awesome command-line
|
|
||||||
utilities that work with the file-system.
|
|
||||||
source-code: https://github.com/sayanarijit/xplr
|
|
||||||
issues: https://github.com/sayanarijit/xplr/issues
|
|
||||||
website: https://xplr.dev/
|
|
||||||
|
|
||||||
base: core20
|
|
||||||
grade: devel # must be 'stable' to release into candidate/stable channels
|
|
||||||
confinement: devmode # use 'strict' once you have the right plugs and slots
|
|
||||||
|
|
||||||
|
|
||||||
parts:
|
|
||||||
xplr:
|
|
||||||
plugin: rust
|
|
||||||
source: .
|
|
||||||
|
|
||||||
apps:
|
|
||||||
xplr:
|
|
||||||
command: bin/xplr
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -1,224 +0,0 @@
|
|||||||
// Things of the past, mostly bad decisions, which cannot erased, stays in this
|
|
||||||
// haunted module.
|
|
||||||
|
|
||||||
use crate::app;
|
|
||||||
use crate::lua;
|
|
||||||
use crate::ui::block;
|
|
||||||
use crate::ui::string_to_text;
|
|
||||||
use crate::ui::Constraint;
|
|
||||||
use crate::ui::ContentRendererArg;
|
|
||||||
use crate::ui::UI;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tui::layout::Constraint as TuiConstraint;
|
|
||||||
use tui::layout::Rect as TuiRect;
|
|
||||||
use tui::widgets::Cell;
|
|
||||||
use tui::widgets::List;
|
|
||||||
use tui::widgets::ListItem;
|
|
||||||
use tui::widgets::Paragraph;
|
|
||||||
use tui::widgets::Row;
|
|
||||||
use tui::widgets::Table;
|
|
||||||
use tui::Frame;
|
|
||||||
|
|
||||||
/// A cursed enum from crate::ui.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub enum ContentBody {
|
|
||||||
/// A paragraph to render
|
|
||||||
StaticParagraph { render: String },
|
|
||||||
|
|
||||||
/// A Lua function that returns a paragraph to render
|
|
||||||
DynamicParagraph { render: String },
|
|
||||||
|
|
||||||
/// List to render
|
|
||||||
StaticList { render: Vec<String> },
|
|
||||||
|
|
||||||
/// A Lua function that returns lines to render
|
|
||||||
DynamicList { render: String },
|
|
||||||
|
|
||||||
/// A table to render
|
|
||||||
StaticTable {
|
|
||||||
widths: Vec<Constraint>,
|
|
||||||
col_spacing: Option<u16>,
|
|
||||||
render: Vec<Vec<String>>,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// A Lua function that returns a table to render
|
|
||||||
DynamicTable {
|
|
||||||
widths: Vec<Constraint>,
|
|
||||||
col_spacing: Option<u16>,
|
|
||||||
render: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A cursed struct from crate::ui.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct CustomContent {
|
|
||||||
pub title: Option<String>,
|
|
||||||
pub body: ContentBody,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A cursed function from crate::ui.
|
|
||||||
pub fn draw_custom_content(
|
|
||||||
ui: &mut UI,
|
|
||||||
f: &mut Frame,
|
|
||||||
layout_size: TuiRect,
|
|
||||||
app: &app::App,
|
|
||||||
content: CustomContent,
|
|
||||||
) {
|
|
||||||
let config = app.config.general.panel_ui.default.clone();
|
|
||||||
let title = content.title;
|
|
||||||
let body = content.body;
|
|
||||||
|
|
||||||
match body {
|
|
||||||
ContentBody::StaticParagraph { render } => {
|
|
||||||
let render = string_to_text(render);
|
|
||||||
let content = Paragraph::new(render).block(block(
|
|
||||||
config,
|
|
||||||
title.map(|t| format!(" {t} ")).unwrap_or_default(),
|
|
||||||
));
|
|
||||||
f.render_widget(content, layout_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentBody::DynamicParagraph { render } => {
|
|
||||||
let ctx = ContentRendererArg {
|
|
||||||
app: app.to_lua_ctx_light(),
|
|
||||||
layout_size: layout_size.into(),
|
|
||||||
screen_size: ui.screen_size.into(),
|
|
||||||
scrolltop: ui.scrolltop as u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
let render = lua::serialize(ui.lua, &ctx)
|
|
||||||
.map(|arg| {
|
|
||||||
lua::call(ui.lua, &render, arg).unwrap_or_else(|e| format!("{e:?}"))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|e| e.to_string());
|
|
||||||
|
|
||||||
let render = string_to_text(render);
|
|
||||||
|
|
||||||
let content = Paragraph::new(render).block(block(
|
|
||||||
config,
|
|
||||||
title.map(|t| format!(" {t} ")).unwrap_or_default(),
|
|
||||||
));
|
|
||||||
f.render_widget(content, layout_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentBody::StaticList { render } => {
|
|
||||||
let items = render
|
|
||||||
.into_iter()
|
|
||||||
.map(string_to_text)
|
|
||||||
.map(ListItem::new)
|
|
||||||
.collect::<Vec<ListItem>>();
|
|
||||||
|
|
||||||
let content = List::new(items).block(block(
|
|
||||||
config,
|
|
||||||
title.map(|t| format!(" {t} ")).unwrap_or_default(),
|
|
||||||
));
|
|
||||||
f.render_widget(content, layout_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentBody::DynamicList { render } => {
|
|
||||||
let ctx = ContentRendererArg {
|
|
||||||
app: app.to_lua_ctx_light(),
|
|
||||||
layout_size: layout_size.into(),
|
|
||||||
screen_size: ui.screen_size.into(),
|
|
||||||
scrolltop: ui.scrolltop as u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
let items = lua::serialize(ui.lua, &ctx)
|
|
||||||
.map(|arg| {
|
|
||||||
lua::call(ui.lua, &render, arg)
|
|
||||||
.unwrap_or_else(|e| vec![format!("{e:?}")])
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|e| vec![e.to_string()])
|
|
||||||
.into_iter()
|
|
||||||
.map(string_to_text)
|
|
||||||
.map(ListItem::new)
|
|
||||||
.collect::<Vec<ListItem>>();
|
|
||||||
|
|
||||||
let content = List::new(items).block(block(
|
|
||||||
config,
|
|
||||||
title.map(|t| format!(" {t} ")).unwrap_or_default(),
|
|
||||||
));
|
|
||||||
f.render_widget(content, layout_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentBody::StaticTable {
|
|
||||||
widths,
|
|
||||||
col_spacing,
|
|
||||||
render,
|
|
||||||
} => {
|
|
||||||
let rows = render
|
|
||||||
.into_iter()
|
|
||||||
.map(|cols| {
|
|
||||||
Row::new(
|
|
||||||
cols.into_iter()
|
|
||||||
.map(string_to_text)
|
|
||||||
.map(Cell::from)
|
|
||||||
.collect::<Vec<Cell>>(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<Row>>();
|
|
||||||
|
|
||||||
let widths = widths
|
|
||||||
.into_iter()
|
|
||||||
.map(|w| w.to_tui(ui.screen_size, layout_size))
|
|
||||||
.collect::<Vec<TuiConstraint>>();
|
|
||||||
|
|
||||||
let content = Table::new(rows, widths)
|
|
||||||
.column_spacing(col_spacing.unwrap_or(1))
|
|
||||||
.block(block(
|
|
||||||
config,
|
|
||||||
title.map(|t| format!(" {t} ")).unwrap_or_default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
f.render_widget(content, layout_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentBody::DynamicTable {
|
|
||||||
widths,
|
|
||||||
col_spacing,
|
|
||||||
render,
|
|
||||||
} => {
|
|
||||||
let ctx = ContentRendererArg {
|
|
||||||
app: app.to_lua_ctx_light(),
|
|
||||||
layout_size: layout_size.into(),
|
|
||||||
screen_size: ui.screen_size.into(),
|
|
||||||
scrolltop: ui.scrolltop as u16,
|
|
||||||
};
|
|
||||||
|
|
||||||
let rows = lua::serialize(ui.lua, &ctx)
|
|
||||||
.map(|arg| {
|
|
||||||
lua::call(ui.lua, &render, arg)
|
|
||||||
.unwrap_or_else(|e| vec![vec![format!("{e:?}")]])
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|e| vec![vec![e.to_string()]])
|
|
||||||
.into_iter()
|
|
||||||
.map(|cols| {
|
|
||||||
Row::new(
|
|
||||||
cols.into_iter()
|
|
||||||
.map(string_to_text)
|
|
||||||
.map(Cell::from)
|
|
||||||
.collect::<Vec<Cell>>(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<Row>>();
|
|
||||||
|
|
||||||
let widths = widths
|
|
||||||
.into_iter()
|
|
||||||
.map(|w| w.to_tui(ui.screen_size, layout_size))
|
|
||||||
.collect::<Vec<TuiConstraint>>();
|
|
||||||
|
|
||||||
let mut content = Table::new(rows, &widths).block(block(
|
|
||||||
config,
|
|
||||||
title.map(|t| format!(" {t} ")).unwrap_or_default(),
|
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(col_spacing) = col_spacing {
|
|
||||||
content = content.column_spacing(col_spacing);
|
|
||||||
};
|
|
||||||
|
|
||||||
f.render_widget(content, layout_size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
use std::{env, path::PathBuf};
|
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use xdg::BaseDirectories;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref BASE_DIRS: Option<BaseDirectories> = BaseDirectories::new().ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn home_dir() -> Option<PathBuf> {
|
|
||||||
home::home_dir()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn config_dir() -> Option<PathBuf> {
|
|
||||||
BASE_DIRS.as_ref().map(|base| base.get_config_home())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn runtime_dir() -> PathBuf {
|
|
||||||
let Some(dir) = BASE_DIRS
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|base| base.get_runtime_directory().ok())
|
|
||||||
else {
|
|
||||||
return env::temp_dir();
|
|
||||||
};
|
|
||||||
dir.clone()
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,904 +0,0 @@
|
|||||||
use crate::app::VERSION;
|
|
||||||
use crate::config::NodeTypesConfig;
|
|
||||||
use crate::explorer;
|
|
||||||
use crate::lua;
|
|
||||||
use crate::msg::in_::external::ExplorerConfig;
|
|
||||||
use crate::node::Node;
|
|
||||||
use crate::path;
|
|
||||||
use crate::path::RelativityConfig;
|
|
||||||
use crate::permissions::Octal;
|
|
||||||
use crate::permissions::Permissions;
|
|
||||||
use crate::ui;
|
|
||||||
use crate::ui::Layout;
|
|
||||||
use crate::ui::Style;
|
|
||||||
use crate::ui::WrapOptions;
|
|
||||||
use anyhow::Result;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use lscolors::LsColors;
|
|
||||||
use mlua::Error as LuaError;
|
|
||||||
use mlua::Lua;
|
|
||||||
use mlua::LuaSerdeExt;
|
|
||||||
use mlua::Table;
|
|
||||||
use mlua::Value;
|
|
||||||
use path_absolutize::*;
|
|
||||||
use serde::de::Error;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json as json;
|
|
||||||
use serde_yaml as yaml;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref LS_COLORS: LsColors = LsColors::from_env().unwrap_or_default();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the xplr version details.
|
|
||||||
///
|
|
||||||
/// Type: function() -> { major: number, minor: number, patch: number }
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.version()
|
|
||||||
/// -- { major = 0, minor = 0, patch = 0 }
|
|
||||||
/// ```
|
|
||||||
pub fn version<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
||||||
struct Version {
|
|
||||||
major: u16,
|
|
||||||
minor: u16,
|
|
||||||
patch: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
let func = lua.create_function(|lua, ()| {
|
|
||||||
let (major, minor, patch, _) =
|
|
||||||
lua::parse_version(VERSION).map_err(LuaError::custom)?;
|
|
||||||
|
|
||||||
let version = Version {
|
|
||||||
major,
|
|
||||||
minor,
|
|
||||||
patch,
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = lua::serialize(lua, &version).map_err(LuaError::custom)?;
|
|
||||||
Ok(res)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
util.set("version", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print the given value to the console, and return it as a string.
|
|
||||||
/// Useful for debugging.
|
|
||||||
///
|
|
||||||
/// Type: function( value ) -> string
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.debug({ foo = "bar", bar = function() end })
|
|
||||||
/// -- {
|
|
||||||
/// -- ["bar"] = function: 0x55e5cebdeae0,
|
|
||||||
/// -- ["foo"] = "bar",
|
|
||||||
/// -- }
|
|
||||||
/// ```
|
|
||||||
pub fn debug<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|_, value: Value| {
|
|
||||||
let log = format!("{:#?}", value);
|
|
||||||
println!("{}", log);
|
|
||||||
Ok(log)
|
|
||||||
})?;
|
|
||||||
util.set("debug", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clone/deepcopy a Lua value. Doesn't work with functions.
|
|
||||||
///
|
|
||||||
/// Type: function( value ) -> value
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// local val = { foo = "bar" }
|
|
||||||
/// local val_clone = xplr.util.clone(val)
|
|
||||||
/// val.foo = "baz"
|
|
||||||
/// print(val_clone.foo)
|
|
||||||
/// -- "bar"
|
|
||||||
/// ```
|
|
||||||
pub fn clone<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(move |lua, value: Value| {
|
|
||||||
lua::serialize(lua, &value).map_err(LuaError::custom)
|
|
||||||
})?;
|
|
||||||
util.set("clone", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the given path exists.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> boolean
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.exists("/foo/bar")
|
|
||||||
/// -- true
|
|
||||||
/// ```
|
|
||||||
pub fn exists<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func =
|
|
||||||
lua.create_function(move |_, path: String| Ok(PathBuf::from(path).exists()))?;
|
|
||||||
util.set("exists", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the given path is a directory.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> boolean
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.is_dir("/foo/bar")
|
|
||||||
/// -- true
|
|
||||||
/// ```
|
|
||||||
pub fn is_dir<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func =
|
|
||||||
lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_dir()))?;
|
|
||||||
util.set("is_dir", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the given path is a file.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> boolean
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.is_file("/foo/bar")
|
|
||||||
/// -- true
|
|
||||||
/// ```
|
|
||||||
pub fn is_file<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func =
|
|
||||||
lua.create_function(move |_, path: String| Ok(PathBuf::from(path).is_file()))?;
|
|
||||||
util.set("is_file", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the given path is a symlink.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> boolean
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.is_file("/foo/bar")
|
|
||||||
/// -- true
|
|
||||||
/// ```
|
|
||||||
pub fn is_symlink<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua
|
|
||||||
.create_function(move |_, path: String| Ok(PathBuf::from(path).is_symlink()))?;
|
|
||||||
util.set("is_symlink", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the given path is an absolute path.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> boolean
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.is_absolute("/foo/bar")
|
|
||||||
/// -- true
|
|
||||||
/// ```
|
|
||||||
pub fn is_absolute<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua
|
|
||||||
.create_function(move |_, path: String| Ok(PathBuf::from(path).is_absolute()))?;
|
|
||||||
util.set("is_absolute", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Split a path into its components.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> boolean
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.path_split("/foo/bar")
|
|
||||||
/// -- { "/", "foo", "bar" }
|
|
||||||
///
|
|
||||||
/// xplr.util.path_split(".././foo")
|
|
||||||
/// -- { "..", "foo" }
|
|
||||||
/// ```
|
|
||||||
pub fn path_split<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(move |_, path: String| {
|
|
||||||
let components: Vec<String> = PathBuf::from(path)
|
|
||||||
.components()
|
|
||||||
.map(|c| c.as_os_str().to_string_lossy().to_string())
|
|
||||||
.collect();
|
|
||||||
Ok(components)
|
|
||||||
})?;
|
|
||||||
util.set("path_split", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get [Node][5] information of a given path.
|
|
||||||
/// Doesn't check if the path exists.
|
|
||||||
/// Returns nil if the path is "/".
|
|
||||||
/// Errors out if absolute path can't be obtained.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> [Node][5]|nil
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.node("./bar")
|
|
||||||
/// -- { parent = "/pwd", relative_path = "bar", absolute_path = "/pwd/bar", ... }
|
|
||||||
///
|
|
||||||
/// xplr.util.node("/")
|
|
||||||
/// -- nil
|
|
||||||
/// ```
|
|
||||||
pub fn node<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(move |lua, path: String| {
|
|
||||||
let path = PathBuf::from(path);
|
|
||||||
let abs = path.absolutize()?;
|
|
||||||
match (abs.parent(), abs.file_name()) {
|
|
||||||
(Some(parent), Some(name)) => {
|
|
||||||
let node = Node::new(
|
|
||||||
parent.to_string_lossy().to_string(),
|
|
||||||
name.to_string_lossy().to_string(),
|
|
||||||
);
|
|
||||||
Ok(lua::serialize(lua, &node).map_err(LuaError::custom)?)
|
|
||||||
}
|
|
||||||
(_, _) => Ok(Value::Nil),
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
util.set("node", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the configured [Node Type][6] of a given [Node][5].
|
|
||||||
///
|
|
||||||
/// Type: function( [Node][5], [xplr.config.node_types][7]|nil ) -> [Node Type][6]
|
|
||||||
///
|
|
||||||
/// If the second argument is missing, global config `xplr.config.node_types`
|
|
||||||
/// will be used.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.node_type(app.focused_node)
|
|
||||||
/// -- { style = { fg = "Red", ... }, meta = { icon = "", ... } ... }
|
|
||||||
///
|
|
||||||
/// xplr.util.node_type(xplr.util.node("/foo/bar"), xplr.config.node_types)
|
|
||||||
/// -- { style = { fg = "Red", ... }, meta = { icon = "", ... } ... }
|
|
||||||
/// ```
|
|
||||||
pub fn node_type<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func =
|
|
||||||
lua.create_function(move |lua, (node, config): (Table, Option<Table>)| {
|
|
||||||
let node: Node = lua.from_value(Value::Table(node))?;
|
|
||||||
let config: Table = if let Some(config) = config {
|
|
||||||
config
|
|
||||||
} else {
|
|
||||||
lua.globals()
|
|
||||||
.get::<_, Table>("xplr")?
|
|
||||||
.get::<_, Table>("config")?
|
|
||||||
.get::<_, Table>("node_types")?
|
|
||||||
};
|
|
||||||
let config: NodeTypesConfig = lua.from_value(Value::Table(config))?;
|
|
||||||
let node_type = config.get(&node);
|
|
||||||
let node_type = lua::serialize(lua, &node_type).map_err(LuaError::custom)?;
|
|
||||||
Ok(node_type)
|
|
||||||
})?;
|
|
||||||
util.set("node_type", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the directory name of a given path.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> path:string|nil
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.dirname("/foo/bar")
|
|
||||||
/// -- "/foo"
|
|
||||||
/// ```
|
|
||||||
pub fn dirname<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|_, path: String| {
|
|
||||||
let parent = PathBuf::from(path)
|
|
||||||
.parent()
|
|
||||||
.map(|p| p.to_string_lossy().to_string());
|
|
||||||
Ok(parent)
|
|
||||||
})?;
|
|
||||||
util.set("dirname", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the base name of a given path.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> path:string|nil
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.basename("/foo/bar")
|
|
||||||
/// -- "bar"
|
|
||||||
/// ```
|
|
||||||
pub fn basename<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|_, path: String| {
|
|
||||||
let parent = PathBuf::from(path)
|
|
||||||
.file_name()
|
|
||||||
.map(|p| p.to_string_lossy().to_string());
|
|
||||||
Ok(parent)
|
|
||||||
})?;
|
|
||||||
util.set("basename", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the absolute path of the given path by prepending $PWD.
|
|
||||||
/// It doesn't check if the path exists.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> path:string
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.absolute("foo/bar")
|
|
||||||
/// -- "/tmp/foo/bar"
|
|
||||||
/// ```
|
|
||||||
pub fn absolute<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|_, path: String| {
|
|
||||||
let abs = PathBuf::from(path)
|
|
||||||
.absolutize()?
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string();
|
|
||||||
Ok(abs)
|
|
||||||
})?;
|
|
||||||
util.set("absolute", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the relative path based on the given base path or current working dir.
|
|
||||||
/// Will error if it fails to determine a relative path.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string, options:table|nil ) -> path:string
|
|
||||||
///
|
|
||||||
/// Options type: { base:string|nil, with_prefix_dots:bookean|nil, without_suffix_dots:boolean|nil }
|
|
||||||
///
|
|
||||||
/// - If `base` path is given, the path will be relative to it.
|
|
||||||
/// - If `with_prefix_dots` is true, the path will always start with dots `..` / `.`
|
|
||||||
/// - If `without_suffix_dots` is true, the name will be visible instead of dots `..` / `.`
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.relative_to("/present/working/directory")
|
|
||||||
/// -- "."
|
|
||||||
///
|
|
||||||
/// xplr.util.relative_to("/present/working/directory/foo")
|
|
||||||
/// -- "foo"
|
|
||||||
///
|
|
||||||
/// xplr.util.relative_to("/present/working/directory/foo", { with_prefix_dots = true })
|
|
||||||
/// -- "./foo"
|
|
||||||
///
|
|
||||||
/// xplr.util.relative_to("/present/working/directory", { without_suffix_dots = true })
|
|
||||||
/// -- "../directory"
|
|
||||||
///
|
|
||||||
/// xplr.util.relative_to("/present/working")
|
|
||||||
/// -- ".."
|
|
||||||
///
|
|
||||||
/// xplr.util.relative_to("/present/working", { without_suffix_dots = true })
|
|
||||||
/// -- "../../working"
|
|
||||||
///
|
|
||||||
/// xplr.util.relative_to("/present/working/directory", { base = "/present/foo/bar" })
|
|
||||||
/// -- "../../working/directory"
|
|
||||||
/// ```
|
|
||||||
pub fn relative_to<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|lua, (path, config): (String, Option<Table>)| {
|
|
||||||
let config: Option<RelativityConfig<String>> =
|
|
||||||
lua.from_value(config.map(Value::Table).unwrap_or(Value::Nil))?;
|
|
||||||
path::relative_to(path, config.as_ref())
|
|
||||||
.map(|p| p.to_string_lossy().to_string())
|
|
||||||
.map_err(LuaError::custom)
|
|
||||||
})?;
|
|
||||||
util.set("relative_to", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorten the given absolute path using the following rules:
|
|
||||||
/// - either relative to your home dir if it makes sense
|
|
||||||
/// - or relative to the current working directory
|
|
||||||
/// - or absolute path if it makes the most sense
|
|
||||||
///
|
|
||||||
/// Type: Similar to `xplr.util.relative_to`
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.shorten("/home/username/.config")
|
|
||||||
/// -- "~/.config"
|
|
||||||
///
|
|
||||||
/// xplr.util.shorten("/present/working/directory")
|
|
||||||
/// -- "."
|
|
||||||
///
|
|
||||||
/// xplr.util.shorten("/present/working/directory/foo")
|
|
||||||
/// -- "foo"
|
|
||||||
///
|
|
||||||
/// xplr.util.shorten("/present/working/directory/foo", { with_prefix_dots = true })
|
|
||||||
/// -- "./foo"
|
|
||||||
///
|
|
||||||
/// xplr.util.shorten("/present/working/directory", { without_suffix_dots = true })
|
|
||||||
/// -- "../directory"
|
|
||||||
///
|
|
||||||
/// xplr.util.shorten("/present/working/directory", { base = "/present/foo/bar" })
|
|
||||||
/// -- "../../working/directory"
|
|
||||||
///
|
|
||||||
/// xplr.util.shorten("/tmp")
|
|
||||||
/// -- "/tmp"
|
|
||||||
/// ```
|
|
||||||
pub fn shorten<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func =
|
|
||||||
lua.create_function(move |lua, (path, config): (String, Option<Table>)| {
|
|
||||||
let config: Option<RelativityConfig<String>> =
|
|
||||||
lua.from_value(config.map(Value::Table).unwrap_or(Value::Nil))?;
|
|
||||||
path::shorten(path, config.as_ref()).map_err(LuaError::custom)
|
|
||||||
})?;
|
|
||||||
util.set("shorten", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Explore directories with the given explorer config.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string, [ExplorerConfig][1]|nil ) -> { [Node][2], ... }
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
///
|
|
||||||
/// xplr.util.explore("/tmp")
|
|
||||||
/// -- { { absolute_path = "/tmp/a", ... }, ... }
|
|
||||||
///
|
|
||||||
/// xplr.util.explore("/tmp", app.explorer_config)
|
|
||||||
/// -- { { absolute_path = "/tmp/a", ... }, ... }
|
|
||||||
/// ```
|
|
||||||
pub fn explore<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|lua, (path, config): (String, Option<Table>)| {
|
|
||||||
let config: ExplorerConfig = if let Some(cfg) = config {
|
|
||||||
lua.from_value(Value::Table(cfg))?
|
|
||||||
} else {
|
|
||||||
ExplorerConfig::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let nodes = explorer::explore(&PathBuf::from(path), &config)
|
|
||||||
.map_err(LuaError::custom)?;
|
|
||||||
let res = lua::serialize(lua, &nodes).map_err(LuaError::custom)?;
|
|
||||||
Ok(res)
|
|
||||||
})?;
|
|
||||||
util.set("explore", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute shell commands safely.
|
|
||||||
///
|
|
||||||
/// Type: function( program:string, args:{ string, ... }|nil ) -> { stdout = string, stderr = string, returncode = number|nil }
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.shell_execute("pwd")
|
|
||||||
/// -- { stdout = "/present/working/directory", stderr = "", returncode = 0 }
|
|
||||||
///
|
|
||||||
/// xplr.util.shell_execute("bash", {"-c", "xplr --help"})
|
|
||||||
/// -- { stdout = "xplr...", stderr = "", returncode = 0 }
|
|
||||||
/// ```
|
|
||||||
pub fn shell_execute<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func =
|
|
||||||
lua.create_function(|lua, (program, args): (String, Option<Vec<String>>)| {
|
|
||||||
let mut cmd = Command::new(program);
|
|
||||||
let mut cmd_ref = &mut cmd;
|
|
||||||
if let Some(args) = args {
|
|
||||||
cmd_ref = cmd_ref.args(args)
|
|
||||||
};
|
|
||||||
let output = cmd_ref.output()?;
|
|
||||||
|
|
||||||
let res = lua.create_table()?;
|
|
||||||
res.set("stdout", String::from_utf8_lossy(&output.stdout))?;
|
|
||||||
res.set("stderr", String::from_utf8_lossy(&output.stderr))?;
|
|
||||||
res.set("returncode", output.status.code())?;
|
|
||||||
Ok(res)
|
|
||||||
})?;
|
|
||||||
util.set("shell_execute", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Quote commands and paths safely.
|
|
||||||
///
|
|
||||||
/// Type: function( string ) -> string
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.shell_quote("a'b\"c")
|
|
||||||
/// -- 'a'"'"'b"c'
|
|
||||||
/// ```
|
|
||||||
pub fn shell_quote<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|_, string: String| {
|
|
||||||
Ok(format!("'{}'", string.replace('\'', r#"'"'"'"#)))
|
|
||||||
})?;
|
|
||||||
util.set("shell_quote", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Escape commands and paths safely.
|
|
||||||
///
|
|
||||||
/// Type: function( string ) -> string
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.shell_escape("a'b\"c")
|
|
||||||
/// -- "\"a'b\\\"c\""
|
|
||||||
/// ```
|
|
||||||
pub fn shell_escape<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(move |_, string: String| {
|
|
||||||
let val = path::escape(&string).to_string();
|
|
||||||
Ok(val)
|
|
||||||
})?;
|
|
||||||
util.set("shell_escape", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load JSON string into Lua value.
|
|
||||||
///
|
|
||||||
/// Type: function( string ) -> any
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.from_json([[{"foo": "bar"}]])
|
|
||||||
/// -- { foo = "bar" }
|
|
||||||
/// ```
|
|
||||||
pub fn from_json<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|lua, string: String| {
|
|
||||||
let val = json::from_str::<yaml::Value>(&string).map_err(LuaError::custom)?;
|
|
||||||
lua::serialize(lua, &val).map_err(Error::custom)
|
|
||||||
})?;
|
|
||||||
util.set("from_json", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dump Lua value into JSON (i.e. also YAML) string.
|
|
||||||
///
|
|
||||||
/// Type: function( value ) -> string
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.to_json({ foo = "bar" })
|
|
||||||
/// -- [[{ "foo": "bar" }]]
|
|
||||||
///
|
|
||||||
/// xplr.util.to_json({ foo = "bar" }, { pretty = true })
|
|
||||||
/// -- [[{
|
|
||||||
/// -- "foo": "bar"
|
|
||||||
/// -- }]]
|
|
||||||
/// ```
|
|
||||||
pub fn to_json<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
|
||||||
struct Options {
|
|
||||||
pretty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
let func =
|
|
||||||
lua.create_function(|lua, (value, options): (Value, Option<Table>)| {
|
|
||||||
let options: Options = if let Some(o) = options {
|
|
||||||
lua.from_value(Value::Table(o))?
|
|
||||||
} else {
|
|
||||||
Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
if options.pretty {
|
|
||||||
json::to_string_pretty(&value).map_err(Error::custom)
|
|
||||||
} else {
|
|
||||||
json::to_string(&value).map_err(Error::custom)
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
util.set("to_json", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Load YAML (i.e. also JSON) string into Lua value.
|
|
||||||
///
|
|
||||||
/// Type: function( string ) -> value
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.from_yaml([[{foo: bar}]])
|
|
||||||
/// -- { foo = "bar" }
|
|
||||||
/// ```
|
|
||||||
pub fn from_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|lua, string: String| {
|
|
||||||
let val = yaml::from_str::<yaml::Value>(&string).map_err(LuaError::custom)?;
|
|
||||||
lua::serialize(lua, &val).map_err(Error::custom)
|
|
||||||
})?;
|
|
||||||
util.set("from_yaml", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dump Lua value into YAML string.
|
|
||||||
///
|
|
||||||
/// Type: function( value ) -> string
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.to_yaml({ foo = "bar" })
|
|
||||||
/// -- "foo: bar"
|
|
||||||
/// ```
|
|
||||||
pub fn to_yaml<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
|
||||||
pub struct Options {
|
|
||||||
pretty: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
let func = lua.create_function(|_, value: Value| {
|
|
||||||
yaml::to_string(&value).map_err(Error::custom)
|
|
||||||
})?;
|
|
||||||
util.set("to_yaml", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a [Style][3] object for the given path based on the LS_COLORS
|
|
||||||
/// environment variable.
|
|
||||||
///
|
|
||||||
/// Type: function( path:string ) -> [Style][3]
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.lscolor("Desktop")
|
|
||||||
/// -- { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} }
|
|
||||||
/// ```
|
|
||||||
pub fn lscolor<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(move |lua, path: String| {
|
|
||||||
let style = LS_COLORS
|
|
||||||
.style_for_path(path)
|
|
||||||
.map(Style::from)
|
|
||||||
.unwrap_or_default();
|
|
||||||
lua::serialize(lua, &style).map_err(LuaError::custom)
|
|
||||||
})?;
|
|
||||||
util.set("lscolor", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Apply style (escape sequence) to string using a given [Style][3] object.
|
|
||||||
///
|
|
||||||
/// Type: function( string, [Style][3]|nil ) -> string
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.paint("Desktop", { fg = "Red", bg = nil, add_modifiers = {}, sub_modifiers = {} })
|
|
||||||
/// -- "\u001b[31mDesktop\u001b[0m"
|
|
||||||
/// ```
|
|
||||||
pub fn paint<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func =
|
|
||||||
lua.create_function(|lua, (string, style): (String, Option<Table>)| {
|
|
||||||
if *ui::NO_COLOR {
|
|
||||||
return Ok(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(style) = style {
|
|
||||||
let style: Style = lua.from_value(Value::Table(style))?;
|
|
||||||
let ansi_style: nu_ansi_term::Style = style.into();
|
|
||||||
Ok::<String, LuaError>(ansi_style.paint(string).to_string())
|
|
||||||
} else {
|
|
||||||
Ok(string)
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
util.set("paint", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mix multiple [Style][3] objects into one.
|
|
||||||
///
|
|
||||||
/// Type: function( { [Style][3], [Style][3], ... } ) -> [Style][3]
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.style_mix({{ fg = "Red" }, { bg = "Blue" }, { add_modifiers = {"Bold"} }})
|
|
||||||
/// -- { fg = "Red", bg = "Blue", add_modifiers = { "Bold" }, sub_modifiers = {} }
|
|
||||||
/// ```
|
|
||||||
pub fn style_mix<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|lua, styles: Vec<Table>| {
|
|
||||||
let mut style = Style::default();
|
|
||||||
for other in styles {
|
|
||||||
let other: Style = lua.from_value(Value::Table(other))?;
|
|
||||||
style = style.extend(&other);
|
|
||||||
}
|
|
||||||
|
|
||||||
lua::serialize(lua, &style).map_err(LuaError::custom)
|
|
||||||
})?;
|
|
||||||
util.set("style_mix", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Wrap the given text to fit the specified width.
|
|
||||||
/// It will try to not split words when possible.
|
|
||||||
///
|
|
||||||
/// Type: function( string, options:number|table ) -> { string, ...}
|
|
||||||
///
|
|
||||||
/// Options type: { width = number, initial_indent = string|nil, subsequent_indent = string|nil, break_words = boolean|nil }
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.textwrap("this will be cut off", 11)
|
|
||||||
/// -- { "this will', 'be cut off" }
|
|
||||||
///
|
|
||||||
/// xplr.util.textwrap(
|
|
||||||
/// "this will be cut off",
|
|
||||||
/// { width = 12, initial_indent = "", subsequent_indent = " ", break_words = false }
|
|
||||||
/// )
|
|
||||||
/// -- { "this will be", " cut off" }
|
|
||||||
/// ```
|
|
||||||
pub fn textwrap<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|lua, (text, options): (String, Value)| {
|
|
||||||
let lines = match lua.from_value::<usize>(options.clone()) {
|
|
||||||
Ok(width) => textwrap::wrap(&text, width),
|
|
||||||
Err(_) => {
|
|
||||||
let options = lua.from_value::<WrapOptions>(options)?;
|
|
||||||
textwrap::wrap(&text, options.get_options())
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(lines.iter().map(Cow::to_string).collect::<Vec<String>>())
|
|
||||||
})?;
|
|
||||||
util.set("textwrap", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the target layout in the given layout and replace it with the replacement layout,
|
|
||||||
/// returning a new layout.
|
|
||||||
///
|
|
||||||
/// Type: function( layout:[Layout][4], target:[Layout][4], replacement:[Layout][4] ) -> layout:[Layout][4]
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// local layout = {
|
|
||||||
/// Horizontal = {
|
|
||||||
/// splits = {
|
|
||||||
/// "Table", -- Target
|
|
||||||
/// "HelpMenu",
|
|
||||||
/// },
|
|
||||||
/// config = ...,
|
|
||||||
/// }
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// xplr.util.layout_replace(layout, "Table", "Selection")
|
|
||||||
/// -- {
|
|
||||||
/// -- Horizontal = {
|
|
||||||
/// -- splits = {
|
|
||||||
/// -- "Selection", -- Replacement
|
|
||||||
/// -- "HelpMenu",
|
|
||||||
/// -- },
|
|
||||||
/// -- config = ...
|
|
||||||
/// -- }
|
|
||||||
/// -- }
|
|
||||||
/// ```
|
|
||||||
pub fn layout_replace<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(
|
|
||||||
move |lua, (layout, target, replacement): (Value, Value, Value)| {
|
|
||||||
let layout: Layout = lua.from_value(layout)?;
|
|
||||||
let target: Layout = lua.from_value(target)?;
|
|
||||||
let replacement: Layout = lua.from_value(replacement)?;
|
|
||||||
|
|
||||||
let res = layout.replace(&target, &replacement);
|
|
||||||
let res = lua::serialize(lua, &res).map_err(LuaError::custom)?;
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
util.set("layout_replace", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert [Permission][8] to rwxrwxrwx representation with special bits.
|
|
||||||
///
|
|
||||||
/// Type: function( [Permission][8] ) -> string
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.permissions_rwx({ user_read = true })
|
|
||||||
/// -- "r--------"
|
|
||||||
///
|
|
||||||
/// xplr.util.permissions_rwx(app.focused_node.permission)
|
|
||||||
/// -- "rwxrwsrwT"
|
|
||||||
/// ```
|
|
||||||
pub fn permissions_rwx<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|lua, permission: Table| {
|
|
||||||
let permissions: Permissions = lua.from_value(Value::Table(permission))?;
|
|
||||||
let permissions = permissions.to_string();
|
|
||||||
Ok(permissions)
|
|
||||||
})?;
|
|
||||||
util.set("permissions_rwx", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert [Permission][8] to octal representation.
|
|
||||||
///
|
|
||||||
/// Type: function( [Permission][8] ) -> { number, number, number, number }
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
///
|
|
||||||
/// ```lua
|
|
||||||
/// xplr.util.permissions_octal({ user_read = true })
|
|
||||||
/// -- { 0, 4, 0, 0 }
|
|
||||||
///
|
|
||||||
/// xplr.util.permissions_octal(app.focused_node.permission)
|
|
||||||
/// -- { 0, 7, 5, 4 }
|
|
||||||
/// ```
|
|
||||||
pub fn permissions_octal<'a>(util: Table<'a>, lua: &Lua) -> Result<Table<'a>> {
|
|
||||||
let func = lua.create_function(|lua, permission: Table| {
|
|
||||||
let permissions: Permissions = lua.from_value(Value::Table(permission))?;
|
|
||||||
let permissions: Octal = permissions.into();
|
|
||||||
let permissions = lua::serialize(lua, &permissions).map_err(LuaError::custom)?;
|
|
||||||
Ok(permissions)
|
|
||||||
})?;
|
|
||||||
util.set("permissions_octal", func)?;
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
/// [1]: https://xplr.dev/en/lua-function-calls#explorer-config
|
|
||||||
/// [2]: https://xplr.dev/en/lua-function-calls#node
|
|
||||||
/// [3]: https://xplr.dev/en/style
|
|
||||||
/// [4]: https://xplr.dev/en/layout
|
|
||||||
/// [5]: https://xplr.dev/en/lua-function-calls#node
|
|
||||||
/// [6]: https://xplr.dev/en/node-type
|
|
||||||
/// [7]: https://xplr.dev/en/node_types
|
|
||||||
/// [8]: https://xplr.dev/en/column-renderer#permission
|
|
||||||
|
|
||||||
pub(crate) fn create_table(lua: &Lua) -> Result<Table> {
|
|
||||||
let mut util = lua.create_table()?;
|
|
||||||
|
|
||||||
util = version(util, lua)?;
|
|
||||||
util = debug(util, lua)?;
|
|
||||||
util = clone(util, lua)?;
|
|
||||||
util = exists(util, lua)?;
|
|
||||||
util = is_dir(util, lua)?;
|
|
||||||
util = is_file(util, lua)?;
|
|
||||||
util = is_symlink(util, lua)?;
|
|
||||||
util = is_absolute(util, lua)?;
|
|
||||||
util = path_split(util, lua)?;
|
|
||||||
util = node(util, lua)?;
|
|
||||||
util = node_type(util, lua)?;
|
|
||||||
util = dirname(util, lua)?;
|
|
||||||
util = basename(util, lua)?;
|
|
||||||
util = absolute(util, lua)?;
|
|
||||||
util = relative_to(util, lua)?;
|
|
||||||
util = shorten(util, lua)?;
|
|
||||||
util = explore(util, lua)?;
|
|
||||||
util = shell_execute(util, lua)?;
|
|
||||||
util = shell_quote(util, lua)?;
|
|
||||||
util = shell_escape(util, lua)?;
|
|
||||||
util = from_json(util, lua)?;
|
|
||||||
util = to_json(util, lua)?;
|
|
||||||
util = from_yaml(util, lua)?;
|
|
||||||
util = to_yaml(util, lua)?;
|
|
||||||
util = lscolor(util, lua)?;
|
|
||||||
util = paint(util, lua)?;
|
|
||||||
util = style_mix(util, lua)?;
|
|
||||||
util = textwrap(util, lua)?;
|
|
||||||
util = layout_replace(util, lua)?;
|
|
||||||
util = permissions_rwx(util, lua)?;
|
|
||||||
util = permissions_octal(util, lua)?;
|
|
||||||
|
|
||||||
Ok(util)
|
|
||||||
}
|
|
@ -1,499 +0,0 @@
|
|||||||
use crate::dirs;
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
pub use snailquote::escape;
|
|
||||||
use std::path::{Component, Path, PathBuf};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref HOME: Option<PathBuf> = dirs::home_dir();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stolen from https://github.com/Manishearth/pathdiff/blob/master/src/lib.rs
|
|
||||||
pub fn diff<P, B>(path: P, base: B) -> Result<PathBuf>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
B: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let path = path.as_ref();
|
|
||||||
let base = base.as_ref();
|
|
||||||
|
|
||||||
if path.is_absolute() != base.is_absolute() {
|
|
||||||
if path.is_absolute() {
|
|
||||||
Ok(PathBuf::from(path))
|
|
||||||
} else {
|
|
||||||
let path = path.to_string_lossy();
|
|
||||||
bail!("{path}: is not absolute")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let mut ita = path.components();
|
|
||||||
let mut itb = base.components();
|
|
||||||
let mut comps: Vec<Component> = vec![];
|
|
||||||
loop {
|
|
||||||
match (ita.next(), itb.next()) {
|
|
||||||
(None, None) => break,
|
|
||||||
(Some(a), None) => {
|
|
||||||
comps.push(a);
|
|
||||||
comps.extend(ita.by_ref());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
(None, _) => comps.push(Component::ParentDir),
|
|
||||||
(Some(a), Some(b)) if comps.is_empty() && a == b => (),
|
|
||||||
(Some(a), Some(Component::CurDir)) => comps.push(a),
|
|
||||||
(Some(_), Some(Component::ParentDir)) => {
|
|
||||||
let path = path.to_string_lossy();
|
|
||||||
let base = base.to_string_lossy();
|
|
||||||
bail!("{base} is not a parent of {path}")
|
|
||||||
}
|
|
||||||
(Some(a), Some(_)) => {
|
|
||||||
comps.push(Component::ParentDir);
|
|
||||||
for _ in itb {
|
|
||||||
comps.push(Component::ParentDir);
|
|
||||||
}
|
|
||||||
comps.push(a);
|
|
||||||
comps.extend(ita.by_ref());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(comps.iter().map(|c| c.as_os_str()).collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct RelativityConfig<B: AsRef<Path>> {
|
|
||||||
base: Option<B>,
|
|
||||||
with_prefix_dots: Option<bool>,
|
|
||||||
without_suffix_dots: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<B: AsRef<Path>> RelativityConfig<B> {
|
|
||||||
pub fn with_base(mut self, base: B) -> Self {
|
|
||||||
self.base = Some(base);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_prefix_dots(mut self) -> Self {
|
|
||||||
self.with_prefix_dots = Some(true);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn without_suffix_dots(mut self) -> Self {
|
|
||||||
self.without_suffix_dots = Some(true);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn relative_to<P, B>(
|
|
||||||
path: P,
|
|
||||||
config: Option<&RelativityConfig<B>>,
|
|
||||||
) -> Result<PathBuf>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
B: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let path = path.as_ref();
|
|
||||||
let base = match config.and_then(|c| c.base.as_ref()) {
|
|
||||||
Some(base) => PathBuf::from(base.as_ref()),
|
|
||||||
None => std::env::current_dir()?,
|
|
||||||
};
|
|
||||||
|
|
||||||
let diff = diff(path, base)?;
|
|
||||||
|
|
||||||
let relative = if diff.to_str() == Some("") {
|
|
||||||
".".into()
|
|
||||||
} else {
|
|
||||||
diff
|
|
||||||
};
|
|
||||||
|
|
||||||
let relative = if config.and_then(|c| c.with_prefix_dots).unwrap_or(false)
|
|
||||||
&& !relative.starts_with(".")
|
|
||||||
&& !relative.starts_with("..")
|
|
||||||
{
|
|
||||||
PathBuf::from(".").join(relative)
|
|
||||||
} else {
|
|
||||||
relative
|
|
||||||
};
|
|
||||||
|
|
||||||
let relative = if !config.and_then(|c| c.without_suffix_dots).unwrap_or(false) {
|
|
||||||
relative
|
|
||||||
} else if relative.ends_with(".") {
|
|
||||||
match (path.parent(), path.file_name()) {
|
|
||||||
(Some(_), Some(name)) => PathBuf::from("..").join(name),
|
|
||||||
(_, _) => relative,
|
|
||||||
}
|
|
||||||
} else if relative.ends_with("..") {
|
|
||||||
match (path.parent(), path.file_name()) {
|
|
||||||
(Some(parent), Some(name)) => {
|
|
||||||
if parent.parent().is_some() {
|
|
||||||
relative.join("..").join(name)
|
|
||||||
} else {
|
|
||||||
// always prefer absolute path if it's a child of the root directory
|
|
||||||
// to guarantee that the basename is included
|
|
||||||
path.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(_, _) => relative,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
relative
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(relative)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shorten<P, B>(path: P, config: Option<&RelativityConfig<B>>) -> Result<String>
|
|
||||||
where
|
|
||||||
P: AsRef<Path>,
|
|
||||||
B: AsRef<Path>,
|
|
||||||
{
|
|
||||||
let path = path.as_ref();
|
|
||||||
let pathstring = path.to_string_lossy().to_string();
|
|
||||||
let relative = relative_to(path, config)?;
|
|
||||||
|
|
||||||
let relative = relative.to_string_lossy().to_string();
|
|
||||||
|
|
||||||
let fromhome = HOME
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|h| {
|
|
||||||
path.strip_prefix(h).ok().map(|p| {
|
|
||||||
if p.to_str() == Some("") {
|
|
||||||
"~".into()
|
|
||||||
} else {
|
|
||||||
PathBuf::from("~").join(p).to_string_lossy().to_string()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or(pathstring);
|
|
||||||
|
|
||||||
if relative.len() < fromhome.len() {
|
|
||||||
Ok(relative)
|
|
||||||
} else {
|
|
||||||
Ok(fromhome)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
type Config<'a> = Option<&'a RelativityConfig<String>>;
|
|
||||||
|
|
||||||
const NONE: Config = Config::None;
|
|
||||||
|
|
||||||
fn default<'a>() -> RelativityConfig<&'a str> {
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_relative_to_pwd() {
|
|
||||||
let path = std::env::current_dir().unwrap();
|
|
||||||
|
|
||||||
let relative = relative_to(&path, NONE).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("."));
|
|
||||||
|
|
||||||
let relative = relative_to(&path, Some(&default().with_prefix_dots())).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("."));
|
|
||||||
|
|
||||||
let relative =
|
|
||||||
relative_to(&path, Some(&default().without_suffix_dots())).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
relative,
|
|
||||||
PathBuf::from("..").join(path.file_name().unwrap())
|
|
||||||
);
|
|
||||||
|
|
||||||
let relative = relative_to(
|
|
||||||
&path,
|
|
||||||
Some(&default().with_prefix_dots().without_suffix_dots()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
relative,
|
|
||||||
PathBuf::from("..").join(path.file_name().unwrap())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_relative_to_parent() {
|
|
||||||
let path = std::env::current_dir().unwrap().join("docs");
|
|
||||||
let parent = path.parent().unwrap();
|
|
||||||
|
|
||||||
let base = default().with_base(path.to_str().unwrap());
|
|
||||||
|
|
||||||
let relative = relative_to(parent, Some(&base)).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from(".."));
|
|
||||||
|
|
||||||
let relative =
|
|
||||||
relative_to(parent, Some(&base.clone().with_prefix_dots())).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from(".."));
|
|
||||||
|
|
||||||
let relative =
|
|
||||||
relative_to(parent, Some(&base.clone().without_suffix_dots())).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
relative,
|
|
||||||
PathBuf::from("../..").join(parent.file_name().unwrap())
|
|
||||||
);
|
|
||||||
|
|
||||||
let relative = relative_to(
|
|
||||||
parent,
|
|
||||||
Some(&base.clone().with_prefix_dots().without_suffix_dots()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
relative,
|
|
||||||
PathBuf::from("../..").join(parent.file_name().unwrap())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_relative_to_file() {
|
|
||||||
let path = std::env::current_dir().unwrap().join("foo").join("bar");
|
|
||||||
|
|
||||||
let relative = relative_to(&path, NONE).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("foo/bar"));
|
|
||||||
|
|
||||||
let relative = relative_to(&path, Some(&default().with_prefix_dots())).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("./foo/bar"));
|
|
||||||
|
|
||||||
let relative = relative_to(
|
|
||||||
&path,
|
|
||||||
Some(&default().with_prefix_dots().without_suffix_dots()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("./foo/bar"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_relative_to_root() {
|
|
||||||
let relative = relative_to("/foo", Some(&default().with_base("/"))).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("foo"));
|
|
||||||
|
|
||||||
let relative = relative_to(
|
|
||||||
"/foo",
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base("/")
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("./foo"));
|
|
||||||
|
|
||||||
let relative = relative_to("/", Some(&default().with_base("/"))).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("."));
|
|
||||||
|
|
||||||
let relative = relative_to(
|
|
||||||
"/",
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base("/")
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("."));
|
|
||||||
|
|
||||||
let relative = relative_to("/", Some(&default().with_base("/foo"))).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from(".."));
|
|
||||||
|
|
||||||
let relative = relative_to(
|
|
||||||
"/",
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base("/foo")
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from(".."));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_relative_to_base() {
|
|
||||||
let path = "/some/directory";
|
|
||||||
let base = "/another/foo/bar";
|
|
||||||
|
|
||||||
let relative = relative_to(path, Some(&default().with_base(base))).unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("../../../some/directory"));
|
|
||||||
|
|
||||||
let relative = relative_to(
|
|
||||||
path,
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base(base)
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(relative, PathBuf::from("../../../some/directory"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shorten_home() {
|
|
||||||
let path = HOME.as_ref().unwrap();
|
|
||||||
|
|
||||||
let res = shorten(path, NONE).unwrap();
|
|
||||||
assert_eq!(res, "~");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
path,
|
|
||||||
Some(&default().with_prefix_dots().without_suffix_dots()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "~");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
path,
|
|
||||||
Some(&default().with_prefix_dots().without_suffix_dots()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "~");
|
|
||||||
|
|
||||||
let res = shorten(path.join("foo"), NONE).unwrap();
|
|
||||||
assert_eq!(res, "~/foo");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
path.join("foo"),
|
|
||||||
Some(&default().with_prefix_dots().without_suffix_dots()),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "~/foo");
|
|
||||||
|
|
||||||
let res = shorten(format!("{}foo", path.to_string_lossy()), NONE).unwrap();
|
|
||||||
assert_ne!(res, "~/foo");
|
|
||||||
assert_eq!(res, format!("{}foo", path.to_string_lossy()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shorten_base() {
|
|
||||||
let path = "/present/working/directory";
|
|
||||||
let base = "/present/foo/bar";
|
|
||||||
|
|
||||||
let res = shorten(path, Some(&default().with_base(base))).unwrap();
|
|
||||||
assert_eq!(res, "../../working/directory");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
path,
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base(base)
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "../../working/directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shorten_pwd() {
|
|
||||||
let path = "/present/working/directory";
|
|
||||||
|
|
||||||
let res = shorten(path, Some(&default().with_base(path))).unwrap();
|
|
||||||
assert_eq!(res, ".");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
path,
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base(path)
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "../directory");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shorten_parent() {
|
|
||||||
let path = "/present/working";
|
|
||||||
let base = "/present/working/directory";
|
|
||||||
|
|
||||||
let res = shorten(path, Some(&default().with_base(base))).unwrap();
|
|
||||||
assert_eq!(res, "..");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
path,
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base(base)
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "../../working");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_shorten_root() {
|
|
||||||
let res = shorten("/", Some(&default().with_base("/"))).unwrap();
|
|
||||||
assert_eq!(res, "/");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
"/",
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base("/")
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "/");
|
|
||||||
|
|
||||||
let res = shorten("/foo", Some(&default().with_base("/"))).unwrap();
|
|
||||||
assert_eq!(res, "foo");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
"/foo",
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base("/")
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "/foo");
|
|
||||||
|
|
||||||
let res = shorten(
|
|
||||||
"/",
|
|
||||||
Some(
|
|
||||||
&default()
|
|
||||||
.with_base("/foo")
|
|
||||||
.with_prefix_dots()
|
|
||||||
.without_suffix_dots(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res, "/");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_path_escape() {
|
|
||||||
let text = "foo".to_string();
|
|
||||||
assert_eq!(escape(&text), "foo");
|
|
||||||
|
|
||||||
let text = "foo bar".to_string();
|
|
||||||
assert_eq!(escape(&text), "'foo bar'");
|
|
||||||
|
|
||||||
let text = "foo\nbar".to_string();
|
|
||||||
assert_eq!(escape(&text), "\"foo\\nbar\"");
|
|
||||||
|
|
||||||
let text = "foo$bar".to_string();
|
|
||||||
assert_eq!(escape(&text), "'foo$bar'");
|
|
||||||
|
|
||||||
let text = "foo'$\n'bar".to_string();
|
|
||||||
assert_eq!(escape(&text), "\"foo'\\$\\n'bar\"");
|
|
||||||
|
|
||||||
let text = "a'b\"c".to_string();
|
|
||||||
assert_eq!(escape(&text), "\"a'b\\\"c\"");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
use lazy_static::lazy_static;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use skim::prelude::{ExactOrFuzzyEngineFactory, RegexEngineFactory};
|
|
||||||
use skim::{MatchEngine, MatchEngineFactory, SkimItem};
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref FUZZY_FACTORY: ExactOrFuzzyEngineFactory =
|
|
||||||
ExactOrFuzzyEngineFactory::builder().build();
|
|
||||||
static ref REGEX_FACTORY: RegexEngineFactory = RegexEngineFactory::builder().build();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PathItem {
|
|
||||||
path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for PathItem {
|
|
||||||
fn from(value: String) -> Self {
|
|
||||||
Self { path: value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SkimItem for PathItem {
|
|
||||||
fn text(&self) -> std::borrow::Cow<str> {
|
|
||||||
std::borrow::Cow::from(&self.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub enum SearchAlgorithm {
|
|
||||||
#[default]
|
|
||||||
Fuzzy,
|
|
||||||
Regex,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SearchAlgorithm {
|
|
||||||
pub fn toggle(self) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Fuzzy => Self::Regex,
|
|
||||||
Self::Regex => Self::Fuzzy,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn engine(&self, pattern: &str) -> Box<dyn MatchEngine> {
|
|
||||||
match self {
|
|
||||||
Self::Fuzzy => FUZZY_FACTORY.create_engine(pattern),
|
|
||||||
Self::Regex => REGEX_FACTORY.create_engine(pattern),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
use serde_yaml::with::singleton_map_recursive;
|
|
||||||
pub use serde_yaml::Result;
|
|
||||||
pub use serde_yaml::Value;
|
|
||||||
|
|
||||||
pub fn to_string<T>(value: &T) -> Result<String>
|
|
||||||
where
|
|
||||||
T: ?Sized + serde::Serialize,
|
|
||||||
{
|
|
||||||
let mut vec = Vec::with_capacity(128);
|
|
||||||
let mut serializer = serde_yaml::Serializer::new(&mut vec);
|
|
||||||
|
|
||||||
singleton_map_recursive::serialize(&value, &mut serializer)?;
|
|
||||||
String::from_utf8(vec).map_err(serde::ser::Error::custom)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_str<'de, T>(s: &'de str) -> Result<T>
|
|
||||||
where
|
|
||||||
T: serde::Deserialize<'de>,
|
|
||||||
{
|
|
||||||
let deserializer = serde_yaml::Deserializer::from_str(s);
|
|
||||||
singleton_map_recursive::deserialize(deserializer)
|
|
||||||
}
|
|
Loading…
Reference in New Issue