TruePath icon indicating copy to clipboard operation
TruePath copied to clipboard

#18 implement behavior to calculate parents from exclusively-relative paths

Open Kataane opened this issue 1 year ago • 1 comments

Closes #18.

Pull Request: Define Parent for LocalPath(".") and Relative Paths

Problem Description: Currently, new LocalPath(".").Parent (similarly for paths like .. or ../..) is not defined. This creates ambiguity and inconsistency in the behavior of the library.

Proposed Solution: After researching how other libraries handle the parent of paths such as ".", "..", and "../..", we propose that these should return null. This approach aligns with the general expectations and behavior seen in other path-handling libraries.

Changes Made:

  • Implemented logic to return null for the parent of LocalPath("."), LocalPath(".."), and other relative paths.
  • Added corresponding unit tests to ensure consistent behavior and to prevent regressions in the future. These tests cover the mentioned cases as well as any arbitrary exclusively-relative path.

Rationale: It's important to note that the decision to return null is debatable. Here are some examples of how different languages handle these cases:

Python:

from pathlib import Path
import os

# Pathlib
path = Path(".")
print(f"For the . parent is {path.parent}")  # For the . parent is .
path = Path("..")
print(f"For the .. parent is {path.parent}")  # For the .. parent is .
path = Path("../..")
print(f"For the ../.. parent is {path.parent}")  # For the ../.. parent is ..

# os.path
path = os.path.dirname(".")
print(f"For the . parent is {path}")  # For the . parent is 
path = os.path.dirname("..")
print(f"For the .. parent is {path}")  # For the .. parent is 
path = os.path.dirname("../..")
print(f"For the ../.. parent is {path}")  # For the ../.. parent is ..

Go:

package main

import (
    "fmt"
    "path"
)

func main() {
    // .
    // .
    // ..
    parent := path.Dir(".")
    fmt.Println(parent)
    parent2 := path.Dir("..")
    fmt.Println(parent2)
    parent3 := path.Dir("../..")
    fmt.Println(parent3)
}

Java:

import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) {
        // Parent of current directory: null
        // Parent of parent directory: null
        // Parent directory of the file: ..
        Path currentDir = Paths.get(".");
        Path parentDir = currentDir.getParent();
        System.out.println("Parent of current directory: " + parentDir);

        Path parentPath = Paths.get("..");
        Path parentOfParent = parentPath.getParent();
        System.out.println("Parent of parent directory: " + parentOfParent);

        Path filePath = Paths.get("../..");
        Path fileParent = filePath.getParent();
        System.out.println("Parent directory of the file: " + fileParent);
    }
}

C#:

using System;
using System.IO;

public class Program
{
    public static void Main()
    {
        // All returns the current working folder

        DirectoryInfo dirInfo = new DirectoryInfo(".");
        var parentDir = dirInfo.Parent;
        Console.WriteLine(parentDir);

        DirectoryInfo dirInfo2 = new DirectoryInfo("..");
        var parentDir2 = dirInfo2.Parent;
        Console.WriteLine(parentDir2);

        DirectoryInfo dirInfo3 = new DirectoryInfo("../..");
        var parentDir3 = dirInfo3.Parent;
        Console.WriteLine(parentDir3);
    }
}

Rust:

use std::path::Path;

fn main() {
    let path = Path::new(".");
    println!("{:?}", path.parent()); // Some("")

    let path = Path::new("..");
    println!("{:?}", path.parent()); // Some("")

    let path = Path::new("../..");
    println!("{:?}", path.parent()); // Some("..")
}

If you look behind the trends, you can see that modern, trendy languages try to reclaim the solved path or treat it fairly with this kind of behaviour.

Bottom line, I definitely can't claim that returning null is a good idea

Kataane avatar Jul 20 '24 13:07 Kataane

Thanks for a thorough investigation! I agree that returning null sounds more sound than whatever some other libraries do.

ForNeVeR avatar Aug 11 '24 20:08 ForNeVeR

After some thought, I decided that we should go with the following invariant: calling Parent should effectively be the same as adding to a path .. (and re-normalizing it afterwards): meaning that .Parent should be defined for any relative path.

This will create some nice properties: say, (absolutePath / relativePath).Parent will be (almost always, except very weird cases[^1]) the same as absolutePath / relativePath.Parent.

I'll work on updating this PR.

[^1]: My eventual plan is to handle the weird cases as well, thus making these two examples to behave exactly the same always. I'll open an issue on the details of this later.

ForNeVeR avatar Sep 22 '24 16:09 ForNeVeR