mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
fix: make URLs with invalid UTF-8 serializable
This commit is contained in:
parent
0e3cd8545c
commit
5f63cefd1e
14 changed files with 161 additions and 63 deletions
|
|
@ -1 +1 @@
|
|||
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash"],"version":"0.2","flagWords":[],"language":"en"}
|
||||
{"version":"0.2","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded"],"language":"en"}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
use anyhow::{Context, Result};
|
||||
use yazi_fs::{ok_or_not_found, remove_dir_clean, services::Local};
|
||||
use yazi_macro::outln;
|
||||
use yazi_shared::url::Url;
|
||||
|
||||
use super::Dependency;
|
||||
use crate::shared::{maybe_exists, remove_sealed};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use serde::Deserialize;
|
|||
use yazi_codegen::DeserializeOver2;
|
||||
|
||||
use super::{Offset, Origin};
|
||||
use crate::popup::Position;
|
||||
|
||||
#[derive(Deserialize, DeserializeOver2)]
|
||||
pub struct Confirm {
|
||||
|
|
@ -30,4 +31,20 @@ pub struct Confirm {
|
|||
|
||||
impl Confirm {
|
||||
pub const fn border(&self) -> u16 { 2 }
|
||||
|
||||
pub const fn trash_position(&self) -> Position {
|
||||
Position::new(self.trash_origin, self.trash_offset)
|
||||
}
|
||||
|
||||
pub const fn delete_position(&self) -> Position {
|
||||
Position::new(self.delete_origin, self.delete_offset)
|
||||
}
|
||||
|
||||
pub const fn overwrite_position(&self) -> Position {
|
||||
Position::new(self.overwrite_origin, self.overwrite_offset)
|
||||
}
|
||||
|
||||
pub const fn quit_position(&self) -> Position {
|
||||
Position::new(self.quit_origin, self.quit_offset)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use ratatui::{text::{Line, Text}, widgets::{Paragraph, Wrap}};
|
||||
use yazi_shared::url::Url;
|
||||
use yazi_shared::{IntoStringLossy, url::Url};
|
||||
|
||||
use super::{Offset, Origin, Position};
|
||||
use super::{Offset, Position};
|
||||
use crate::YAZI;
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -106,14 +106,14 @@ impl InputCfg {
|
|||
impl ConfirmCfg {
|
||||
fn new(
|
||||
title: String,
|
||||
(origin, offset): (Origin, Offset),
|
||||
position: Position,
|
||||
body: Option<Text<'static>>,
|
||||
list: Option<Text<'static>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
position: Position::new(origin, offset),
|
||||
position,
|
||||
title: Line::raw(title),
|
||||
body: body.map(|c| Paragraph::new(c).wrap(Wrap { trim: false })).unwrap_or_default(),
|
||||
body: body.map(|b| Paragraph::new(b).wrap(Wrap { trim: false })).unwrap_or_default(),
|
||||
list: list.map(|l| Paragraph::new(l).wrap(Wrap { trim: false })).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ impl ConfirmCfg {
|
|||
pub fn trash(urls: &[yazi_shared::url::Url]) -> Self {
|
||||
Self::new(
|
||||
Self::replace_number(&YAZI.confirm.trash_title, urls.len()),
|
||||
(YAZI.confirm.trash_origin, YAZI.confirm.trash_offset),
|
||||
YAZI.confirm.trash_position(),
|
||||
None,
|
||||
Self::truncate_list(urls.iter(), urls.len(), 100),
|
||||
)
|
||||
|
|
@ -130,7 +130,7 @@ impl ConfirmCfg {
|
|||
pub fn delete(urls: &[yazi_shared::url::Url]) -> Self {
|
||||
Self::new(
|
||||
Self::replace_number(&YAZI.confirm.delete_title, urls.len()),
|
||||
(YAZI.confirm.delete_origin, YAZI.confirm.delete_offset),
|
||||
YAZI.confirm.delete_position(),
|
||||
None,
|
||||
Self::truncate_list(urls.iter(), urls.len(), 100),
|
||||
)
|
||||
|
|
@ -139,16 +139,16 @@ impl ConfirmCfg {
|
|||
pub fn overwrite(url: &Url) -> Self {
|
||||
Self::new(
|
||||
YAZI.confirm.overwrite_title.to_owned(),
|
||||
(YAZI.confirm.overwrite_origin, YAZI.confirm.overwrite_offset),
|
||||
YAZI.confirm.overwrite_position(),
|
||||
Some(Text::raw(&YAZI.confirm.overwrite_body)),
|
||||
Some(url.to_string().into()),
|
||||
Some(url.into_string_lossy().into()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn quit(len: usize, names: Vec<String>) -> Self {
|
||||
Self::new(
|
||||
Self::replace_number(&YAZI.confirm.quit_title, len),
|
||||
(YAZI.confirm.quit_origin, YAZI.confirm.quit_offset),
|
||||
YAZI.confirm.quit_position(),
|
||||
Some(Text::raw(&YAZI.confirm.quit_body)),
|
||||
Self::truncate_list(names.into_iter(), len, 10),
|
||||
)
|
||||
|
|
@ -159,7 +159,7 @@ impl ConfirmCfg {
|
|||
}
|
||||
|
||||
fn truncate_list(
|
||||
it: impl Iterator<Item = impl Into<String>>,
|
||||
it: impl Iterator<Item = impl IntoStringLossy>,
|
||||
len: usize,
|
||||
max: usize,
|
||||
) -> Option<Text<'static>> {
|
||||
|
|
@ -169,7 +169,7 @@ impl ConfirmCfg {
|
|||
lines.push(format!("... and {} more", len - max));
|
||||
break;
|
||||
}
|
||||
lines.push(s.into());
|
||||
lines.push(s.into_string_lossy());
|
||||
}
|
||||
Some(Text::from_iter(lines))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pub struct Position {
|
|||
|
||||
impl Position {
|
||||
#[inline]
|
||||
pub fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }
|
||||
pub const fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }
|
||||
|
||||
pub fn rect(&self, WindowSize { columns, rows, .. }: WindowSize) -> Rect {
|
||||
use Origin::*;
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ impl Cha {
|
|||
pub fn from_dummy(_url: &Url, ft: Option<FileType>) -> Self {
|
||||
let mut me = ft.map(Self::from_half_ft).unwrap_or_default();
|
||||
#[cfg(unix)]
|
||||
if yazi_shared::url::Urn::new(_url).is_hidden() {
|
||||
if _url.urn().is_hidden() {
|
||||
me.kind |= ChaKind::HIDDEN;
|
||||
}
|
||||
me
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use crate::{CWD, services};
|
|||
#[inline]
|
||||
pub fn clean_url(url: &Url) -> Url { Url::from(clean_path(url)) }
|
||||
|
||||
// FIXME: VFS
|
||||
#[inline]
|
||||
pub fn clean_path(path: impl AsRef<Path>) -> PathBuf { _clean_path(path.as_ref()) }
|
||||
|
||||
|
|
@ -30,9 +31,11 @@ fn _clean_path(path: &Path) -> PathBuf {
|
|||
if out.is_empty() { PathBuf::from(".") } else { out.iter().collect() }
|
||||
}
|
||||
|
||||
// FIXME: VFS
|
||||
#[inline]
|
||||
pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
|
||||
|
||||
// FIXME: keep the original scheme of the Url
|
||||
#[inline]
|
||||
pub fn expand_url<'a>(url: impl Into<Cow<'a, Url>>) -> Cow<'a, Url> {
|
||||
let u: Cow<'a, Url> = url.into();
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ impl Scheduler {
|
|||
|
||||
pub fn file_cut(&self, from: Url, mut to: Url, force: bool) {
|
||||
let mut ongoing = self.ongoing.lock();
|
||||
let id = ongoing.add(TaskKind::User, format!("Cut {from} to {to}"));
|
||||
let id = ongoing.add(TaskKind::User, format!("Cut {} to {}", from.display(), to.display()));
|
||||
|
||||
if to.starts_with(&from) && to != from {
|
||||
self.new_and_fail(id, "Cannot cut directory into itself").ok();
|
||||
|
|
@ -108,7 +108,10 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
pub fn file_copy(&self, from: Url, mut to: Url, force: bool, follow: bool) {
|
||||
let id = self.ongoing.lock().add(TaskKind::User, format!("Copy {from} to {to}"));
|
||||
let id = self
|
||||
.ongoing
|
||||
.lock()
|
||||
.add(TaskKind::User, format!("Copy {} to {}", from.display(), to.display()));
|
||||
|
||||
if to.starts_with(&from) && to != from {
|
||||
self.new_and_fail(id, "Cannot copy directory into itself").ok();
|
||||
|
|
@ -125,7 +128,10 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
pub fn file_link(&self, from: Url, mut to: Url, relative: bool, force: bool) {
|
||||
let id = self.ongoing.lock().add(TaskKind::User, format!("Link {from} to {to}"));
|
||||
let id = self
|
||||
.ongoing
|
||||
.lock()
|
||||
.add(TaskKind::User, format!("Link {} to {}", from.display(), to.display()));
|
||||
|
||||
let file = self.file.clone();
|
||||
self.send_micro(id, LOW, async move {
|
||||
|
|
@ -139,7 +145,10 @@ impl Scheduler {
|
|||
}
|
||||
|
||||
pub fn file_hardlink(&self, from: Url, mut to: Url, force: bool, follow: bool) {
|
||||
let id = self.ongoing.lock().add(TaskKind::User, format!("Hardlink {from} to {to}"));
|
||||
let id = self
|
||||
.ongoing
|
||||
.lock()
|
||||
.add(TaskKind::User, format!("Hardlink {} to {}", from.display(), to.display()));
|
||||
|
||||
if to.starts_with(&from) && to != from {
|
||||
self.new_and_fail(id, "Cannot hardlink directory into itself").ok();
|
||||
|
|
@ -157,7 +166,7 @@ impl Scheduler {
|
|||
|
||||
pub fn file_delete(&self, target: Url) {
|
||||
let mut ongoing = self.ongoing.lock();
|
||||
let id = ongoing.add(TaskKind::User, format!("Delete {target}"));
|
||||
let id = ongoing.add(TaskKind::User, format!("Delete {}", target.display()));
|
||||
|
||||
ongoing.hooks.add_async(id, {
|
||||
let target = target.clone();
|
||||
|
|
@ -186,7 +195,7 @@ impl Scheduler {
|
|||
|
||||
pub fn file_trash(&self, target: Url) {
|
||||
let mut ongoing = self.ongoing.lock();
|
||||
let id = ongoing.add(TaskKind::User, format!("Trash {target}"));
|
||||
let id = ongoing.add(TaskKind::User, format!("Trash {}", target.display()));
|
||||
|
||||
ongoing.hooks.add_async(id, {
|
||||
let target = target.clone();
|
||||
|
|
@ -251,7 +260,8 @@ impl Scheduler {
|
|||
let mut ongoing = self.ongoing.lock();
|
||||
|
||||
for target in targets {
|
||||
let id = ongoing.add(TaskKind::Preload, format!("Calculate the size of {target}"));
|
||||
let id =
|
||||
ongoing.add(TaskKind::Preload, format!("Calculate the size of {}", target.display()));
|
||||
let target = target.clone();
|
||||
let throttle = throttle.clone();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
yazi_macro::mod_pub!(errors event shell translit url);
|
||||
|
||||
yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os osstr rand ro_cell source sync_cell terminal throttle time utf8);
|
||||
yazi_macro::mod_flat!(alias bytes chars condition debounce either env id layer natsort os osstr rand ro_cell source string sync_cell terminal throttle time utf8);
|
||||
|
||||
pub fn init() {
|
||||
LOG_LEVEL.replace(<_>::from(std::env::var("YAZI_LOG").unwrap_or_default()));
|
||||
|
|
|
|||
37
yazi-shared/src/string.rs
Normal file
37
yazi-shared/src/string.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use std::{borrow::Cow, ffi::{OsStr, OsString}};
|
||||
|
||||
use crate::url::Url;
|
||||
|
||||
pub trait IntoStringLossy {
|
||||
fn into_string_lossy(self) -> String;
|
||||
}
|
||||
|
||||
impl IntoStringLossy for String {
|
||||
fn into_string_lossy(self) -> String { self }
|
||||
}
|
||||
|
||||
impl IntoStringLossy for &OsStr {
|
||||
fn into_string_lossy(self) -> String { self.to_string_lossy().into_owned() }
|
||||
}
|
||||
|
||||
impl IntoStringLossy for OsString {
|
||||
fn into_string_lossy(self) -> String {
|
||||
match self.to_string_lossy() {
|
||||
Cow::Owned(s) => s,
|
||||
Cow::Borrowed(_) => unsafe { String::from_utf8_unchecked(self.into_encoded_bytes()) },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStringLossy for Cow<'_, OsStr> {
|
||||
fn into_string_lossy(self) -> String {
|
||||
match self {
|
||||
Cow::Owned(s) => s.into_string_lossy(),
|
||||
Cow::Borrowed(s) => s.into_string_lossy(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoStringLossy for &Url {
|
||||
fn into_string_lossy(self) -> String { self.os_str().into_string_lossy() }
|
||||
}
|
||||
20
yazi-shared/src/url/display.rs
Normal file
20
yazi-shared/src/url/display.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use crate::url::Url;
|
||||
|
||||
pub struct Display<'a> {
|
||||
inner: &'a Url,
|
||||
}
|
||||
|
||||
impl<'a> Display<'a> {
|
||||
#[inline]
|
||||
pub fn new(inner: &'a Url) -> Self { Self { inner } }
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Display for Display<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Url { loc, scheme } = self.inner;
|
||||
if scheme.is_virtual() {
|
||||
scheme.fmt(f)?;
|
||||
}
|
||||
loc.display().fmt(f)
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(component loc scheme url urn);
|
||||
yazi_macro::mod_flat!(component display loc scheme url urn);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::{borrow::Cow, fmt::Display};
|
|||
use anyhow::{Result, bail};
|
||||
use percent_encoding::{CONTROLS, PercentEncode, percent_decode, percent_encode};
|
||||
|
||||
use crate::BytesExt;
|
||||
use crate::{BytesExt, url::Loc};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum Scheme {
|
||||
|
|
@ -19,24 +19,29 @@ pub enum Scheme {
|
|||
}
|
||||
|
||||
impl Scheme {
|
||||
pub(super) fn parse(bytes: &[u8]) -> Result<(Self, usize)> {
|
||||
let Some((protocol, rest)) = bytes.split_by_seq(b"://") else {
|
||||
return Ok((Self::Regular, 0));
|
||||
pub(super) fn parse(bytes: &[u8]) -> Result<(Self, usize, bool)> {
|
||||
let Some((mut protocol, rest)) = bytes.split_by_seq(b"://") else {
|
||||
return Ok((Self::Regular, 0, false));
|
||||
};
|
||||
|
||||
let tilde = protocol.ends_with(b"~");
|
||||
if tilde {
|
||||
protocol = &protocol[..protocol.len() - 1];
|
||||
}
|
||||
|
||||
Ok(match protocol {
|
||||
b"regular" => (Self::Regular, 10),
|
||||
b"regular" => (Self::Regular, 10, tilde),
|
||||
b"search" => {
|
||||
let (name, skip) = Self::decode_param(rest)?;
|
||||
(Self::Search(name), 9 + skip)
|
||||
(Self::Search(name), 9 + skip, tilde)
|
||||
}
|
||||
b"archive" => {
|
||||
let (name, skip) = Self::decode_param(rest)?;
|
||||
(Self::Archive(name), 10 + skip)
|
||||
(Self::Archive(name), 10 + skip, tilde)
|
||||
}
|
||||
b"sftp" => {
|
||||
let (name, skip) = Self::decode_param(rest)?;
|
||||
(Self::Sftp(name), 7 + skip)
|
||||
(Self::Sftp(name), 7 + skip, tilde)
|
||||
}
|
||||
_ => bail!("Could not parse protocol from URL: {}", String::from_utf8_lossy(bytes)),
|
||||
})
|
||||
|
|
@ -77,6 +82,17 @@ impl Scheme {
|
|||
|
||||
#[inline]
|
||||
fn encode_param<'a>(s: &'a str) -> PercentEncode<'a> { percent_encode(s.as_bytes(), CONTROLS) }
|
||||
|
||||
pub fn encode_tilded(&self, loc: &Loc) -> String {
|
||||
let loc = percent_encode(loc.as_os_str().as_encoded_bytes(), CONTROLS);
|
||||
match self {
|
||||
Self::Regular => format!("regular~://{loc}"),
|
||||
Self::Search(kw) => format!("search~://{}/{loc}", Self::encode_param(kw)),
|
||||
Self::SearchItem => format!("search-item~://{loc}"),
|
||||
Self::Archive(id) => format!("archive~://{}/{loc}", Self::encode_param(id)),
|
||||
Self::Sftp(id) => format!("sftp~://{}/{loc}", Self::encode_param(id)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Scheme {
|
||||
|
|
@ -84,7 +100,7 @@ impl Display for Scheme {
|
|||
match self {
|
||||
Self::Regular => write!(f, "regular://"),
|
||||
Self::Search(kw) => write!(f, "search://{}/", Self::encode_param(kw)),
|
||||
Self::SearchItem => write!(f, "search_item://"),
|
||||
Self::SearchItem => write!(f, "search-item://"),
|
||||
Self::Archive(id) => write!(f, "archive://{}/", Self::encode_param(id)),
|
||||
Self::Sftp(id) => write!(f, "sftp://{}/", Self::encode_param(id)),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Display, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
|
||||
use std::{borrow::Cow, ffi::OsStr, fmt::{Debug, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}};
|
||||
|
||||
use percent_encoding::{AsciiSet, CONTROLS, percent_encode};
|
||||
use percent_encoding::percent_decode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::UrnBuf;
|
||||
use crate::{IntoOsStr, url::{Components, Loc, Scheme}};
|
||||
|
||||
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
||||
use crate::{IntoOsStr, url::{Components, Display, Loc, Scheme}};
|
||||
|
||||
#[derive(Clone, Default, Eq, Ord, PartialOrd)]
|
||||
pub struct Url {
|
||||
|
|
@ -53,9 +51,15 @@ impl TryFrom<&[u8]> for Url {
|
|||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
let (scheme, skip) = Scheme::parse(bytes)?;
|
||||
let (scheme, skip, tilde) = Scheme::parse(bytes)?;
|
||||
|
||||
Ok(Url { loc: bytes[skip..].into_os_str()?.into(), scheme })
|
||||
let rest = &bytes[skip + tilde as usize..];
|
||||
|
||||
Ok(if tilde {
|
||||
Self { loc: Cow::from(percent_decode(rest)).into_os_str()?.into(), scheme }
|
||||
} else {
|
||||
Self { loc: rest.into_os_str()?.into(), scheme }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -80,25 +84,6 @@ impl AsRef<Path> for Url {
|
|||
fn as_ref(&self) -> &Path { &self.loc }
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
impl Display for Url {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if matches!(self.scheme, Scheme::Regular | Scheme::SearchItem) {
|
||||
return write!(f, "{}", self.loc.display());
|
||||
}
|
||||
|
||||
let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), ENCODE_SET);
|
||||
write!(f, "{}{loc}", self.scheme)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
impl From<&Url> for String {
|
||||
fn from(url: &Url) -> Self { url.to_string() }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Url> for Cow<'a, Url> {
|
||||
fn from(url: &'a Url) -> Self { Cow::Borrowed(url) }
|
||||
}
|
||||
|
|
@ -149,6 +134,12 @@ impl Url {
|
|||
self.scheme.covariant(&other.scheme) && self.loc == other.loc
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn display(&self) -> Display<'_> { Display::new(self) }
|
||||
|
||||
#[inline]
|
||||
pub fn os_str(&self) -> Cow<'_, OsStr> { self.components().os_str() }
|
||||
|
||||
pub fn parent_url(&self) -> Option<Url> {
|
||||
let parent = self.loc.parent()?;
|
||||
let base = self.loc.base();
|
||||
|
|
@ -273,7 +264,12 @@ impl PartialEq for Url {
|
|||
|
||||
impl Serialize for Url {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.collect_str(self)
|
||||
let Url { scheme, loc } = self;
|
||||
match (scheme.is_virtual(), loc.to_str()) {
|
||||
(false, Some(s)) => serializer.serialize_str(s),
|
||||
(true, Some(s)) => serializer.serialize_str(&format!("{scheme}{s}")),
|
||||
(_, None) => serializer.serialize_str(&scheme.encode_tilded(loc)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue