Skip to content

Instantly share code, notes, and snippets.

@ExpHP
Created November 8, 2017 02:59
Show Gist options
  • Save ExpHP/3f7d8c03be1a45ebe5abd3ad5a517d73 to your computer and use it in GitHub Desktop.
Save ExpHP/3f7d8c03be1a45ebe5abd3ad5a517d73 to your computer and use it in GitHub Desktop.
path normalization comparison
import os
import sys
alphabet = sys.argv[1]
max_depth = int(sys.argv[2])
def gen_strings(alphabet, s, depth):
yield s
if depth > 0:
for ch in alphabet:
yield from gen_strings(alphabet, s + ch, depth - 1)
for s in gen_strings(alphabet, '', max_depth):
print("{:>15} -> {:>15}".format(s, os.path.normpath(s)))
use std::path::Path;
use std::path::PathBuf;
use std::path::Component;
// https://github.com/rust-lang/rfcs/issues/2208#issuecomment-342679694
fn normalize(p: &Path) -> PathBuf {
let mut stack: Vec<Component> = vec![];
// We assume .components() removes redundant consecutive path separators.
// Note that .components() also does some normalization of '.' on its own anyways.
// This '.' normalization happens to be compatible with the approach below.
for component in p.components() {
match component {
// Drop CurDir components, do not even push onto the stack.
Component::CurDir => {},
// For ParentDir components, we need to use the contents of the stack.
Component::ParentDir => {
// Look at the top element of stack, if any.
let top = stack.last().cloned();
match top {
// A component is on the stack, need more pattern matching.
Some(c) => {
match c {
// Push the ParentDir on the stack.
Component::Prefix(_) => { stack.push(component); },
// The parent of a RootDir is itself, so drop the ParentDir (no-op).
Component::RootDir => {},
// A CurDir should never be found on the stack, since they are dropped when seen.
Component::CurDir => { panic!("Found an unexpected Component::CurDir"); },
// If a ParentDir is found, it must be due to it piling up at the start of a path.
// Push the new ParentDir onto the stack.
Component::ParentDir => { stack.push(component); },
// If a Normal is found, pop it off.
Component::Normal(_) => { let _ = stack.pop(); }
}
},
// Stack is empty, so path is empty, just push.
None => { stack.push(component); }
}
},
// All others, simply push onto the stack.
_ => { stack.push(component); },
}
}
// If an empty PathBuf would be return, instead return CurDir ('.').
if stack.is_empty() {
return PathBuf::from(Component::CurDir.as_ref());
}
let mut norm_path = PathBuf::new();
for item in &stack {
norm_path.push(item.as_ref());
}
norm_path
}
fn gen_strings(alphabet: &[String], s: String, depth: u8, f: fn(&str)) {
f(&s);
if let Some(deeper) = depth.checked_sub(1) {
for ch in alphabet {
gen_strings(alphabet, s.clone() + ch, deeper, f);
}
}
}
fn main() {
let mut args = ::std::env::args();
let _ = args.next();
let alphabet = args.next().unwrap().chars().map(|ch| ch.to_string()).collect::<Vec<_>>();
let max_depth = args.next().unwrap().parse().unwrap();
gen_strings(&alphabet[..], Default::default(), max_depth, |s| {
let p = Path::new(&s);
println!("{:>15} -> {:>15}", p.display(), normalize(&p).display());
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment