2017-08-24 7 views
-2

L'objectif est donc d'écrire une fonction qui obtient deux chemins, input_dir et output_dir, et convertit tous les fichiers de démarques de input_dir en fichiers html en output_dir.J'ai toujours besoin d'envelopper ma tête autour de Rouille. Comment puis-je rendre ce code moins répétitif?

J'ai finalement réussi à le faire fonctionner mais c'était plutôt frustrant. Les parties qui devraient être difficiles sont super faciles: la conversion réelle de Markdown en HTML est effectivement une seule ligne. Les parties apparemment faciles sont ce qui m'a pris le plus longtemps. Utiliser un vecteur de chemins et y mettre tous les fichiers est quelque chose que j'ai remplacé par la caisse glob. Non pas parce que je ne pouvais pas le faire fonctionner, mais c'était un gâchis de if let et unwrap. Une fonction simple qui itère sur la liste des éléments et qui détermine quels sont réellement des fichiers et non des répertoires? Soit j'ai besoin de quatre niveaux d'indentation si if let ou je panique sur match es.

Qu'est-ce que je fais mal?

Mais laisse commencer par quelques choses que j'ai essayé d'obtenir une liste d'éléments dans un répertoire filtré pour contenir uniquement les fichiers réels:

use std::fs; 
use std::vec::Vec; 


fn list_files (path: &str) -> Result<Vec<&str>, &str> { 
    if let Ok(dir_list) = fs::read_dir(path) { 
     Ok(dir_list.filter_map(|e| { 
      match e { 
       Ok(entry) => match entry.file_type() { 
        Ok(_) => entry.file_name().to_str(), 
        _ => None 
       }, 
       _ => None 
      } 
     }).collect()) 
    } else { 
     Err("nope") 
    } 
} 


fn main() { 
    let files = list_files("testdir"); 
    println!("{:?}", files.unwrap_or(Vec::new())); 
} 

Ainsi, ce code ne construit pas, parce que le nom du fichier en ligne 10 ne vit pas assez longtemps. Je suppose que je pourrais en quelque sorte créer un String appartenant, mais cela introduirait un autre niveau d'imbrication, car OsStr.to_string() renvoie un Result.

Maintenant, je regardé à travers le code de la caisse glob et ils utilisent simplement un vecteur mutable:

fn list_files (path: &str) -> Result<Vec<&str>, &str> { 
    let mut list = Vec::new(); 

    if let Ok(dir_list) = fs::read_dir(path) { 
     for entry in dir_list { 
      if let Ok(entry) = entry { 
       if let Ok(file_type) = entry.file_type() { 
        if file_type.is_file() { 
         if let Some(name) = entry.file_name().to_str() { 
          list.push(name) 
         } 
        } 
       } 
      } 
     } 

     Ok(list) 
    } else { 
     Err("nope") 
    } 
} 

Cela ajoute non seulement l'imbrication fou, il échoue aussi avec le même problème. Si je change Vec<&str>-Vec<String>, cela fonctionne:

fn list_files (path: &str) -> Result<Vec<String>, &str> { 
    let mut list = Vec::new(); 

    if let Ok(dir_list) = fs::read_dir(path) { 
     for entry in dir_list { 
      if let Ok(entry) = entry { 
       if let Ok(file_type) = entry.file_type() { 
        if file_type.is_file() { 
         if let Ok(name) = entry.file_name().into_string() { 
          list.push(name) 
         } 
        } 
       } 
      } 
     } 

     Ok(list) 
    } else { 
     Err("nope") 
    } 
} 

On dirait que je l'appliquer à mon premier essai, non?

fn list_files (path: &str) -> Result<Vec<String>, &str> { 
    if let Ok(dir_list) = fs::read_dir(path) { 
     Ok(dir_list.filter_map(|e| { 
      match e { 
       Ok(entry) => match entry.file_type() { 
        Ok(_) => Some(entry.file_name().into_string().ok()), 
        _ => None 
       }, 
       _ => None 
      } 
     }).collect()) 
    } else { 
     Err("nope") 
    } 
} 

au moins un peu plus court ... mais il ne parvient pas à compiler parce une collection de type std::vec::Vec<std::string::String> ne peut pas être construit à partir d'un itérateur sur des éléments de type std::option::Option<std::string::String>.

Il est difficile de rester patient ici. Pourquoi .filter_map renvoie Option s au lieu de simplement les utiliser pour filtrer? Maintenant, je dois changer la ligne 15 de }).collect()) à }).map(|e| e.unwrap()).collect()) qui itère une fois de plus sur le jeu de résultats.

Cela ne peut pas être vrai! Qu'est-ce qui me manque ici?

+0

'ok()' retourne 'Option' et vous l'enroulez ensuite dans' Some'. Vous vous retrouvez avec 'Option >'. Supprimez 'Some (...)' de 'Some (entry.file_name(). Into_string(). Ok())'. Ce n'est pas une réponse complète, mais au moins cela vous permettra d'y aller. – red75prime

+0

Si votre code fonctionne (j'ai du mal à le dire en lisant votre question), alors demander un moyen de mieux l'écrire est [mieux adapté à l'examen du code] (https://codereview.meta.stackexchange.com/q/ 5777/32521). – Shepmaster

+0

Merci @Shepmaster, mais ma question n'était pas tellement sur le morceau de code, je l'ai seulement écrit comme un exemple. Au lieu de cela, j'ai voulu voir quel est mon problème général qui fait que ce code est si fou imbriqué. – koehr

Répondre

1

Vous pouvez massivement compter sur ? operator:

use std::fs; 
use std::io::{Error, ErrorKind}; 

fn list_files(path: &str) -> Result<Vec<String>, Error> { 
    let mut list = Vec::new(); 

    for entry in fs::read_dir(path)? { 
     let entry = entry?; 
     if entry.file_type()?.is_file() { 
      list.push(entry.file_name().into_string().map_err(|_| { 
       Error::new(ErrorKind::InvalidData, "Cannot convert file name") 
      })?) 
     } 
    } 

    Ok(list) 
} 

N'oubliez pas que vous pouvez diviser votre code en fonctions ou mettre en œuvre vos propres trait s pour simplifier le code final:

use std::fs; 
use std::io::{Error, ErrorKind}; 

trait CustomGetFileName { 
    fn get_file_name(self) -> Result<String, Error>; 
} 

impl CustomGetFileName for std::fs::DirEntry { 
    fn get_file_name(self) -> Result<String, Error> { 
     Ok(self.file_name().into_string().map_err(|_| 
      Error::new(ErrorKind::InvalidData, "Cannot convert file name") 
     )?) 
    } 
} 

fn list_files(path: &str) -> Result<Vec<String>, Error> { 
    let mut list = Vec::new(); 

    for entry in fs::read_dir(path)? { 
     let entry = entry?; 
     if entry.file_type()?.is_file() { 
      list.push(entry.get_file_name()?) 
     } 
    } 

    Ok(list) 
} 
+0

Merci beaucoup @Boiethios! J'ai vu cet opérateur plus tôt mais chaque fois que je voulais l'utiliser, ça n'a pas marché pour moi. – koehr

+0

Peut-être parce que c'était dans la version nocturne seulement, avant. – Boiethios

+0

Pourriez-vous me diriger vers une documentation pour cet opérateur? – koehr

1

Une alternative répondre par itérateurs, playground

use std::fs; 
use std::error::Error; 
use std::path::PathBuf; 

fn list_files(path: &str) -> Result<Vec<PathBuf>, Box<Error>> { 
    let x = fs::read_dir(path)? 
     .filter_map(|e| e.ok()) 
     .filter(|e| e.metadata().is_ok()) 
     .filter(|e| e.metadata().unwrap().is_file()) 
     .map(|e| e.path()) 
     .collect(); 

    Ok(x) 
} 

fn main() { 
    let path = "."; 
    for res in list_files(path).unwrap() { 
     println!("{:#?}", res); 
    } 
} 
+0

Merci! Quelle est la qualité du compilateur Rust dans l'optimisation de telles choses? Parce que je ne voudrais pas répéter encore et encore. – koehr

+0

c'est la belle chose à propos des itérateurs, ce n'est qu'une itération car il n'y a qu'un seul appel à collecter – user25064

+2

Cette réponse est géniale! Cependant, il y a une différence avec le code de l'OP car dans votre code, vous ne retournez pas à la première erreur. Vous ne les jetez que silencieusement. – Boiethios