How to search file in directory using rust DirEntry? - rust

im making a program where user will input the filename that has to be searched in directory so when im using DirEntry to get path im getting a error like this
mismatched types
expected reference `&DirEntry`
found reference `&String`
the code is
pub fn search_file(filepath : &String) -> Result<&Path,(&str, std::io::Error)>{
let mut filename = String::new();
println!("enter your filename to be searched");
io::stdin().read_line(&mut filename).expect("failed to read input");
let ans = DirEntry::path(&filename).as_path();
Ok(ans)
}
can someone help me to rectify this program so it can successfully search the file in dir and return the path

DirEntry is a result of read_dir: when you traverse a directory, you iterate directory entries.
You can't create a direntry, it's a thing you get from an iteration. Not to mention you don't need to iterate anything, a directory is essentially an associative array, if you have a directory and a filename you can just check if the file exists in the directory:
let dir = Path::new(&filepath);
let candidate = dir.join(&filename);
if candidate.exists() {
Ok(candidate)
} else {
todo!()
}

Related

Rust fs read dir

I am writing a program that read dir and then if there a dir it should read it
I have tried this code:
pub async fn list_folder() {
let program_data_folder = String::from(std::env::var("ProgramData").expect("err") + "/Microsoft/Windows/Start Menu/Programs");
let path = Path::new(&program_data_folder);
let paths = fs::read_dir(path).unwrap();
for folder in paths {
println!("Name: {}", folder.unwrap().path().display());
if folder.unwrap().path().is_dir() {
fs::read_dir(folder.unwrap().file_name()).unwrap();
}
}
}
Error:
use of moved value: `folder`
value used here after moverustcClick for full compiler diagnostic
listeners.rs(15, 37): `folder` moved due to this method call
listeners.rs(15, 30): help: consider calling `.as_ref()` or `.as_mut()` to borrow the type's contents
listeners.rs(14, 9): move occurs because `folder` has type `Result<DirEntry, std::io::Error>`, which does not implement the `Copy` trait
result.rs(1106, 19): `Result::<T, E>::unwrap` takes ownership of the receiver `self`, which moves `folder`
How can I fix it?
The key is the last line:
Result::<T, E>::unwrap takes ownership of the receiver self, which moves folder
After you unwrap a Result it's not usable any more because the Ok value's been moved out. That means you can't call unwrap() repeatedly.
To fix it, call unwrap() once and save the result, like so:
for folder in paths {
let folder = folder.unwrap();
println!("Name: {}", folder.path().display());
if folder.path().is_dir() {
fs::read_dir(folder.file_name()).unwrap();
}
}
Unwrapping everything is a bad habit, though. It means I/O errors crash the program immediately. It's a lot better to let errors propagate to the caller so it can deal with them however it chooses. This entails making your function return a Result and replacing all the unwraps with ?. Rust makes the best option, ?, the shortest, to encourage good error handling.
pub async fn list_folder() -> std::io::Result<()> {
let program_data_folder = String::from(std::env::var("ProgramData").expect("err") + "/Microsoft/Windows/Start Menu/Programs");
let path = Path::new(&program_data_folder);
let paths = fs::read_dir(path)?;
for folder in paths {
let folder = folder?;
println!("Name: {}", folder.path().display());
if folder.path().is_dir() {
fs::read_dir(folder.file_name())?;
}
}
Ok(())
}

fs::read_to_string(file_path) error 123: InvalidFilename works when hardcoded but fails when a variable with an equivalent string is passed in [duplicate]

This question already has answers here:
Why does my string not match when reading user input from stdin?
(3 answers)
Closed last month.
I have some text files in my project's folder on the same level as my src folder. I have a run function that gets user input and makes a readfile struct from it.
pub fn run() {
loop {
let file = ReadFile::println_recieve_h("What is the filepath?");
let query = ReadFile::println_recieve_h("What phrase do you want to find?");
let readfile = ReadFile::new(&file, &query);
...
Here is the helper function that I made to reduce redundancy (I believe I have isolated the issue here):
fn println_recieve_h(print: &str) -> String {
println!("{print}");
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
input
}
Here is the function that invokes the point of failure
pub struct ReadFile{ query: String, file_path: String, contents: String }
impl ReadFile {
//Builds a readfile struct with a path and phrase.
fn new(file_path: &String, phrase: &String) -> ReadFile{
use std::fs;
ReadFile {
query: phrase.clone(),
file_path: file_path.clone(),
contents: fs::read_to_string(file_path).expect("ERROR 003: FILE NOT FOUND"),
}
}
contents: fs::read_to_string(file_path).expect("ERROR 003: FILE NOT FOUND") fails with the use case however succeeds with hardcoded string slice (I have tried converting the string reference to a slice).
Example: contents: fs::read_to_string("longtxt1.txt").expect("ERROR 003: FILE NOT FOUND").
Does anyone see the problem that is causing this? I am new to Rust.
I have tried converting the reference to a string slice, cloning the reference, etc. I made sure there are no spelling errors when I do my inputs in the console. I have hardcoded file in the run function to String::from("longtxt1.txt") which makes everything work. I believe this isolates the issue to my usage of the helper function.
read_line includes a trailing newline in the returned string, which is not in your filename. You'll need to trim the returned string.

Find Files that Match a Dynamic Pattern

I want to be able to parse all the files in a directory to find the one with the greatest timestamp that matches a user provided pattern.
I.e. if the user runs
$ search /foo/bar/baz.txt
and the directory /foo/bar/ contains files baz.001.txt, baz.002.txt, and baz.003.txt, then the result should be baz.003.txt
At the moment I'm constructing a PathBuf.
Using that to build a Regex.
Then finding all the files in the directory that match the expression.
But it feels like this is a lot of work for a relatively simple problem.
fn find(foo: &str) -> Result<Vec<String>, Box<dyn Error>> {
let mut files = vec![];
let mut path = PathBuf::from(foo);
let base = path.parent().unwrap().to_str().unwrap();
let file_name = path.file_stem().unwrap().to_str().unwrap();
let extension = path.extension().unwrap().to_str().unwrap();
let pattern = format!("{}\\.\\d{{3}}\\.{}", file_name, extension);
let expression = Regex::new(&pattern).unwrap();
let objects: Vec<String> = fs::read_dir(&base)
.unwrap()
.map(|entry| {
entry
.unwrap()
.path()
.file_name()
.unwrap()
.to_str()
.unwrap()
.to_owned()
})
.collect();
for object in objects.iter() {
if expression.is_match(object) {
files.push(String::from(object));
}
}
Ok(files)
}
Is there an easier way to take the file path, generate a pattern, and find all the matching files?
Rust is not really a language appropriated for quick and dirty solutions. Instead, it strongly incentivizes elegant solutions, where all corner cases are properly handled. This usually does not lead to extremely short solutions, but you can avoid too much boilerplate relying on external crates that factor a lot of code. Here is what I would do, assuming you don't already have a "library-wide" error.
fn find(foo: &str) -> Result<Vec<String>, FindError> {
let path = PathBuf::from(foo);
let base = path
.parent()
.ok_or(FindError::InvalidBaseFile)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let file_name = path
.file_stem()
.ok_or(FindError::InvalidFileName)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let file_extension = path
.extension()
.ok_or(FindError::NoFileExtension)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?;
let pattern = format!(r"{}\.\d{{3}}\.{}", file_name, file_extension);
let expression = Regex::new(&pattern)?;
Ok(
fs::read_dir(&base)?
.map(|entry| Ok(
entry?
.path()
.file_name()
.ok_or(FindError::InvalidFileName)?
.to_str()
.ok_or(FindError::OsStringNotUtf8)?
.to_string()
))
.collect::<Result<Vec<_>, FindError>>()?
.into_iter()
.filter(|file_name| expression.is_match(&file_name))
.collect()
)
}
A simplistic definition of FindError could be achieved via the thiserror crate:
use thiserror::Error;
#[derive(Error, Debug)]
enum FindError {
#[error(transparent)]
RegexError(#[from] regex::Error),
#[error("File name has no extension")]
NoFileExtension,
#[error("Not a valid file name")]
InvalidFileName,
#[error("No valid base file")]
InvalidBaseFile,
#[error("An OS string is not valid utf-8")]
OsStringNotUtf8,
#[error(transparent)]
IoError(#[from] std::io::Error),
}
Edit
As pointed out by #Masklinn, you can retrieve the stem and the extension of the file without all that hassle. It results in less-well handled errors (and some corner cases such as a hidden file without extension get handled poorly), but overall less verbose code. For you to chose depending on your needs.
fn find(foo: &str) -> Result<Vec<String>, FindError> {
let (file_name, file_extension) = foo
.rsplit_one('.')
.ok_or(FindError::NoExtension)?;
... // the rest is unchanged
}
You probably need to adapt FindError too. You can also get rid of the ok_or case, and just replace it with a .unwrap_or((foo, "")) if you don't really care about it (however this will give surprising results...).

How to add a folder to a path before the filename?

How do I change "C:\foo\bar.txt" to "C:\foo\baz\bar.txt" using either Path or PathBuf?
I want to add a folder to the path immediately before the filename.
The Path type supports a number of methods to manipulate and destructure paths, so it should be straightforward to append a directory. For example:
fn append_dir(p: &Path, d: &str) -> PathBuf {
let dirs = p.parent().unwrap();
dirs.join(d).join(p.file_name().unwrap())
}
I'm testing it on Linux, so for me the test looks like this, but on Windows you should be able to use C:\... just fine:
fn main() {
let p = Path::new(r"/foo/bar.txt");
assert_eq!(append_dir(&p, "baz"), Path::new(r"/foo/baz/bar.txt"));
}

Proper way to handle a compile-time relevant text file passed to a procedural macro

I have a requirement to pass to a procedural macro either a text file or the contents of a text file, such that the procedural macro acts based on the contents of that text file at compile time. That is, the text file configures the output of the macro. The use case for this is the file defining a register map which the macro builds into a library.
The second requirement is that the text file is properly handled by Cargo, such that changes to the text file trigger a recompile in the same way as changes to the source file trigger a recompile.
My initial thought was to create a static string using the include_str! macro. This solves the second requirement but I can't see how to pass that to the macro - at that point I only have the identifier of the string to pass in:
use my_macro_lib::my_macro;
static MYSTRING: &'static str = include_str!("myfile");
my_macro!(MYSTRING); // Not the string itself!
I can pass a string to the macro with the name of the file in a string literal, and open the file inside the macro:
my_macro!("myfile");
At which point I have two problems:
It's not obvious how to get the path of the calling function in order to get the path of the file. I initially thought this would be exposed through the token Span, but it seems in general not (perhaps I'm missing something?).
It's not obvious how to make the file make Cargo trigger a recompile on changes. One idea I had to force this was to add an include_str!("myfile") to the output of the macro, which would hopefully result in the compile being made aware of "myfile", but this is a bit mucky.
Is there some way to do what I'm trying to do? Perhaps either by somehow getting the contents of the string inside the macro that was created outside, or reliably getting the path of the calling rust file (then making Cargo treat changes properly).
As an aside, I've read various places that tell me I can't get access to the contents of variables inside the macro, but it seems to me that this is exactly what the quote macro is doing with #variables. How is this working?
So it turns out this is possible in essentially the way I was hoping with the stable compiler.
If we accept that we need to work relative to the crate root, we can define our paths as such.
Helpfully, inside the macro code, std::env::current_dir() will return the current working directory as the root of the crate containing the call site. This means, even if the macro invocation is inside some crate hierarchy, it will still return a path that is meaningful at the location of the macro invocation.
The following example macro does essentially what I need. For brevity, it's not designed to handle errors properly:
extern crate proc_macro;
use quote::quote;
use proc_macro::TokenStream;
use syn::parse::{Parse, ParseStream, Result};
use syn;
use std;
use std::fs::File;
use std::io::Read;
#[derive(Debug)]
struct FileName {
filename: String,
}
impl Parse for FileName {
fn parse(input: ParseStream) -> Result<Self> {
let lit_file: syn::LitStr = input.parse()?;
Ok(Self { filename: lit_file.value() })
}
}
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as FileName);
let cwd = std::env::current_dir().unwrap();
let file_path = cwd.join(&input.filename);
let file_path_str = format!("{}", file_path.display());
println!("path: {}", file_path.display());
let mut file = File::open(file_path).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
println!("contents: {:?}", contents);
let result = quote!(
const FILE_STR: &'static str = include_str!(#file_path_str);
pub fn foo() -> bool {
println!("Hello");
true
}
);
TokenStream::from(result)
}
Which can be invoked with
my_macro!("mydir/myfile");
where mydir is a directory in the root of the invoking crate.
This uses the hack of using an include_str!() in the macro output to cause rebuilds on changes to myfile. This is necessary and does what is expected. I would expect this to be optimised out if it's never actually used.
I'd be interested to know if this approach falls over in any situation.
Relevant to my original question, current nightly implements the source_file() method on Span. This might be a better way to implement the above, but I'd rather stick with stable. The tracking issue for this is here.
Edit:
The above implementation fails when the package is in a workspace, at which point the current working directory is the workspace root, not the crate root. This is easy to work around with something like as follows (inserted between cwd and file_path declarations).
let mut cwd = std::env::current_dir().unwrap();
let cargo_path = cwd.join("Cargo.toml");
let mut cargo_file = File::open(cargo_path).unwrap();
let mut cargo_contents = String::new();
cargo_file.read_to_string(&mut cargo_contents).unwrap();
// Use a simple regex to detect the suitable tag in the toml file. Much
// simpler than using the toml crate and probably good enough according to
// the workspace RFC.
let cargo_re = regex::Regex::new(r"(?m)^\[workspace\][ \t]*$").unwrap();
let workspace_path = match cargo_re.find(&cargo_contents) {
Some(val) => std::env::var("CARGO_PKG_NAME"),
None => "".to_string()
};
let file_path = cwd.join(workspace_path).join(input.filename);
let file_path_str = format!("{}", file_path.display());

Resources