1

My input is a flat list of filesystem paths that are all sub-directories (or file therein) of a single top-level directory.

My ultimate output should be:

  1. A textual hierarchical display of the paths, like that of unix tree command.
  2. A hierarchical JSON serialization of the paths with matching logical structure to (1)

I have created an intermediate data structure, which is a self-referencing struct Dir that has a name and a vector of Box'ed child struct Dir.

I can successfully use Dir to represent an arbitrary directory tree as shown in the output below.

I was thinking to use a stack to process the list, adding a Dir for each sub-directory and popping when ascending, but I can't seem to make it work with Rust the way it would with C or other languages. I get compiler errors no matter what I try.

How can I turn the flat list into a Dir and keep the compiler happy? Or alternatively, how to achieve (1) and (2) in a different way?

Code:

// A type to represent a path, split into its component parts
#[derive(Debug)]
struct Path {
    parts: Vec<String>,
}
impl Path {
    pub fn new(path: &str) -> Path {
        Path {
            parts: path.to_string().split("/").map(|s| s.to_string()).collect(),
        }
    }
}

// A recursive type to represent a directory tree.
// Simplification: If it has children, it is considered
// a directory, else considered a file.
#[derive(Debug)]
struct Dir {
    name: String,
    children: Vec<Box<Dir>>,
}

impl Dir {
    fn new(name: &str) -> Dir {
        Dir {
            name: name.to_string(),
            children: Vec::<Box<Dir>>::new(),
        }
    }

    fn has_child(&self, name: &str) -> bool {
        for c in self.children.iter() {
            if c.name == name {
                return true;
            }
        }
        false
    }

    fn add_child<T>(mut self, leaf: T) -> Self
    where
        T: Into<Dir>,
    {
        self.children.push(Box::new(leaf.into()));
        self
    }
}

fn dir(val: &str) -> Dir {
    Dir::new(val)
}

fn main() {
    // Form our INPUT:  a list of paths.
    let paths = vec![
        Path::new("root/child1/grandchild1.txt"),
        Path::new("root/child1/grandchild2.json"),
        Path::new("root/child2/grandchild3.pdf"),
        Path::new("root/child3"),
    ];
    println!("Input Paths:\n{:#?}\n", paths);

    // Transformation:
    // I need an algorithm here that converts the list of paths
    // above to a recursive struct (tree) below.
    // ie: paths --> dir
    let top = dir("root");
    let mut cwd = &top;
    for p in paths.iter() {
        for part in p.parts.iter() {
            if !cwd.has_child(part) {
                // cwd.add_child(dir(part));
                // cwd = &cwd.children[cwd.children.len() - 1];
            }
        }
    }

    // Intermediate Representation:
    // The above transformation should result in the following
    // hierarchical structure.
    let top = dir("root")
        .add_child(
            dir("child1")
                .add_child(dir("grandchild1.txt"))
                .add_child(dir("grandchild2.json")),
        )
        .add_child(dir("child2").add_child(dir("grandchild3.pdf")))
        .add_child(dir("child3"));
    println!("Intermediate Representation of Dirs:\n{:#?}\n\nOutput Tree Format:\n", top);

    // Output:  textual `tree` format
    print_dir(&top, 0);
}

// A function to print a Dir in format similar to unix `tree` command.
fn print_dir(dir: &Dir, depth: u32) {
    if depth == 0 {
        println!("{}", dir.name);
    } else {
        println!(
            "{:indent$}{} {}",
            "",
            "└──",
            dir.name,
            indent = ((depth as usize) - 1) * 4
        );
    }

    for child in dir.children.iter() {
        print_dir(child, depth + 1)
    }
}

Output:

$ ./target/debug/rust-tree                  
Input Paths:
[
    Path {
        parts: [
            "root",
            "child1",
            "grandchild1.txt",
        ],
    },
    Path {
        parts: [
            "root",
            "child1",
            "grandchild2.json",
        ],
    },
    Path {
        parts: [
            "root",
            "child2",
            "grandchild3.pdf",
        ],
    },
    Path {
        parts: [
            "root",
            "child3",
        ],
    },
]

Intermediate Representation of Dirs:
Dir {
    name: "root",
    children: [
        Dir {
            name: "child1",
            children: [
                Dir {
                    name: "grandchild1.txt",
                    children: [],
                },
                Dir {
                    name: "grandchild2.json",
                    children: [],
                },
            ],
        },
        Dir {
            name: "child2",
            children: [
                Dir {
                    name: "grandchild3.pdf",
                    children: [],
                },
            ],
        },
        Dir {
            name: "child3",
            children: [],
        },
    ],
}

Output Tree Format:

root
└── child1
    └── grandchild1.txt
    └── grandchild2.json
└── child2
    └── grandchild3.pdf
└── child3
Shepmaster
  • 274,917
  • 47
  • 731
  • 969
danda
  • 153
  • 7
  • It looks like your question might be answered by the answers of [Cannot move out of borrowed content / cannot move out of behind a shared reference](https://stackoverflow.com/q/28158738/155423). If not, please **[edit]** your question to explain the differences. Otherwise, we can mark this question as already answered. – Shepmaster Mar 01 '20 at 20:12
  • 2
    It's worth pointing out that strings are *not* a good way of representing paths. This is why Rust has the [`Path`](https://doc.rust-lang.org/std/path/struct.Path.html) type and friends. – Shepmaster Mar 01 '20 at 20:15
  • thx but representing the Path is not what I'm asking about. Converting a list of paths into a hierarchical structure is the object. – danda Mar 01 '20 at 22:58
  • I read the link you provided, but it does not resolve the issue. I already tried various things with &self to no avail -- and using clone() is pointless I think. So if you mark this question as answered, I will be no closer to a solution than when I asked it. I have googled all around and I do not find any example of someone doing what I'm trying to do, it must be possible in rust, but is non-obvious to someone learning the language like myself. If that link is relevant, perhaps you can be so good as to modify the code I provided and demonstrate how exactly it solves it. – danda Mar 01 '20 at 23:03
  • [The duplicate applied to your situation](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=22cf5215738ce982c6417c6e8df4dc58). See also [What does “cannot borrow as immutable because it is also borrowed as mutable” mean in an nested array index?](https://stackoverflow.com/q/59145661/155423); [“borrowed value does not live long enough” when using the builder pattern](https://stackoverflow.com/q/28469667/155423). – Shepmaster Mar 02 '20 at 14:05
  • well, that's nice thank-you. However, I still do not have the desired transformation. The second I try to add a stack, it blows up again. eg: let stack = Vec::::new(); ... stack.push(cwd); – danda Mar 02 '20 at 18:00
  • In case it helps someone else, I was finally able to perform the transformation using a recursive function and mutable iterator. [Code is here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8d635f7ab9d2db67ced09df7c6f8195d). – danda Mar 02 '20 at 21:34

1 Answers1

2

Since someone deleted my previous answer with a link to the working code, I will post the full working code here.

// A type to represent a path, split into its component parts
#[derive(Debug)]
struct Path {
    parts: Vec<String>,
}
impl Path {
    pub fn new(path: &str) -> Path {
        Path {
            parts: path.to_string().split("/").map(|s| s.to_string()).collect(),
        }
    }
}

// A recursive type to represent a directory tree.
// Simplification: If it has children, it is considered
// a directory, else considered a file.
#[derive(Debug, Clone)]
struct Dir {
    name: String,
    children: Vec<Box<Dir>>,
}

impl Dir {
    fn new(name: &str) -> Dir {
        Dir {
            name: name.to_string(),
            children: Vec::<Box<Dir>>::new(),
        }
    }

    fn find_child(&mut self, name: &str) -> Option<&mut Dir> {
        for c in self.children.iter_mut() {
            if c.name == name {
                return Some(c);
            }
        }
        None
    }

    fn add_child<T>(&mut self, leaf: T) -> &mut Self
    where
        T: Into<Dir>,
    {
        self.children.push(Box::new(leaf.into()));
        self
    }
}

fn dir(val: &str) -> Dir {
    Dir::new(val)
}

fn main() {
    // Form our INPUT:  a list of paths.
    let paths = vec![
        Path::new("child1/grandchild1.txt"),
        Path::new("child1/grandchild2.json"),
        Path::new("child2/grandchild3.pdf"),
        Path::new("child3"),
    ];
    println!("Input Paths:\n{:#?}\n", paths);

    // Transformation:
    // A recursive algorithm that converts the list of paths
    // above to Dir (tree) below.
    // ie: paths --> dir
    let mut top = dir("root");
    for path in paths.iter() {
        build_tree(&mut top, &path.parts, 0);
    }

    println!(
        "Intermediate Representation of Dirs:\n{:#?}\n\nOutput Tree Format:\n",
        top
    );

    // Output:  textual `tree` format
    print_dir(&top, 0);
}

fn build_tree(node: &mut Dir, parts: &Vec<String>, depth: usize) {
    if depth < parts.len() {
        let item = &parts[depth];

        let mut dir = match node.find_child(&item) {
            Some(d) => d,
            None => {
                let d = Dir::new(&item);
                node.add_child(d);
                match node.find_child(&item) {
                    Some(d2) => d2,
                    None => panic!("Got here!"),
                }
            }
        };
        build_tree(&mut dir, parts, depth + 1);
    }
}

// A function to print a Dir in format similar to unix `tree` command.
fn print_dir(dir: &Dir, depth: u32) {
    if depth == 0 {
        println!("{}", dir.name);
    } else {
        println!(
            "{:indent$}{} {}",
            "",
            "└──",
            dir.name,
            indent = ((depth as usize) - 1) * 4
        );
    }

    for child in dir.children.iter() {
        print_dir(child, depth + 1)
    }
}
danda
  • 153
  • 7