Git Product home page Git Product logo

path_abs's Introduction

path_abs: ergonomic paths and files in rust.

Build Status Build status Docs

This library aims to provide ergonomic path and file operations to rust with reasonable performance.

See the library docs for more information

LICENSE

The source code in this repository is Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

path_abs's People

Contributors

alexanderkjall avatar bors[bot] avatar rreverser avatar screwtapello avatar vitiral avatar zebp avatar zilbuz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

path_abs's Issues

PathAbs constructor cleanup

Currently, there are a bunch of different ways to construct a PathAbs:

  • PathArc::absolute() constructs a PathAbs using lexical canonicalization
  • PathAbs::new() has a docstring that implies it uses std::fs::canonicalize() but actually calls PathArc::absolute().
  • PathAbs::mock() constructs a PathAbs but doesn't actually validate its absoluteness

I think these could be cleaned up/rationalized. For example:

  • PathAbs::new() should take a path and validate that it conforms to the rules for absolute paths, returning an error if something's wrong
    • Normally new() is for the most commonly-used constructor, but I'm not sure any of them are obviously the best
    • "validate in new()" follows standard library types like std::ptr::NonNull
  • PathAbs::new_with_canonicalization() should take a path and call std::fs::canonicalize() on it
    • Not currently a supported constructor, but if it's in the standard library people will be surprised if this crate doesn't support it at all
  • PathAbs::new_with_parents_removed() should resolve .. components by removing the previous component, like PathArc::absolute() does today
  • Delete PathAbs::mock()
    • somebody who wants a path for testing can use the tempfile crate
    • if this method is really needed, we can rename it to PathAbs::new_unchecked() after the model of std::ptr::NonNull
  • Delete PathArc::absolute()
    • PathArc is kind of a behind-the-scenes type (see #18) and doesn't need any more attention drawn to it
    • people who want to turn a PathArc into a PathAbs can use one of the direct constructors

This would also provide a natural place for PathAbs::new_with_parents_resolved() to slot in, from #20.

Moving/copying/removing a symbolic link

(I wish GitHub provided a better way to have discussion other than opening a bug report)

Let's talk symlinks.

I have an application which has historically taken the easy way out of path management by changing the current directory. In moving away from this setup towards a more principled design, I considered using path_abs as a means of leveraging the type system to ensure that I don't accidentally leave behind any lingering relative paths.

Reviewing the documentation, however, it is evident to me that my mental model of symlinks differs from that of the library. Of course, this is to be expected; I understand that PathAbs is opinionated, and that our ideas will inevitably clash somewhere.

But with regard to symlinks specifically, I find the model presented by this crate to be dangerous, and quite scary! I'd like to hear your point of view on this, and I wonder if there is something I am missing.


I'll describe these clashing models by making analogy to Rust's borrow semantics.

To me:

  • A directory in the filesystem is like some Box<_> type. It has a unique owner (its "true" parent) and that's the only way you can move or delete it.
  • A file in the filesystem is like some Arc<_> type. It may have multiple owners via hard links, and dies when the last link vanishes.
  • A symlink is like some *mut _ type. It can change data belonging to its target, but only if you explicitly dereference it (by joining another path component). The pointer itself does not own its target, and its target might not even exist.

In PathAbs:

  • by immediately resolving symlinks, PathAbs makes symlinks feel more like C++'s mutable references. A symlink is the directory or file it points to. To me, this is terrifying, because if I'm not careful, then code which was intended to delete a symlink could end up accidentally recursively deleting its target!

What do you think of this? Do you disagree with this risk about deleting a symlink? Is there room in PathAbs for symlinks to live as first class citizens? Am I asking too many questions? ๐Ÿ˜›

Help in handling weird join behavior

@soc left a comment here and I wrote a quick script to demonstrate the problem:

Joining: a with b
Result : a/b
Joining: a/ with b
Result : a/b
Joining: /a with b
Result : /a/b
Joining: a with /b
Result : /b
Joining: /a with /b
Result : /b

This is indeed bizare and an issue I think path_abs might be able to help with.

The issue with "joining not compiling" is that the API for most path operations takes in AsRef<Path>. I think you would agree that both RelativePath and AbsolutePath should be AsRef<Path>, right? This makes the issue harder as I'm not sure of a way to disallow a certain type in rust (please let me know if there is a way).

Types probably shouldn't implement the `Deref` trait.

The Rust API guidelines say:

The Deref traits are used implicitly by the compiler in many circumstances, and interact with method resolution. The relevant rules are designed specifically to accommodate smart pointers, and so the traits should be used only for that purpose.

If I understand correctly, path_abs uses Deref to put its various path types into an inheritance hierarchy, but "Deref-as-inheritance" is often considered an anti-pattern.

When I was working on PR#27, in the code that builds up a path I instinctively reached for .push() since I expected to make a lot of changes. When I wrote something like this:

let mut res = PathAbs::new(path);
res.push(something);

...I got an error on the second line saying something like "cannot borrow immutable reference as mutable" and a warning on the first line saying "variable is marked mut but doesn't need to be", a combination that confused me for some time. Eventually I figured out that the .push() method was visible because of the Deref trait, but couldn't be used because of the lack of DerefMut. Since we can't really implement DerefMut because of Arc, we should probably drop Deref.

Once I'd figured it out, I switched to:

let mut res = PathAbs::new(path);
res = res.join(something);

...but that failed too. In this situation, .join() is inherited from PathArc, so it returns a PathArc instead of a PathAbs, and what I really need is:

let mut res = PathAbs::new(path);
res = PathAbs(res.join(something));

...which I would have found more quickly if Deref hadn't given me a .join() that didn't quite work the way I wanted.

File* Constructor cleanup

Before the next version I want to cleanup the constructors.

  • open_* instead of new or just read. I.e. open_read, open_append, etc. I think I'm going to keep these methods on PathFile, but rename accordingly.
  • open_abs instead of open_path.

Making Stdio from {FileRead, FileWrite}

I don't see an easy way to use FileRead and FileWrite in std::process::Command::{stdout, stderr, stdin} as there's no way to get the File and they don't impl Into<Stdio>

add wasm32-wasi target

It would be great if we could target WASI. Currently:

Camerons-MacBook-Pro:path_abs cameron$ cargo build --target wasm32-wasi
    Updating crates.io index
   Compiling proc-macro2 v1.0.21
   Compiling memchr v2.3.3
   Compiling unicode-xid v0.2.1
   Compiling lazy_static v1.4.0
   Compiling syn v1.0.41
   Compiling serde_derive v1.0.116
   Compiling regex-syntax v0.6.18
   Compiling serde v1.0.116
   Compiling std_prelude v0.2.12
   Compiling thread_local v1.0.1
   Compiling aho-corasick v0.7.13
   Compiling quote v1.0.7
   Compiling regex v1.3.9
   Compiling stfu8 v0.2.4
   Compiling path_abs v0.5.0 (/Users/cameron/rs/path_abs)
error[E0425]: cannot find function `symlink_dir` in this scope
   --> src/dir.rs:308:9
    |
308 |         symlink_dir(&self, &dst).map_err(|err| {
    |         ^^^^^^^^^^^-------------
    |         |
    |         help: try calling `symlink_dir` as a method: `self.symlink_dir(&dst)`

error[E0425]: cannot find function `symlink_file` in this scope
   --> src/file.rs:413:9
    |
413 |         symlink_file(&self, &dst).map_err(|err| {
    |         ^^^^^^^^^^^^-------------
    |         |
    |         help: try calling `symlink_file` as a method: `self.symlink_file(&dst)`

warning: unused import: `OsString`
  --> src/ser.rs:16:23
   |
16 | use std::ffi::{OsStr, OsString};
   |                       ^^^^^^^^
   |
   = note: `#[warn(unused_imports)]` on by default

error[E0046]: not all trait items implemented, missing: `to_stfu8`
   --> src/ser.rs:150:1
    |
28  |       fn to_stfu8(&self) -> String;
    |       ----------------------------- `to_stfu8` from trait
...
150 | / impl<T> ToStfu8 for T
151 | | where
152 | |     T: Borrow<PathBuf>,
153 | | {
...   |
164 | |     }
165 | | }
    | |_^ missing `to_stfu8` in implementation

error[E0046]: not all trait items implemented, missing: `from_stfu8`
   --> src/ser.rs:167:1
    |
32  |       fn from_stfu8(s: &str) -> Result<Self, stfu8::DecodeError>;
    |       ----------------------------------------------------------- `from_stfu8` from trait
...
167 | / impl<T> FromStfu8 for T
168 | | where
169 | |     T: From<PathBuf>,
170 | | {
...   |
185 | |     }
186 | | }
    | |_^ missing `from_stfu8` in implementation

warning: use of deprecated associated function `std::error::Error::description`: use the Display impl or to_string()
   --> src/lib.rs:338:21
    |
338 |         self.io_err.description()
    |                     ^^^^^^^^^^^
    |
    = note: `#[warn(deprecated)]` on by default

error: aborting due to 4 previous errors; 2 warnings emitted

Some errors have detailed explanations: E0046, E0425.
For more information about an error, try `rustc --explain E0046`.
error: could not compile `path_abs`

To learn more, run the command again with --verbose.

Internationalization (i18n) support

There's two aspects of internationalization relevant to path_abs:

  1. It should be possible for path_abs to present its own error messages (and other human-readable text) in the user's language, instead of hard-coding specific English messages.
  2. path_abs should not depend on the exact text of error messages from the underlying operating system, since they can vary.

For path_abs' own error messages, the biggest offender is probably the Error::action() method which returns a string. Possibly this could be replaced with an enum, patterned after std::io::ErrorKind. There may be other issues; the code should probably be audited.

For errors from the underlying operating system, currently the only dependency I'm aware of is the test "sanity_errors", which could be amended by checking error fields individually rather than formatting the error and checking the resulting string. Again, there may be other issues and the code should probably be audited.

Notes from a new user

I was pointed to this crate from the comments on a Reddit post I made, and thought I'd take notes about things that confused me while reading the documentation.

Errors always include filenames? That's awesome!

In the list of types introduced in this crate, PathArc is first and therefore presumably most important, but it's not actually used anywhere in the examples? So maybe it's just an implementation detail I should ignore?

In the crate-level docs, PathAbs says "An absolute (not necessarily canonicalized) path that may or may not exist" but the documentation for PathAbs::new() says "The path must exist or io::Error will be returned."? Must the path exist, or not?

I'm interested in working with paths that may or may not exist, but the example at the top of the file only creates these structs with ::create(), creating them on disk at the same time, so it doesn't particularly help me.

I see PathArc has an absolute() method, which does the broken thing of resolving /../ segments semantically, which means it's almost never safe to use. It's a shame; the other parts of its functionality are pretty useful, and there's other useful cleanups it could perform (like removing empty // path segments, and normalizing delimiters to / or \ depending on platform), but while there's a chance that it will corrupt a path I'd rather not use it.

The specific functionality I was looking for in my Reddit post, and which I can't seem to find in your crate, was partial canonicalization. Instead of Rust's canonicalize which requires the the entire path exist, a method that requires all-but-the-last-segment exists, or even a method that silently switches from real canonicalization to semantic canonicalization at the first segment that does not exist.

Inherent methods for PathOps and PathInfo

It feels extremely silly to have to import traits to get methods that could sensibly be provided on the types themselves, especially traits that have so many methods (PathInfo), and especially when I have no intention to use the traits generically. (I already have my own trait with a method named join, and importing yours would cause a conflict)

Most notably:

  • {PathAbs,PathDir}::{join,concat}
  • {PathAbs,PathFile}::{file_name,with_file_name,with_extension} (and maybe stuff for PathDir; though the term base_name might be cause less confusion than file_name)
  • PathAbs::{exists,is_file,is_dir,parent...}... in fact, nearly all of the Path methods are useful for PathAbs, making it really hard for me to see why you removed impl Deref for PathAbs. Maybe Target=PathArc was too much, but isn't at least Target=Path reasonable?

PartialOrd and PartialEq traits

I have started to play a bit your crate in my application (RnR) because I had some problems with canonicalize() resolving symlinks. It seems that path_abs solves some of them.

I was wondering if you are planning to support PartialOrd and PartialEq traits in path_abs structs since they are already implemented on Path and PathBuf. I can think in several use cases for that.

Nice job btw!

Add `join` back

@Screwtapello I noticed we have removed join. While I agree that I'm not the biggest fan of this method, I think it should still be in this library since:

  • It will be pure surprise to find such a standard method missing
  • It will make refactoring with/without path-abs more difficult.
  • It breaks API backwards compatibility

I'm willing to suffer the (minor) pain points to lessen these two issues.

What should happen when canonicalizing `..` at the root?

Currently, on POSIX:

let maybe_path = PathArc::new("/foo/../..").absolute();
println!("{:?}", maybe_path);
// -> Err(Error<resolving resulted in empty path when resolving absolute /foo/../..>)

Meanwhile, on Windows:

let maybe_path = PathArc::new(r"C:\foo\..\..").absolute();
println!("{:?}", maybe_path);
// -> Ok("\\\\?\\C:")

...which says "Ok" but is actually an invalid path (since it has an "extended path syntax" prefix component, but no root component). Adding more parent components (C:\foo\..\..\..\..\) produces the same result, but no error.

Meanwhile, on both POSIX and Windows, typing cd .. in a root directory does nothing.

I'm guessing that the current platform-specific behaviour is a bug, but if we have to pick a behaviour for all platforms, should it be "error", "silently clamp", or something else?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.