mirror of
https://github.com/oxalica/nil.git
synced 2024-11-23 12:03:30 +03:00
Refactor API of VfsPath
This commit is contained in:
parent
756be130c8
commit
c84d1619d0
@ -2,7 +2,7 @@ use rowan::{TextRange, TextSize};
|
|||||||
use salsa::Durability;
|
use salsa::Durability;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::Path;
|
use std::path::{Component, Path};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
@ -11,71 +11,85 @@ pub struct FileId(pub u32);
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct SourceRootId(pub u32);
|
pub struct SourceRootId(pub u32);
|
||||||
|
|
||||||
/// An absolute path in format `(/.+)*`
|
/// An absolute Unix-like path in the virtual filesystem.
|
||||||
/// Currently, it represent an absolute filesytem path.
|
///
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
/// It must be in form `(/[^/]+)+` and every segment must be non-empty and not `.` or `..`.
|
||||||
|
/// The root represented by an empty path.
|
||||||
|
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct VfsPath(String);
|
pub struct VfsPath(String);
|
||||||
|
|
||||||
impl VfsPath {
|
impl VfsPath {
|
||||||
pub fn root() -> Self {
|
pub fn root() -> Self {
|
||||||
Self(String::new())
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_path(p: &Path) -> Option<Self> {
|
/// Construct a new absolute path in Unix-like format.
|
||||||
Self::new(p.to_str()?)
|
pub fn new(s: impl Into<String>) -> Result<Self, ParseVfsPathError> {
|
||||||
|
let s = s.into();
|
||||||
|
if s == "/" {
|
||||||
|
return Ok(Self::root());
|
||||||
|
}
|
||||||
|
if !s.starts_with('/') || s[1..].split('/').any(|seg| matches!(seg, "" | "." | "..")) {
|
||||||
|
return Err(ParseVfsPathError);
|
||||||
|
}
|
||||||
|
Ok(Self(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(s: impl Into<String>) -> Option<Self> {
|
pub fn from_path(path: &Path) -> Result<Self, ParseVfsPathError> {
|
||||||
let mut s: String = s.into();
|
let mut ret = Self::root();
|
||||||
if s.is_empty() || s == "/" {
|
for comp in path.components() {
|
||||||
return Some(Self::root());
|
match comp {
|
||||||
|
Component::RootDir => {}
|
||||||
|
Component::Normal(seg) => {
|
||||||
|
ret.push_segment(seg.to_str().ok_or(ParseVfsPathError)?);
|
||||||
|
}
|
||||||
|
_ => return Err(ParseVfsPathError),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if s.ends_with('/') || s.as_bytes().windows(2).any(|w| w == b"//") {
|
Ok(ret)
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if !s.starts_with('/') {
|
|
||||||
s.insert(0, '/');
|
|
||||||
}
|
|
||||||
Some(Self(s))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push(&mut self, relative: &Self) {
|
/// Assume another VfsPath as relative and append it to this one.
|
||||||
|
pub fn append(&mut self, relative: &Self) {
|
||||||
self.0.push_str(&relative.0);
|
self.0.push_str(&relative.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_segment(&mut self, segment: &str) -> Option<()> {
|
/// Push a path segment at the end.
|
||||||
if !segment.contains('/') {
|
/// Panic if it is empty or contains `/`.
|
||||||
self.0 += "/";
|
pub fn push_segment(&mut self, segment: &str) {
|
||||||
self.0 += segment;
|
assert!(!segment.is_empty() && !segment.contains('/'));
|
||||||
Some(())
|
self.0 += "/";
|
||||||
} else {
|
self.0 += segment;
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pop the last segment from the end.
|
||||||
|
/// Returns `None` if it is already the root.
|
||||||
pub fn pop(&mut self) -> Option<()> {
|
pub fn pop(&mut self) -> Option<()> {
|
||||||
self.0.truncate(self.0.rsplit_once('/')?.0.len());
|
self.0.truncate(self.0.rsplit_once('/')?.0.len());
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the path in Unix-like form.
|
||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.0
|
if !self.0.is_empty() {
|
||||||
|
&self.0
|
||||||
|
} else {
|
||||||
|
"/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<String> for VfsPath {
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
type Error = ();
|
#[non_exhaustive]
|
||||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
pub struct ParseVfsPathError;
|
||||||
Self::new(s).ok_or(())
|
|
||||||
|
impl fmt::Display for ParseVfsPathError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("Invalid VfsPath")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for VfsPath {
|
impl std::error::Error for ParseVfsPathError {}
|
||||||
type Error = ();
|
|
||||||
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
|
|
||||||
Self::new(s).ok_or(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A set of [`VfsPath`]s identified by [`FileId`]s.
|
/// A set of [`VfsPath`]s identified by [`FileId`]s.
|
||||||
#[derive(Default, Clone, PartialEq, Eq)]
|
#[derive(Default, Clone, PartialEq, Eq)]
|
||||||
|
@ -28,7 +28,7 @@ impl Path {
|
|||||||
for _ in 0..(data.supers.saturating_add(1)) {
|
for _ in 0..(data.supers.saturating_add(1)) {
|
||||||
vpath.pop()?;
|
vpath.pop()?;
|
||||||
}
|
}
|
||||||
vpath.push(&data.relative);
|
vpath.append(&data.relative);
|
||||||
Some(vpath)
|
Some(vpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ impl PathData {
|
|||||||
.filter(|&seg| !seg.is_empty() && seg != ".")
|
.filter(|&seg| !seg.is_empty() && seg != ".")
|
||||||
{
|
{
|
||||||
if seg != ".." {
|
if seg != ".." {
|
||||||
relative.push_segment(seg).expect("Checked by lexer");
|
relative.push_segment(seg);
|
||||||
// Extra ".." has no effect for absolute path.
|
// Extra ".." has no effect for absolute path.
|
||||||
} else if relative.pop().is_none() && anchor != PathAnchor::Absolute {
|
} else if relative.pop().is_none() && anchor != PathAnchor::Absolute {
|
||||||
supers = supers.saturating_add(1);
|
supers = supers.saturating_add(1);
|
||||||
@ -91,12 +91,12 @@ mod tests {
|
|||||||
for anchor in [PathAnchor::Relative(FileId(0)), PathAnchor::Home, PathAnchor::Search("foo".into())] {
|
for anchor in [PathAnchor::Relative(FileId(0)), PathAnchor::Home, PathAnchor::Search("foo".into())] {
|
||||||
let norm = |s| PathData::normalize(anchor.clone(), s);
|
let norm = |s| PathData::normalize(anchor.clone(), s);
|
||||||
let path = |supers, p: &str| PathData { anchor: anchor.clone(), supers, relative: VfsPath::new(p).unwrap() };
|
let path = |supers, p: &str| PathData { anchor: anchor.clone(), supers, relative: VfsPath::new(p).unwrap() };
|
||||||
assert_eq!(norm(""), path(0, ""));
|
assert_eq!(norm(""), path(0, "/"));
|
||||||
assert_eq!(norm("./."), path(0, ""));
|
assert_eq!(norm("./."), path(0, "/"));
|
||||||
assert_eq!(norm("./.."), path(1, ""));
|
assert_eq!(norm("./.."), path(1, "/"));
|
||||||
assert_eq!(norm("../."), path(1, ""));
|
assert_eq!(norm("../."), path(1, "/"));
|
||||||
assert_eq!(norm("foo/./bar/../.baz"), path(0, "foo/.baz"));
|
assert_eq!(norm("foo/./bar/../.baz"), path(0, "/foo/.baz"));
|
||||||
assert_eq!(norm("../../foo"), path(2, "foo"));
|
assert_eq!(norm("../../foo"), path(2, "/foo"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,11 +106,11 @@ mod tests {
|
|||||||
let anchor = PathAnchor::Absolute;
|
let anchor = PathAnchor::Absolute;
|
||||||
let norm = |s| PathData::normalize(anchor.clone(), s);
|
let norm = |s| PathData::normalize(anchor.clone(), s);
|
||||||
let path = |p: &str| PathData { anchor: anchor.clone(), supers: 0, relative: VfsPath::new(p).unwrap() };
|
let path = |p: &str| PathData { anchor: anchor.clone(), supers: 0, relative: VfsPath::new(p).unwrap() };
|
||||||
assert_eq!(norm("/"), path(""));
|
assert_eq!(norm("/"), path("/"));
|
||||||
assert_eq!(norm("./."), path(""));
|
assert_eq!(norm("./."), path("/"));
|
||||||
assert_eq!(norm("./.."), path(""));
|
assert_eq!(norm("./.."), path("/"));
|
||||||
assert_eq!(norm("../."), path(""));
|
assert_eq!(norm("../."), path("/"));
|
||||||
assert_eq!(norm("foo/./bar/../.baz"), path("foo/.baz"));
|
assert_eq!(norm("foo/./bar/../.baz"), path("/foo/.baz"));
|
||||||
assert_eq!(norm("../../foo"), path("foo"));
|
assert_eq!(norm("../../foo"), path("/foo"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,7 +221,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn path() {
|
fn path() {
|
||||||
check("1 + $0./.", expect!["file://"]);
|
check("1 + $0./.", expect!["file:///"]);
|
||||||
check(
|
check(
|
||||||
"
|
"
|
||||||
#- /default.nix
|
#- /default.nix
|
||||||
|
@ -37,7 +37,7 @@ impl TestDB {
|
|||||||
change.change_file(file, text.to_owned().into());
|
change.change_file(file, text.to_owned().into());
|
||||||
}
|
}
|
||||||
let entry = file_set
|
let entry = file_set
|
||||||
.file_for_path(&"/default.nix".try_into().unwrap())
|
.file_for_path(&VfsPath::new("/default.nix").unwrap())
|
||||||
.context("Missing entry file")?;
|
.context("Missing entry file")?;
|
||||||
change.set_roots(vec![SourceRoot::new_local(file_set, Some(entry))]);
|
change.set_roots(vec![SourceRoot::new_local(file_set, Some(entry))]);
|
||||||
change.apply(&mut db);
|
change.apply(&mut db);
|
||||||
@ -101,7 +101,7 @@ impl Fixture {
|
|||||||
if let Some(path) = line.strip_prefix("#- ") {
|
if let Some(path) = line.strip_prefix("#- ") {
|
||||||
ensure!(!missing_header, "Missing path header at the first line");
|
ensure!(!missing_header, "Missing path header at the first line");
|
||||||
|
|
||||||
let path = VfsPath::new(path).context("Invalid path")?;
|
let path = VfsPath::new(path)?;
|
||||||
if let Some(prev_path) = cur_path.replace(path) {
|
if let Some(prev_path) = cur_path.replace(path) {
|
||||||
this.insert_file(prev_path, mem::take(&mut cur_text))?;
|
this.insert_file(prev_path, mem::take(&mut cur_text))?;
|
||||||
cur_file.0 += 1;
|
cur_file.0 += 1;
|
||||||
@ -109,7 +109,7 @@ impl Fixture {
|
|||||||
} else {
|
} else {
|
||||||
if cur_path.is_none() {
|
if cur_path.is_none() {
|
||||||
missing_header = true;
|
missing_header = true;
|
||||||
cur_path = Some("/default.nix".try_into().unwrap());
|
cur_path = Some(VfsPath::new("/default.nix").unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut iter = line.chars().peekable();
|
let mut iter = line.chars().peekable();
|
||||||
|
Loading…
Reference in New Issue
Block a user