feat(export): 添加导出数据库功能支持json和csv格式

新增export子命令,支持将数据库中的目录条目导出为json或csv格式。添加了相关依赖库serde_json和csv,并更新了所有shell的自动补全脚本。
This commit is contained in:
soar0216 2026-04-25 09:09:48 +08:00
parent d52c6336b8
commit a128974954
12 changed files with 271 additions and 5 deletions

41
Cargo.lock generated
View File

@ -260,6 +260,27 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "csv"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde_core",
]
[[package]]
name = "csv-core"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
dependencies = [
"memchr",
]
[[package]]
name = "difflib"
version = "0.4.0"
@ -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",
@ -994,6 +1025,7 @@ dependencies = [
"clap_complete_fig",
"clap_complete_nushell",
"color-print",
"csv",
"dirs",
"dunce",
"fastrand",
@ -1003,6 +1035,7 @@ dependencies = [
"rstest",
"rstest_reuse",
"serde",
"serde_json",
"tempfile",
"which",
]

View File

@ -28,8 +28,10 @@ dirs = "6.0.0"
dunce = "1.0.1"
fastrand = "2.0.0"
glob = "0.3.0"
csv = "1.2.0"
ouroboros = "0.18.3"
serde = { version = "1.0.116", features = ["derive"] }
serde_json = "1.0.96"
[target.'cfg(unix)'.dependencies]
nix = { version = "0.30.1", default-features = false, features = [

View File

@ -94,6 +94,18 @@ _arguments "${_arguments_options[@]}" : \
;;
esac
;;
(export)
_arguments "${_arguments_options[@]}" : \
'-f+[Output format (json or csv)]:FORMAT:(json csv)' \
'--format=[Output format (json or csv)]:FORMAT:(json csv)' \
'-o+[Output file path (default\: stdout)]:OUT:_files' \
'--out=[Output file path (default\: stdout)]:OUT:_files' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \
'--version[Print version]' \
&& ret=0
;;
(import)
_arguments "${_arguments_options[@]}" : \
'--from=[Application to import from]:FROM:(autojump z)' \
@ -155,6 +167,7 @@ _zoxide_commands() {
local commands; commands=(
'add:Add a new directory or increment its rank' \
'edit:Edit the database' \
'export:Export entries from the database' \
'import:Import entries from another application' \
'init:Generate shell configuration' \
'query:Search for a directory in the database' \
@ -197,6 +210,11 @@ _zoxide__edit__reload_commands() {
local commands; commands=()
_describe -t commands 'zoxide edit reload commands' commands "$@"
}
(( $+functions[_zoxide__export_commands] )) ||
_zoxide__export_commands() {
local commands; commands=()
_describe -t commands 'zoxide export commands' commands "$@"
}
(( $+functions[_zoxide__import_commands] )) ||
_zoxide__import_commands() {
local commands; commands=()

View File

@ -27,6 +27,7 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
[CompletionResult]::new('add', 'add', [CompletionResultType]::ParameterValue, 'Add a new directory or increment its rank')
[CompletionResult]::new('edit', 'edit', [CompletionResultType]::ParameterValue, 'Edit the database')
[CompletionResult]::new('export', 'export', [CompletionResultType]::ParameterValue, 'Export entries from the database')
[CompletionResult]::new('import', 'import', [CompletionResultType]::ParameterValue, 'Import entries from another application')
[CompletionResult]::new('init', 'init', [CompletionResultType]::ParameterValue, 'Generate shell configuration')
[CompletionResult]::new('query', 'query', [CompletionResultType]::ParameterValue, 'Search for a directory in the database')
@ -81,6 +82,17 @@ Register-ArgumentCompleter -Native -CommandName 'zoxide' -ScriptBlock {
[CompletionResult]::new('--version', '--version', [CompletionResultType]::ParameterName, 'Print version')
break
}
'zoxide;export' {
[CompletionResult]::new('-f', '-f', [CompletionResultType]::ParameterName, 'Output format (json or csv)')
[CompletionResult]::new('--format', '--format', [CompletionResultType]::ParameterName, 'Output format (json or csv)')
[CompletionResult]::new('-o', '-o', [CompletionResultType]::ParameterName, 'Output file path (default: stdout)')
[CompletionResult]::new('--out', '--out', [CompletionResultType]::ParameterName, 'Output file path (default: stdout)')
[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' {
[CompletionResult]::new('--from', '--from', [CompletionResultType]::ParameterName, 'Application to import from')
[CompletionResult]::new('--merge', '--merge', [CompletionResultType]::ParameterName, 'Merge into existing database')

View File

@ -22,6 +22,9 @@ _zoxide() {
zoxide,edit)
cmd="zoxide__edit"
;;
zoxide,export)
cmd="zoxide__export"
;;
zoxide,import)
cmd="zoxide__import"
;;
@ -53,7 +56,7 @@ _zoxide() {
case "${cmd}" in
zoxide)
opts="-h -V --help --version add edit import init query remove"
opts="-h -V --help --version add edit export import init query remove"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -158,6 +161,58 @@ _zoxide() {
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__export)
opts="-f -o -h -V --format --out --help --version"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
fi
case "${prev}" in
--format)
COMPREPLY=($(compgen -W "json csv" -- "${cur}"))
return 0
;;
-f)
COMPREPLY=($(compgen -W "json csv" -- "${cur}"))
return 0
;;
--out)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
-o)
local oldifs
if [ -n "${IFS+x}" ]; then
oldifs="$IFS"
fi
IFS=$'\n'
COMPREPLY=($(compgen -f "${cur}"))
if [ -n "${oldifs+x}" ]; then
IFS="$oldifs"
fi
if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then
compopt -o filenames
fi
return 0
;;
*)
COMPREPLY=()
;;
esac
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
;;
zoxide__import)
opts="-h -V --from --merge --help --version <PATH>"
if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then

View File

@ -24,6 +24,7 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand --version 'Print version'
cand add 'Add a new directory or increment its rank'
cand edit 'Edit the database'
cand export 'Export entries from the database'
cand import 'Import entries from another application'
cand init 'Generate shell configuration'
cand query 'Search for a directory in the database'
@ -71,6 +72,16 @@ set edit:completion:arg-completer[zoxide] = {|@words|
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;export'= {
cand -f 'Output format (json or csv)'
cand --format 'Output format (json or csv)'
cand -o 'Output file path (default: stdout)'
cand --out 'Output file path (default: stdout)'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'
cand --version 'Print version'
}
&'zoxide;import'= {
cand --from 'Application to import from'
cand --merge 'Merge into existing database'

View File

@ -28,6 +28,7 @@ complete -c zoxide -n "__fish_zoxide_needs_command" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_needs_command" -s V -l version -d 'Print version'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "add" -d 'Add a new directory or increment its rank'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "edit" -d 'Edit the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "export" -d 'Export entries from the database'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "import" -d 'Import entries from another application'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "init" -d 'Generate shell configuration'
complete -c zoxide -n "__fish_zoxide_needs_command" -f -a "query" -d 'Search for a directory in the database'
@ -49,6 +50,11 @@ 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 export" -s f -l format -d 'Output format (json or csv)' -r -f -a "json\t''
csv\t''"
complete -c zoxide -n "__fish_zoxide_using_subcommand export" -s o -l out -d 'Output file path (default: stdout)' -r -F
complete -c zoxide -n "__fish_zoxide_using_subcommand export" -s h -l help -d 'Print help'
complete -c zoxide -n "__fish_zoxide_using_subcommand export" -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'

View File

@ -43,6 +43,18 @@ module completions {
--version(-V) # Print version
]
def "nu-complete zoxide export format" [] {
[ "json" "csv" ]
}
# Export entries from the database
export extern "zoxide export" [
--format(-f): string@"nu-complete zoxide export format" # Output format (json or csv)
--out(-o): path # Output file path (default: stdout)
--help(-h) # Print help
--version(-V) # Print version
]
def "nu-complete zoxide import from" [] {
[ "autojump" "z" ]
}

View File

@ -111,6 +111,42 @@ const completion: Fig.Spec = {
},
],
},
{
name: "export",
description: "Export entries from the database",
options: [
{
name: ["-f", "--format"],
description: "Output format (json or csv)",
isRepeatable: true,
args: {
name: "format",
suggestions: [
"json",
"csv",
],
},
},
{
name: ["-o", "--out"],
description: "Output file path (default: stdout)",
isRepeatable: true,
args: {
name: "out",
isOptional: true,
template: "filepaths",
},
},
{
name: ["-h", "--help"],
description: "Print help",
},
{
name: ["-V", "--version"],
description: "Print version",
},
],
},
{
name: "import",
description: "Import entries from another application",

View File

@ -43,6 +43,7 @@ https://github.com/ajeetdsouza/zoxide
pub enum Cmd {
Add(Add),
Edit(Edit),
Export(Export),
Import(Import),
Init(Init),
Query(Query),
@ -88,6 +89,28 @@ pub enum EditCommand {
Reload,
}
/// Export entries from the database
#[derive(Debug, Parser)]
#[clap(
author,
help_template = HelpTemplate,
)]
pub struct Export {
/// Output format (json or csv)
#[clap(value_enum, long, short)]
pub format: ExportFormat,
/// Output file path (default: stdout)
#[clap(long, short, value_hint = ValueHint::FilePath)]
pub out: Option<PathBuf>,
}
#[derive(ValueEnum, Clone, Debug)]
pub enum ExportFormat {
Json,
Csv,
}
/// Import entries from another application
#[derive(Debug, Parser)]
#[clap(

56
src/cmd/export.rs Normal file
View File

@ -0,0 +1,56 @@
use std::fs;
use std::io::{self, Write};
use std::path::Path;
use anyhow::{Context, Result};
use crate::cmd::{Export, ExportFormat, Run};
use crate::db::Database;
impl Run for Export {
fn run(&self) -> Result<()> {
let db = Database::open()?;
let dirs = db.dirs();
let output = match self.format {
ExportFormat::Json => serde_json::to_string(dirs)
.context("could not serialize to JSON")?,
ExportFormat::Csv => {
let mut wtr = csv::Writer::from_writer(Vec::new());
for dir in dirs {
wtr.write_record([&*dir.path, &dir.rank.to_string(), &dir.last_accessed.to_string()])
.context("could not write CSV record")?;
}
wtr.flush().context("could not flush CSV writer")?;
String::from_utf8(wtr.into_inner().context("could not get CSV bytes")?)
.context("CSV output is not valid UTF-8")?
}
};
match &self.out {
Some(path) => {
write_to_file(path, &output)
.with_context(|| format!("could not write to file: {}", path.display()))?;
}
None => {
writeln!(io::stdout(), "{output}")
.context("could not write to stdout")?;
}
}
Ok(())
}
}
fn write_to_file(path: impl AsRef<Path>, content: &str) -> Result<()> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
fs::create_dir_all(parent)
.with_context(|| format!("could not create directory: {}", parent.display()))?;
}
}
fs::write(path, content)
.with_context(|| format!("could not write to file: {}", path.display()))?;
Ok(())
}

View File

@ -1,6 +1,7 @@
mod add;
mod cmd;
mod edit;
mod export;
mod import;
mod init;
mod query;
@ -19,6 +20,7 @@ impl Run for Cmd {
match self {
Cmd::Add(cmd) => cmd.run(),
Cmd::Edit(cmd) => cmd.run(),
Cmd::Export(cmd) => cmd.run(),
Cmd::Import(cmd) => cmd.run(),
Cmd::Init(cmd) => cmd.run(),
Cmd::Query(cmd) => cmd.run(),