import: auto-detect databases; add Atuin support
This commit is contained in:
parent
0cc031e98e
commit
2dde02cec8
|
|
@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
|
||||
- POSIX: support for non-Cygwin Windows environments (e.g. Busybox).
|
||||
- `import` now supports fetching entries from `atuin`.
|
||||
|
||||
### Changed
|
||||
|
||||
- `import` now auto-detects database files.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -260,6 +260,15 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "deranged"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
|
|
@ -436,6 +445,12 @@ dependencies = [
|
|||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
|
|
@ -484,6 +499,12 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
|
|
@ -712,18 +733,28 @@ checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.219"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -804,6 +835,36 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
|
@ -1004,5 +1065,6 @@ dependencies = [
|
|||
"rstest_reuse",
|
||||
"serde",
|
||||
"tempfile",
|
||||
"time",
|
||||
"which",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ fastrand = "2.0.0"
|
|||
glob = "0.3.0"
|
||||
ouroboros = "0.18.3"
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
time = { version = "0.3.47", default-features = false, features = ["parsing", "macros", "std"] }
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = { version = "0.30.1", default-features = false, features = [
|
||||
|
|
|
|||
66
README.md
66
README.md
|
|
@ -350,61 +350,21 @@ zoxide can be installed in 4 easy steps:
|
|||
4. **Import your data** <sup>(optional)</sup>
|
||||
|
||||
If you currently use any of these plugins, you may want to import your data
|
||||
into zoxide:
|
||||
into zoxide. The data file is auto-detected using each plugin's standard
|
||||
conventions.
|
||||
|
||||
<details>
|
||||
<summary>autojump</summary>
|
||||
```sh
|
||||
zoxide import <plugin>
|
||||
```
|
||||
|
||||
> Run this command in your terminal:
|
||||
>
|
||||
> ```sh
|
||||
> zoxide import --from=autojump "/path/to/autojump/db"
|
||||
> ```
|
||||
>
|
||||
> The path usually varies according to your system:
|
||||
>
|
||||
> | OS | Path | Example |
|
||||
> | ------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------ |
|
||||
> | Linux | `$XDG_DATA_HOME/autojump/autojump.txt` or `$HOME/.local/share/autojump/autojump.txt` | `/home/alice/.local/share/autojump/autojump.txt` |
|
||||
> | macOS | `$HOME/Library/autojump/autojump.txt` | `/Users/Alice/Library/autojump/autojump.txt` |
|
||||
> | Windows | `%APPDATA%\autojump\autojump.txt` | `C:\Users\Alice\AppData\Roaming\autojump\autojump.txt` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>fasd, z, z.lua, zsh-z</summary>
|
||||
|
||||
> Run this command in your terminal:
|
||||
>
|
||||
> ```sh
|
||||
> zoxide import --from=z "path/to/z/db"
|
||||
> ```
|
||||
>
|
||||
> The path usually varies according to your system:
|
||||
>
|
||||
> | Plugin | Path |
|
||||
> | ---------------- | ----------------------------------------------------------------------------------- |
|
||||
> | fasd | `$_FASD_DATA` or `$HOME/.fasd` |
|
||||
> | z (bash/zsh) | `$_Z_DATA` or `$HOME/.z` |
|
||||
> | z (fish) | `$Z_DATA` or `$XDG_DATA_HOME/z/data` or `$HOME/.local/share/z/data` |
|
||||
> | z.lua (bash/zsh) | `$_ZL_DATA` or `$HOME/.zlua` |
|
||||
> | z.lua (fish) | `$XDG_DATA_HOME/zlua/zlua.txt` or `$HOME/.local/share/zlua/zlua.txt` or `$_ZL_DATA` |
|
||||
> | zsh-z | `$ZSHZ_DATA` or `$_Z_DATA` or `$HOME/.z` |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>ZLocation</summary>
|
||||
|
||||
> Run this command in PowerShell:
|
||||
>
|
||||
> ```powershell
|
||||
> $db = New-TemporaryFile
|
||||
> (Get-ZLocation).GetEnumerator() | ForEach-Object { Write-Output ($_.Name+'|'+$_.Value+'|0') } | Out-File $db
|
||||
> zoxide import --from=z $db
|
||||
> ```
|
||||
|
||||
</details>
|
||||
| Plugin | Command |
|
||||
| ---------- | ------------------------- |
|
||||
| atuin | `zoxide import atuin` |
|
||||
| autojump | `zoxide import autojump` |
|
||||
| fasd | `zoxide import fasd` |
|
||||
| z | `zoxide import z` |
|
||||
| z.lua | `zoxide import z.lua` |
|
||||
| zsh-z | `zoxide import zsh-z` |
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
|
|||
|
|
@ -96,14 +96,78 @@ esac
|
|||
;;
|
||||
(import)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--from=[Application to import from]:FROM:(autojump z)' \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
':path:_files' \
|
||||
":: :_zoxide__import_commands" \
|
||||
"*::: :->import" \
|
||||
&& ret=0
|
||||
|
||||
case $state in
|
||||
(import)
|
||||
words=($line[1] "${words[@]}")
|
||||
(( CURRENT += 1 ))
|
||||
curcontext="${curcontext%:*:*}:zoxide-import-command-$line[1]:"
|
||||
case $line[1] in
|
||||
(atuin)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
&& ret=0
|
||||
;;
|
||||
(autojump)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
&& ret=0
|
||||
;;
|
||||
(fasd)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
&& ret=0
|
||||
;;
|
||||
(z)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
&& ret=0
|
||||
;;
|
||||
(z.lua)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
&& ret=0
|
||||
;;
|
||||
(zsh-z)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
'--merge[Merge into existing database]' \
|
||||
'-h[Print help]' \
|
||||
'--help[Print help]' \
|
||||
'-V[Print version]' \
|
||||
'--version[Print version]' \
|
||||
&& ret=0
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
(init)
|
||||
_arguments "${_arguments_options[@]}" : \
|
||||
|
|
@ -199,9 +263,46 @@ _zoxide__edit__reload_commands() {
|
|||
}
|
||||
(( $+functions[_zoxide__import_commands] )) ||
|
||||
_zoxide__import_commands() {
|
||||
local commands; commands=()
|
||||
local commands; commands=(
|
||||
'atuin:Import from atuin' \
|
||||
'autojump:Import from autojump' \
|
||||
'fasd:Import from fasd' \
|
||||
'z:Import from z' \
|
||||
'z.lua:Import from z.lua' \
|
||||
'zsh-z:Import from zsh-z' \
|
||||
)
|
||||
_describe -t commands 'zoxide import commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__import__atuin_commands] )) ||
|
||||
_zoxide__import__atuin_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import atuin commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__import__autojump_commands] )) ||
|
||||
_zoxide__import__autojump_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import autojump commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__import__fasd_commands] )) ||
|
||||
_zoxide__import__fasd_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import fasd commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__import__z_commands] )) ||
|
||||
_zoxide__import__z_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import z commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__import__z.lua_commands] )) ||
|
||||
_zoxide__import__z.lua_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import z.lua commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__import__zsh-z_commands] )) ||
|
||||
_zoxide__import__zsh-z_commands() {
|
||||
local commands; commands=()
|
||||
_describe -t commands 'zoxide import zsh-z commands' commands "$@"
|
||||
}
|
||||
(( $+functions[_zoxide__init_commands] )) ||
|
||||
_zoxide__init_commands() {
|
||||
local commands; commands=()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,60 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
|
|||
break
|
||||
}
|
||||
'zoxide;import' {
|
||||
[CompletionResult]::new('--from', '--from', [CompletionResultType]::ParameterName, 'Application to import from')
|
||||
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('atuin', 'atuin', [CompletionResultType]::ParameterValue, 'Import from atuin')
|
||||
[CompletionResult]::new('autojump', 'autojump', [CompletionResultType]::ParameterValue, 'Import from autojump')
|
||||
[CompletionResult]::new('fasd', 'fasd', [CompletionResultType]::ParameterValue, 'Import from fasd')
|
||||
[CompletionResult]::new('z', 'z', [CompletionResultType]::ParameterValue, 'Import from z')
|
||||
[CompletionResult]::new('z.lua', 'z.lua', [CompletionResultType]::ParameterValue, 'Import from z.lua')
|
||||
[CompletionResult]::new('zsh-z', 'zsh-z', [CompletionResultType]::ParameterValue, 'Import from zsh-z')
|
||||
break
|
||||
}
|
||||
'zoxide;import;atuin' {
|
||||
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;import;autojump' {
|
||||
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;import;fasd' {
|
||||
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;import;z' {
|
||||
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;import;z.lua' {
|
||||
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('-V', '-V ', [CompletionResultType]::ParameterName, 'Print version')
|
||||
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
|
||||
break
|
||||
}
|
||||
'zoxide;import;zsh-z' {
|
||||
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')
|
||||
[CompletionResult]::new('-h', '-h', [CompletionResultType]::ParameterName, 'Print help')
|
||||
[CompletionResult]::new('--help', '--help', [CompletionResultType]::ParameterName, 'Print help')
|
||||
|
|
|
|||
|
|
@ -46,6 +46,24 @@ _zoxide() {
|
|||
zoxide__edit,reload)
|
||||
cmd="zoxide__edit__reload"
|
||||
;;
|
||||
zoxide__import,atuin)
|
||||
cmd="zoxide__import__atuin"
|
||||
;;
|
||||
zoxide__import,autojump)
|
||||
cmd="zoxide__import__autojump"
|
||||
;;
|
||||
zoxide__import,fasd)
|
||||
cmd="zoxide__import__fasd"
|
||||
;;
|
||||
zoxide__import,z)
|
||||
cmd="zoxide__import__z"
|
||||
;;
|
||||
zoxide__import,z.lua)
|
||||
cmd="zoxide__import__z.lua"
|
||||
;;
|
||||
zoxide__import,zsh-z)
|
||||
cmd="zoxide__import__zsh__z"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
|
|
@ -159,16 +177,96 @@ _zoxide() {
|
|||
return 0
|
||||
;;
|
||||
zoxide__import)
|
||||
opts="-h -V --from --merge --help --version <PATH>"
|
||||
opts="-h -V --merge --help --version atuin autojump fasd z z.lua zsh-z"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
--from)
|
||||
COMPREPLY=($(compgen -W "autojump z" -- "${cur}"))
|
||||
return 0
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__import__atuin)
|
||||
opts="-h -V --merge --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__import__autojump)
|
||||
opts="-h -V --merge --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__import__fasd)
|
||||
opts="-h -V --merge --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__import__z)
|
||||
opts="-h -V --merge --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__import__z.lua)
|
||||
opts="-h -V --merge --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
;;
|
||||
zoxide__import__zsh__z)
|
||||
opts="-h -V --merge --help --version"
|
||||
if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then
|
||||
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
||||
return 0
|
||||
fi
|
||||
case "${prev}" in
|
||||
*)
|
||||
COMPREPLY=()
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -72,7 +72,54 @@ set edit:completion:arg-completer[zoxide] = {|@words|
|
|||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;import'= {
|
||||
cand --from 'Application to import from'
|
||||
cand --merge 'Merge into existing database'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
cand atuin 'Import from atuin'
|
||||
cand autojump 'Import from autojump'
|
||||
cand fasd 'Import from fasd'
|
||||
cand z 'Import from z'
|
||||
cand z.lua 'Import from z.lua'
|
||||
cand zsh-z 'Import from zsh-z'
|
||||
}
|
||||
&'zoxide;import;atuin'= {
|
||||
cand --merge 'Merge into existing database'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;import;autojump'= {
|
||||
cand --merge 'Merge into existing database'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;import;fasd'= {
|
||||
cand --merge 'Merge into existing database'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;import;z'= {
|
||||
cand --merge 'Merge into existing database'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;import;z.lua'= {
|
||||
cand --merge 'Merge into existing database'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
cand -V 'Print version'
|
||||
cand --version 'Print version'
|
||||
}
|
||||
&'zoxide;import;zsh-z'= {
|
||||
cand --merge 'Merge into existing database'
|
||||
cand -h 'Print help'
|
||||
cand --help 'Print help'
|
||||
|
|
|
|||
|
|
@ -49,11 +49,33 @@ complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subc
|
|||
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from increment" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from reload" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand edit; and __fish_seen_subcommand_from reload" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -l from -d 'Application to import from' -r -f -a "autojump\t''
|
||||
z\t''"
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "atuin" -d 'Import from atuin'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "autojump" -d 'Import from autojump'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "fasd" -d 'Import from fasd'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "z" -d 'Import from z'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "z.lua" -d 'Import from z.lua'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and not __fish_seen_subcommand_from atuin autojump fasd z z.lua zsh-z" -f -a "zsh-z" -d 'Import from zsh-z'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from atuin" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from atuin" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from atuin" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from autojump" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from autojump" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from autojump" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from fasd" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from fasd" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from fasd" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z.lua" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z.lua" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from z.lua" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from zsh-z" -l merge -d 'Merge into existing database'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from zsh-z" -s h -l help -d 'Print help'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand import; and __fish_seen_subcommand_from zsh-z" -s V -l version -d 'Print version'
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l cmd -d 'Changes the prefix of the `z` and `zi` commands' -r
|
||||
complete -c zoxide -n "__fish_zoxide_using_subcommand init" -l hook -d 'Changes how often zoxide increments a directory\'s score' -r -f -a "none\t''
|
||||
prompt\t''
|
||||
|
|
|
|||
|
|
@ -43,14 +43,50 @@ module completions {
|
|||
--version(-V) # Print version
|
||||
]
|
||||
|
||||
def "nu-complete zoxide import from" [] {
|
||||
[ "autojump" "z" ]
|
||||
}
|
||||
|
||||
# Import entries from another application
|
||||
export extern "zoxide import" [
|
||||
path: path
|
||||
--from: string@"nu-complete zoxide import from" # Application to import from
|
||||
--merge # Merge into existing database
|
||||
--help(-h) # Print help
|
||||
--version(-V) # Print version
|
||||
]
|
||||
|
||||
# Import from atuin
|
||||
export extern "zoxide import atuin" [
|
||||
--merge # Merge into existing database
|
||||
--help(-h) # Print help
|
||||
--version(-V) # Print version
|
||||
]
|
||||
|
||||
# Import from autojump
|
||||
export extern "zoxide import autojump" [
|
||||
--merge # Merge into existing database
|
||||
--help(-h) # Print help
|
||||
--version(-V) # Print version
|
||||
]
|
||||
|
||||
# Import from fasd
|
||||
export extern "zoxide import fasd" [
|
||||
--merge # Merge into existing database
|
||||
--help(-h) # Print help
|
||||
--version(-V) # Print version
|
||||
]
|
||||
|
||||
# Import from z
|
||||
export extern "zoxide import z" [
|
||||
--merge # Merge into existing database
|
||||
--help(-h) # Print help
|
||||
--version(-V) # Print version
|
||||
]
|
||||
|
||||
# Import from z.lua
|
||||
export extern "zoxide import z.lua" [
|
||||
--merge # Merge into existing database
|
||||
--help(-h) # Print help
|
||||
--version(-V) # Print version
|
||||
]
|
||||
|
||||
# Import from zsh-z
|
||||
export extern "zoxide import zsh-z" [
|
||||
--merge # Merge into existing database
|
||||
--help(-h) # Print help
|
||||
--version(-V) # Print version
|
||||
|
|
|
|||
|
|
@ -114,19 +114,117 @@ const completion: Fig.Spec = {
|
|||
{
|
||||
name: "import",
|
||||
description: "Import entries from another application",
|
||||
options: [
|
||||
subcommands: [
|
||||
{
|
||||
name: "--from",
|
||||
description: "Application to import from",
|
||||
isRepeatable: true,
|
||||
args: {
|
||||
name: "from",
|
||||
suggestions: [
|
||||
"autojump",
|
||||
"z",
|
||||
],
|
||||
},
|
||||
name: "atuin",
|
||||
description: "Import from atuin",
|
||||
options: [
|
||||
{
|
||||
name: "--merge",
|
||||
description: "Merge into existing database",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "autojump",
|
||||
description: "Import from autojump",
|
||||
options: [
|
||||
{
|
||||
name: "--merge",
|
||||
description: "Merge into existing database",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "fasd",
|
||||
description: "Import from fasd",
|
||||
options: [
|
||||
{
|
||||
name: "--merge",
|
||||
description: "Merge into existing database",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "z",
|
||||
description: "Import from z",
|
||||
options: [
|
||||
{
|
||||
name: "--merge",
|
||||
description: "Merge into existing database",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "z.lua",
|
||||
description: "Import from z.lua",
|
||||
options: [
|
||||
{
|
||||
name: "--merge",
|
||||
description: "Merge into existing database",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "zsh-z",
|
||||
description: "Import from zsh-z",
|
||||
options: [
|
||||
{
|
||||
name: "--merge",
|
||||
description: "Merge into existing database",
|
||||
},
|
||||
{
|
||||
name: ["-h", "--help"],
|
||||
description: "Print help",
|
||||
},
|
||||
{
|
||||
name: ["-V", "--version"],
|
||||
description: "Print version",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
name: "--merge",
|
||||
description: "Merge into existing database",
|
||||
|
|
@ -140,10 +238,6 @@ const completion: Fig.Spec = {
|
|||
description: "Print version",
|
||||
},
|
||||
],
|
||||
args: {
|
||||
name: "path",
|
||||
template: "filepaths",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "init",
|
||||
|
|
|
|||
|
|
@ -95,23 +95,30 @@ pub enum EditCommand {
|
|||
help_template = HelpTemplate,
|
||||
)]
|
||||
pub struct Import {
|
||||
#[clap(value_hint = ValueHint::FilePath)]
|
||||
pub path: PathBuf,
|
||||
|
||||
/// Application to import from
|
||||
#[clap(value_enum, long)]
|
||||
#[clap(subcommand)]
|
||||
pub from: ImportFrom,
|
||||
|
||||
/// Merge into existing database
|
||||
#[clap(long)]
|
||||
#[clap(long, global = true)]
|
||||
pub merge: bool,
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Clone, Debug)]
|
||||
#[derive(Subcommand, Clone, Debug)]
|
||||
pub enum ImportFrom {
|
||||
/// Import from atuin
|
||||
Atuin,
|
||||
/// Import from autojump
|
||||
Autojump,
|
||||
#[clap(alias = "fasd")]
|
||||
/// Import from fasd
|
||||
Fasd,
|
||||
/// Import from z
|
||||
Z,
|
||||
/// Import from z.lua
|
||||
#[clap(name = "z.lua")]
|
||||
ZLua,
|
||||
/// Import from zsh-z
|
||||
#[clap(name = "zsh-z")]
|
||||
ZshZ,
|
||||
}
|
||||
|
||||
/// Generate shell configuration
|
||||
|
|
|
|||
|
|
@ -1,166 +1,25 @@
|
|||
use std::fs;
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use anyhow::{Result, bail};
|
||||
|
||||
use crate::cmd::{Import, ImportFrom, Run};
|
||||
use crate::db::Database;
|
||||
use crate::import;
|
||||
|
||||
impl Run for Import {
|
||||
fn run(&self) -> Result<()> {
|
||||
let buffer = fs::read_to_string(&self.path).with_context(|| {
|
||||
format!("could not open database for importing: {}", &self.path.display())
|
||||
})?;
|
||||
|
||||
let mut db = Database::open()?;
|
||||
if !self.merge && !db.dirs().is_empty() {
|
||||
bail!("current database is not empty, specify --merge to continue anyway");
|
||||
}
|
||||
|
||||
match self.from {
|
||||
ImportFrom::Autojump => import_autojump(&mut db, &buffer),
|
||||
ImportFrom::Z => import_z(&mut db, &buffer),
|
||||
ImportFrom::Atuin => import::run(&import::Atuin {}, &mut db)?,
|
||||
ImportFrom::Autojump => import::run(&import::Autojump {}, &mut db)?,
|
||||
ImportFrom::Fasd => import::run(&import::Fasd {}, &mut db)?,
|
||||
ImportFrom::Z => import::run(&import::Z {}, &mut db)?,
|
||||
ImportFrom::ZLua => import::run(&import::ZLua {}, &mut db)?,
|
||||
ImportFrom::ZshZ => import::run(&import::ZshZ {}, &mut db)?,
|
||||
}
|
||||
.context("import error")?;
|
||||
|
||||
db.save()
|
||||
}
|
||||
}
|
||||
|
||||
fn import_autojump(db: &mut Database, buffer: &str) -> Result<()> {
|
||||
for line in buffer.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (rank, path) =
|
||||
line.split_once('\t').with_context(|| format!("invalid entry: {line}"))?;
|
||||
|
||||
let mut rank = rank.parse::<f64>().with_context(|| format!("invalid rank: {rank}"))?;
|
||||
// Normalize the rank using a sigmoid function. Don't import actual ranks from
|
||||
// autojump, since its scoring algorithm is very different and might
|
||||
// take a while to normalize.
|
||||
rank = sigmoid(rank);
|
||||
|
||||
db.add_unchecked(path, rank, 0);
|
||||
}
|
||||
|
||||
if db.dirty() {
|
||||
db.dedup();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_z(db: &mut Database, buffer: &str) -> Result<()> {
|
||||
for line in buffer.lines() {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let mut split = line.rsplitn(3, '|');
|
||||
|
||||
let last_accessed = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||
let last_accessed =
|
||||
last_accessed.parse().with_context(|| format!("invalid epoch: {last_accessed}"))?;
|
||||
|
||||
let rank = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||
let rank = rank.parse().with_context(|| format!("invalid rank: {rank}"))?;
|
||||
|
||||
let path = split.next().with_context(|| format!("invalid entry: {line}"))?;
|
||||
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
if db.dirty() {
|
||||
db.dedup();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sigmoid(x: f64) -> f64 {
|
||||
1.0 / (1.0 + (-x).exp())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::db::Dir;
|
||||
|
||||
#[test]
|
||||
fn from_autojump() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
for (path, rank, last_accessed) in [
|
||||
("/quux/quuz", 1.0, 100),
|
||||
("/corge/grault/garply", 6.0, 600),
|
||||
("/waldo/fred/plugh", 3.0, 300),
|
||||
("/xyzzy/thud", 8.0, 800),
|
||||
("/foo/bar", 9.0, 900),
|
||||
] {
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
let buffer = "\
|
||||
7.0 /baz
|
||||
2.0 /foo/bar
|
||||
5.0 /quux/quuz";
|
||||
import_autojump(&mut db, buffer).unwrap();
|
||||
|
||||
db.sort_by_path();
|
||||
println!("got: {:?}", &db.dirs());
|
||||
|
||||
let exp = [
|
||||
Dir { path: "/baz".into(), rank: sigmoid(7.0), last_accessed: 0 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/foo/bar".into(), rank: 9.0 + sigmoid(2.0), last_accessed: 900 },
|
||||
Dir { path: "/quux/quuz".into(), rank: 1.0 + sigmoid(5.0), last_accessed: 100 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
];
|
||||
println!("exp: {exp:?}");
|
||||
|
||||
for (dir1, dir2) in db.dirs().iter().zip(exp) {
|
||||
assert_eq!(dir1.path, dir2.path);
|
||||
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
||||
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_z() {
|
||||
let data_dir = tempfile::tempdir().unwrap();
|
||||
let mut db = Database::open_dir(data_dir.path()).unwrap();
|
||||
for (path, rank, last_accessed) in [
|
||||
("/quux/quuz", 1.0, 100),
|
||||
("/corge/grault/garply", 6.0, 600),
|
||||
("/waldo/fred/plugh", 3.0, 300),
|
||||
("/xyzzy/thud", 8.0, 800),
|
||||
("/foo/bar", 9.0, 900),
|
||||
] {
|
||||
db.add_unchecked(path, rank, last_accessed);
|
||||
}
|
||||
|
||||
let buffer = "\
|
||||
/baz|7|700
|
||||
/quux/quuz|4|400
|
||||
/foo/bar|2|200
|
||||
/quux/quuz|5|500";
|
||||
import_z(&mut db, buffer).unwrap();
|
||||
|
||||
db.sort_by_path();
|
||||
println!("got: {:?}", &db.dirs());
|
||||
|
||||
let exp = [
|
||||
Dir { path: "/baz".into(), rank: 7.0, last_accessed: 700 },
|
||||
Dir { path: "/corge/grault/garply".into(), rank: 6.0, last_accessed: 600 },
|
||||
Dir { path: "/foo/bar".into(), rank: 11.0, last_accessed: 900 },
|
||||
Dir { path: "/quux/quuz".into(), rank: 10.0, last_accessed: 500 },
|
||||
Dir { path: "/waldo/fred/plugh".into(), rank: 3.0, last_accessed: 300 },
|
||||
Dir { path: "/xyzzy/thud".into(), rank: 8.0, last_accessed: 800 },
|
||||
];
|
||||
println!("exp: {exp:?}");
|
||||
|
||||
for (dir1, dir2) in db.dirs().iter().zip(exp) {
|
||||
assert_eq!(dir1.path, dir2.path);
|
||||
assert!((dir1.rank - dir2.rank).abs() < 0.01);
|
||||
assert_eq!(dir1.last_accessed, dir2.last_accessed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
mod atuin;
|
||||
mod autojump;
|
||||
mod fasd;
|
||||
mod z;
|
||||
mod z_lua;
|
||||
mod zsh_z;
|
||||
|
||||
pub(crate) use atuin::Atuin;
|
||||
pub(crate) use autojump::Autojump;
|
||||
pub(crate) use fasd::Fasd;
|
||||
pub(crate) use z::Z;
|
||||
pub(crate) use z_lua::ZLua;
|
||||
pub(crate) use zsh_z::ZshZ;
|
||||
|
||||
use std::io::{self, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use crate::config;
|
||||
use crate::db::{Database, Dir};
|
||||
|
||||
pub(crate) trait Importer {
|
||||
/// Yields directory entries to be imported.
|
||||
///
|
||||
/// The outer `Result` reports failure to fetch the input (e.g. missing
|
||||
/// file, subprocess errored). The per-item `Result` reports a malformed
|
||||
/// row, which doesn't necessarily abort the whole import.
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>>;
|
||||
}
|
||||
|
||||
/// A single record that failed to import.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ImportError {
|
||||
/// Path of the source file containing the offending record. `None` if the
|
||||
/// importer is not file-based (e.g. atuin streams from a subprocess).
|
||||
pub path: Option<PathBuf>,
|
||||
|
||||
/// 1-indexed line number of the offending input.
|
||||
pub line_num: usize,
|
||||
|
||||
/// Underlying reason the record could not be imported.
|
||||
pub source: anyhow::Error,
|
||||
}
|
||||
|
||||
/// Drives a single importer end-to-end: writes each `Ok` dir into the
|
||||
/// database and prints each `Err` to stderr in `<path>:<line>: <reason>`
|
||||
/// format. Doesn't abort on per-record errors — bad rows are skipped, the
|
||||
/// rest of the import continues. After the iteration completes successfully,
|
||||
/// the database is deduplicated and aged.
|
||||
pub(crate) fn run<I: Importer>(importer: &I, db: &mut Database) -> Result<()> {
|
||||
let stderr = io::stderr();
|
||||
let mut stderr = stderr.lock();
|
||||
|
||||
for entry in importer.dirs()? {
|
||||
match entry {
|
||||
Ok(dir) => db.add_unchecked(dir.path, dir.rank, dir.last_accessed),
|
||||
Err(e) => {
|
||||
let location = match &e.path {
|
||||
Some(path) => format!("{}:{}", path.display(), e.line_num),
|
||||
None => format!("line {}", e.line_num),
|
||||
};
|
||||
let _ = writeln!(stderr, "{location}: {:#}", e.source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if db.dirty() {
|
||||
db.dedup();
|
||||
let max_age = config::maxage()?;
|
||||
db.age(max_age);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
use std::borrow::Cow;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::process::{Child, ChildStdout, Command, Stdio};
|
||||
use std::str;
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
use crate::db::{Dir, Epoch};
|
||||
use crate::import::{ImportError, Importer};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct Atuin {}
|
||||
|
||||
impl Importer for Atuin {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
// atuin renders `{time}` as `YYYY-MM-DD HH:MM:SS` in UTC.
|
||||
let mut child = Command::new("atuin")
|
||||
.args(["history", "list", "--format={time}\t{directory}", "--print0"])
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.context("failed to run `atuin`; is it installed and on PATH?")?;
|
||||
let stdout = child.stdout.take().expect("stdout piped");
|
||||
let reader = BufReader::new(stdout);
|
||||
Ok(Iter::new(reader, child))
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates atuin's NUL-separated `{time}\t{directory}` records, emitting one
|
||||
/// `Dir` per directory transition (consecutive same-path records collapse).
|
||||
/// Owns the `Child` handle so the subprocess is reaped on Drop.
|
||||
struct Iter {
|
||||
reader: BufReader<ChildStdout>,
|
||||
buf: Vec<u8>,
|
||||
line_num: usize,
|
||||
|
||||
child: Child,
|
||||
prev_cwd: Option<String>,
|
||||
}
|
||||
|
||||
impl Iter {
|
||||
fn new(reader: BufReader<ChildStdout>, child: Child) -> Self {
|
||||
Self { reader, buf: Vec::new(), line_num: 0, child, prev_cwd: None }
|
||||
}
|
||||
|
||||
fn err(&self, source: anyhow::Error) -> ImportError {
|
||||
ImportError { path: None, line_num: self.line_num, source }
|
||||
}
|
||||
|
||||
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
|
||||
let line =
|
||||
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
|
||||
|
||||
let (timestamp, path) =
|
||||
line.split_once('\t').ok_or_else(|| self.err(anyhow!("invalid entry: {line}")))?;
|
||||
|
||||
let timestamp_format =
|
||||
time::macros::format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
|
||||
let timestamp = time::PrimitiveDateTime::parse(timestamp, timestamp_format)
|
||||
.map_err(|e| self.err(anyhow!(e).context(format!("invalid timestamp: {timestamp:?}"))))?
|
||||
.assume_utc()
|
||||
.unix_timestamp();
|
||||
|
||||
let dir = Dir {
|
||||
path: Cow::Owned(path.to_string()),
|
||||
rank: 1.0,
|
||||
last_accessed: timestamp as Epoch,
|
||||
};
|
||||
Ok(dir)
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for Iter {
|
||||
type Item = Result<Dir<'static>, ImportError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
self.buf.clear();
|
||||
self.line_num += 1;
|
||||
|
||||
match self.reader.read_until(b'\0', &mut self.buf) {
|
||||
Ok(0) => return None,
|
||||
Ok(_) => {
|
||||
if self.buf.last() == Some(&b'\0') {
|
||||
self.buf.pop();
|
||||
}
|
||||
if self.buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = self.parse_line(&self.buf);
|
||||
match &result {
|
||||
Ok(dir) => {
|
||||
let path = dir.path.as_ref();
|
||||
if self.prev_cwd.as_deref() == Some(path) {
|
||||
continue; // dedup consecutive same-path entries
|
||||
}
|
||||
self.prev_cwd = Some(path.to_string());
|
||||
return Some(result);
|
||||
}
|
||||
Err(_) => return Some(result),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return Some(Err(self.err(anyhow!(e).context("could not read from atuin"))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Iter {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.child.kill();
|
||||
let _ = self.child.wait();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::import::{ImportError, Importer};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct Autojump {}
|
||||
|
||||
impl Importer for Autojump {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
Ok(Iter::new(reader, path))
|
||||
}
|
||||
}
|
||||
|
||||
struct Iter<R: BufRead> {
|
||||
reader: R,
|
||||
buf: Vec<u8>,
|
||||
line_num: usize,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl<R: BufRead> Iter<R> {
|
||||
fn new(reader: R, path: PathBuf) -> Self {
|
||||
Self { reader, buf: Vec::new(), line_num: 0, path }
|
||||
}
|
||||
|
||||
fn err(&self, source: anyhow::Error) -> ImportError {
|
||||
ImportError { path: Some(self.path.clone()), line_num: self.line_num, source }
|
||||
}
|
||||
|
||||
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
|
||||
let line =
|
||||
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
|
||||
|
||||
let (rank, path) =
|
||||
line.split_once('\t').ok_or_else(|| self.err(anyhow!("invalid entry: {line}")))?;
|
||||
let rank = rank
|
||||
.parse::<f64>()
|
||||
.map_err(|e| self.err(anyhow!(e).context(format!("invalid rank: {rank}"))))?;
|
||||
|
||||
// Normalize the rank using a sigmoid function. Don't import actual ranks from
|
||||
// autojump, since its scoring algorithm is very different and might
|
||||
// take a while to normalize.
|
||||
let rank = sigmoid(rank);
|
||||
|
||||
Ok(Dir { path: Cow::Owned(path.to_string()), rank, last_accessed: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BufRead> Iterator for Iter<R> {
|
||||
type Item = Result<Dir<'static>, ImportError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
self.buf.clear();
|
||||
self.line_num += 1;
|
||||
|
||||
match self.reader.read_until(b'\n', &mut self.buf) {
|
||||
Ok(0) => return None,
|
||||
Ok(_) => {
|
||||
if self.buf.last() == Some(&b'\n') {
|
||||
self.buf.pop();
|
||||
}
|
||||
if self.buf.last() == Some(&b'\r') {
|
||||
self.buf.pop();
|
||||
}
|
||||
if self.buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
return Some(self.parse_line(&self.buf));
|
||||
}
|
||||
Err(e) => return Some(Err(self.err(anyhow::Error::from(e)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors autojump's path logic:
|
||||
///
|
||||
/// ```python
|
||||
/// if is_osx():
|
||||
/// data_home = os.path.join(os.path.expanduser('~'), 'Library')
|
||||
/// elif is_windows():
|
||||
/// data_home = os.getenv('APPDATA')
|
||||
/// else:
|
||||
/// data_home = os.getenv(
|
||||
/// 'XDG_DATA_HOME',
|
||||
/// os.path.join(os.path.expanduser('~'), '.local', 'share'),
|
||||
/// )
|
||||
/// data_path = os.path.join(data_home, 'autojump', 'autojump.txt')
|
||||
/// ```
|
||||
fn data_path() -> Result<PathBuf> {
|
||||
let mut path = if cfg!(target_os = "macos") {
|
||||
let mut path = dirs::home_dir().context("could not find home directory")?;
|
||||
path.push("Library");
|
||||
path
|
||||
} else if cfg!(target_os = "windows") {
|
||||
let appdata = env::var_os("APPDATA").context("%APPDATA% is not set")?;
|
||||
PathBuf::from(appdata)
|
||||
} else if let Some(xdg) = env::var_os("XDG_DATA_HOME") {
|
||||
PathBuf::from(xdg)
|
||||
} else {
|
||||
let mut path = dirs::home_dir().context("could not find home directory")?;
|
||||
path.push(".local");
|
||||
path.push("share");
|
||||
path
|
||||
};
|
||||
path.push("autojump");
|
||||
path.push("autojump.txt");
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn sigmoid(x: f64) -> f64 {
|
||||
1.0 / (1.0 + (-x).exp())
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::import::{ImportError, Importer, z};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct Fasd {}
|
||||
|
||||
impl Importer for Fasd {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
// fasd uses the same `path|rank|last_accessed` line format as z, so reuse z's iterator.
|
||||
Ok(z::Iter::new(reader, path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors fasd's path logic:
|
||||
///
|
||||
/// ```sh
|
||||
/// [ -z "$_FASD_DATA" ] && _FASD_DATA="$HOME/.fasd"
|
||||
/// ```
|
||||
fn data_path() -> Result<PathBuf> {
|
||||
match env::var_os("_FASD_DATA") {
|
||||
Some(path) => Ok(PathBuf::from(path)),
|
||||
None => {
|
||||
let mut path = dirs::home_dir().context("could not find home directory")?;
|
||||
path.push(".fasd");
|
||||
Ok(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::import::{ImportError, Importer};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct Z {}
|
||||
|
||||
impl Importer for Z {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
Ok(Iter::new(reader, path))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Iter<R: BufRead> {
|
||||
reader: R,
|
||||
buf: Vec<u8>,
|
||||
line_num: usize,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl<R: BufRead> Iter<R> {
|
||||
pub(crate) fn new(reader: R, path: PathBuf) -> Self {
|
||||
Self { reader, buf: Vec::new(), line_num: 0, path }
|
||||
}
|
||||
|
||||
fn err(&self, source: anyhow::Error) -> ImportError {
|
||||
ImportError { path: Some(self.path.clone()), line_num: self.line_num, source }
|
||||
}
|
||||
|
||||
fn parse_line(&self, line: &[u8]) -> Result<Dir<'static>, ImportError> {
|
||||
let line =
|
||||
str::from_utf8(line).map_err(|e| self.err(anyhow!(e).context("invalid utf-8")))?;
|
||||
let err = || self.err(anyhow!("invalid entry: {line}"));
|
||||
|
||||
// z stores entries as `path|rank|last_accessed`. Use `rsplitn` so paths
|
||||
// containing `|` are preserved.
|
||||
let mut split = line.rsplitn(3, '|');
|
||||
|
||||
let last_accessed = split.next().ok_or_else(err)?;
|
||||
let last_accessed = last_accessed.parse::<u64>().map_err(|_| err())?;
|
||||
|
||||
let rank = split.next().ok_or_else(err)?;
|
||||
let rank = rank.parse::<f64>().map_err(|_| err())?;
|
||||
|
||||
let path = split.next().ok_or_else(err)?;
|
||||
|
||||
Ok(Dir { path: Cow::Owned(path.to_string()), rank, last_accessed })
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BufRead> Iterator for Iter<R> {
|
||||
type Item = Result<Dir<'static>, ImportError>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
self.buf.clear();
|
||||
self.line_num += 1;
|
||||
|
||||
match self.reader.read_until(b'\n', &mut self.buf) {
|
||||
Ok(0) => return None,
|
||||
Ok(_) => {
|
||||
if self.buf.last() == Some(&b'\n') {
|
||||
self.buf.pop();
|
||||
}
|
||||
if self.buf.last() == Some(&b'\r') {
|
||||
self.buf.pop();
|
||||
}
|
||||
if self.buf.is_empty() {
|
||||
continue;
|
||||
}
|
||||
return Some(self.parse_line(&self.buf));
|
||||
}
|
||||
Err(e) => return Some(Err(self.err(anyhow::Error::from(e)))),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors z's path logic:
|
||||
///
|
||||
/// ```sh
|
||||
/// local datafile="${_Z_DATA:-$HOME/.z}"
|
||||
/// ```
|
||||
fn data_path() -> Result<PathBuf> {
|
||||
match env::var_os("_Z_DATA") {
|
||||
Some(path) => Ok(PathBuf::from(path)),
|
||||
None => {
|
||||
let mut path = dirs::home_dir().context("could not find home directory")?;
|
||||
path.push(".z");
|
||||
Ok(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::import::{ImportError, Importer, z};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct ZLua {}
|
||||
|
||||
impl Importer for ZLua {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let err = match File::open(&path) {
|
||||
Ok(file) => return Ok(z::Iter::new(BufReader::new(file), path)),
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => e,
|
||||
Err(e) => return Err(e).with_context(|| format!("could not read {path:?}")),
|
||||
};
|
||||
|
||||
let fish_path = data_path_fish()?;
|
||||
let file = match File::open(&fish_path) {
|
||||
Ok(file) => file,
|
||||
// Both paths missing - report the original path's error.
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||
return Err(err).with_context(|| format!("could not read {path:?}"));
|
||||
}
|
||||
// Fish path failed for some other reason (permissions, etc.)
|
||||
Err(e) => return Err(e).with_context(|| format!("could not read {fish_path:?}")),
|
||||
};
|
||||
// z.lua uses the same `path|rank|last_accessed` line format as z.
|
||||
Ok(z::Iter::new(BufReader::new(file), fish_path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors z.lua's path logic:
|
||||
///
|
||||
/// ```lua
|
||||
/// DATA_FILE = '~/.zlua' -- default
|
||||
///
|
||||
/// -- in z_init():
|
||||
/// local _zl_data = os.getenv('_ZL_DATA')
|
||||
/// if _zl_data ~= nil and _zl_data ~= "" then
|
||||
/// if windows then
|
||||
/// DATA_FILE = _zl_data
|
||||
/// else
|
||||
/// -- avoid windows environments affect cygwin & msys
|
||||
/// if not string.match(_zl_data, '^%a:[/\\]') then
|
||||
/// DATA_FILE = _zl_data
|
||||
/// end
|
||||
/// end
|
||||
/// end
|
||||
/// ```
|
||||
fn data_path() -> Result<PathBuf> {
|
||||
if let Some(path) = env::var_os("_ZL_DATA")
|
||||
// Skip empty paths.
|
||||
.filter(|path| !path.is_empty())
|
||||
// On non-Windows, skip values that look like a Windows path (`C:\...`)
|
||||
// — guards against Cygwin/MSYS environments leaking through.
|
||||
.filter(|path| cfg!(target_os = "windows") || !looks_like_windows_path(path))
|
||||
{
|
||||
return Ok(PathBuf::from(path));
|
||||
}
|
||||
|
||||
let mut path = dirs::home_dir().context("could not find home directory")?;
|
||||
path.push(".zlua");
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Mirrors z.lua's path logic on Fish:
|
||||
///
|
||||
/// ```fish
|
||||
/// if test -z "$XDG_DATA_HOME"
|
||||
/// set -U _ZL_DATA_DIR "$HOME/.local/share/zlua"
|
||||
/// else
|
||||
/// set -U _ZL_DATA_DIR "$XDG_DATA_HOME/zlua"
|
||||
/// end
|
||||
/// set -x _ZL_DATA "$_ZL_DATA_DIR/zlua.txt"
|
||||
/// ```
|
||||
fn data_path_fish() -> Result<PathBuf> {
|
||||
let mut path = match env::var_os("XDG_DATA_HOME") {
|
||||
Some(xdg) => PathBuf::from(xdg),
|
||||
None => {
|
||||
let mut path = dirs::home_dir().context("could not find home directory")?;
|
||||
path.push(".local");
|
||||
path.push("share");
|
||||
path
|
||||
}
|
||||
};
|
||||
|
||||
path.push("zlua");
|
||||
path.push("zlua.txt");
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Matches Lua's `^%a:[/\\]` — ASCII letter, colon, slash-or-backslash.
|
||||
fn looks_like_windows_path(s: &OsStr) -> bool {
|
||||
let bytes = s.as_encoded_bytes();
|
||||
bytes.len() >= 3
|
||||
&& bytes[0].is_ascii_alphabetic()
|
||||
&& bytes[1] == b':'
|
||||
&& (bytes[2] == b'/' || bytes[2] == b'\\')
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
use crate::db::Dir;
|
||||
use crate::import::{ImportError, Importer, z};
|
||||
|
||||
#[derive(clap::Args, Clone, Debug)]
|
||||
pub(crate) struct ZshZ {}
|
||||
|
||||
impl Importer for ZshZ {
|
||||
fn dirs(&self) -> Result<impl Iterator<Item = Result<Dir<'static>, ImportError>>> {
|
||||
let path = data_path()?;
|
||||
let file = File::open(&path).with_context(|| format!("could not read {path:?}"))?;
|
||||
let reader = BufReader::new(file);
|
||||
// zsh-z uses the same `path|rank|last_accessed` line format as z.
|
||||
Ok(z::Iter::new(reader, path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirrors zsh-z's path logic:
|
||||
///
|
||||
/// ```sh
|
||||
/// # Allow the user to specify a custom datafile in $ZSHZ_DATA (or legacy $_Z_DATA)
|
||||
/// local custom_datafile="${ZSHZ_DATA:-$_Z_DATA}"
|
||||
/// # If the user specified a datafile, use that or default to ~/.z
|
||||
/// local datafile=${${custom_datafile:-$HOME/.z}:A}
|
||||
/// ```
|
||||
fn data_path() -> Result<PathBuf> {
|
||||
match env::var_os("ZSHZ_DATA").or_else(|| env::var_os("_Z_DATA")) {
|
||||
Some(path) => Ok(PathBuf::from(path)),
|
||||
None => {
|
||||
let mut path = dirs::home_dir().context("could not find home directory")?;
|
||||
path.push(".z");
|
||||
Ok(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ mod cmd;
|
|||
mod config;
|
||||
mod db;
|
||||
mod error;
|
||||
mod import;
|
||||
mod shell;
|
||||
mod util;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue