I want to remove the _ from the name of png images in a folder:
use std::error::Error;
use std::fs;
use std::path::Path;
fn main() -> Result<(), Box<dyn Error>> {
let dir = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().unwrap_or_default() == "png" {
if let Some(Some(new_path)) = path
.file_name()
.map(|name| name.to_str().map(|s| s.replace("_", "")))
{
fs::rename(path, new_path)?;
}
}
}
Ok(())
}
The code is "deleting" the png files instead of renaming them. I suspect it's because they are being renamed to a location that doesn't exist. But I'm not quite sure how to modify the code to fix that.
Live code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=323f901e4f0f4c99dbf5affeb0127991
Your current code just uses the file name as new path, what you want to do is use with_path_name to replace the filename of path with your new file name:
use std::error::Error;
use std::fs;
fn main() -> Result<(), Box<dyn Error>> {
for entry in fs::read_dir("/home/alex/Desktop")? {
let path = entry?.path();
if path.is_file() && path.extension().unwrap_or_default() == "png" {
if let Some(new_name) = path
.file_name()
.and_then(|name| name.to_str())
.map(|s| s.replace("_", ""))
{
let new_path = path.with_file_name(new_name);
fs::rename(path, new_path)?;
}
}
}
Ok(())
}
This code deletes PNG files in a folder and prints them. I wanted to simplify it a bit by replacing all the unwrap()'s with ?.
use std::fs;
use std::path::Path;
fn main() -> Result<(), std::io::Error> {
let path = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().unwrap() == "png" {
fs::remove_file(&path)?;
println!("{}", path.file_name().unwrap().to_str().unwrap());
}
}
Ok(())
}
I found out I can't replace the unwrap()'s that are handling Option instead of Result. In this case, extension(), file_name(), and to_str().
I changed the code to solve that problem. However, the code just became more complicated:
use std::fs;
use std::path::Path;
fn main() -> Result<(), std::io::Error> {
let path = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
let ext = path.extension().ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Invalid file extension"))?;
if ext == "png" {
fs::remove_file(&path)?;
println!("{}", path.file_name().ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Invalid file name"))?.to_str().ok_or(std::io::Error::new(std::io::ErrorKind::Other, "Invalid file name"))?);
}
}
}
Ok(())}
How to replace the unwrap()'s that are handling Option without making the code more complicated (especially, without so much nesting)? Or at least not as complicated as the one I shared?
In many of the cases where you were calling unwrap it wasn't actually an error, you just want to do something different (or not at all).
If you're only interested in the case where there is a png extension, check if that's what you got. It doesn't matter if it's None or .jpg.
Printing a filename can fail because filenames can have non unicode characters and Rust strings can't. In this case given it's presumably meant to be human readable, printing the to_string_lossy() output (replacing non-unicode characters) or printing it using debug mode (escaping non-unicode characters) is probably fine.
use std::fs;
use std::path::Path;
fn main() -> Result<(), std::io::Error> {
let path = Path::new("/home/alex/Desktop");
for entry in fs::read_dir(path)? {
let entry = entry?;
let path = entry.path();
if path.is_file() {
if let Some(ext) = path.extension() {
if ext == "png" {
fs::remove_file(&path)?;
// I would keep expect here as extension would return None if there was no filename
// to_string_lossy returns a printable string - possibly replacing non-unicode characters
println!("{}", path.file_name().expect("File has no name").to_string_lossy());
}
}
}
}
Ok(())
}
I'm totally stuck reading a file from a variable path structure of a zip file without decompressing it.
My file is located here:
/info/[random-string]/info.json
Where [random-string] is the only file in the info folder.
So its like read the 'info' folder read the first folder read the 'info.json'.
Any ideas how to do that with one of these libraries (zip or rc_zip)?
let file_path = file.to_str().unwrap();
let file = File::open(file_path).unwrap();
let reader = BufReader::new(file);
let mut archive = zip::ZipArchive::new(reader).unwrap();
let info_folder = archive.by_name("info").unwrap();
// how to list files of info_folder
Here you are:
use std::error::Error;
use std::ffi::OsStr;
use std::fs::File;
use std::path::Path;
use zip::ZipArchive; // zip 0.5.13
fn main() -> Result<(), Box<dyn Error>> {
let archive = File::open("./info.zip")?;
let mut archive = ZipArchive::new(archive)?;
// iterate over all files, because you don't know the exact name
for idx in 0..archive.len() {
let entry = archive.by_index(idx)?;
let name = entry.enclosed_name();
if let Some(name) = name {
// process only entries which are named info.json
if name.file_name() == Some(OsStr::new("info.json")) {
// the ancestors() iterator lets you walk up the path segments
let mut ancestors = name.ancestors();
// skip self - the first entry is always the full path
ancestors.next();
// skip the random string
ancestors.next();
let expect_info = ancestors.next();
// the reminder must be only 'info/' otherwise this is the wrong entry
if expect_info == Some(Path::new("info/")) {
// do something with the file
println!("Found!!!");
break;
}
}
}
}
Ok(())
}
I just started using rust, and i'm having trouble finding out if a path is a file or directory. I have this function that gets input, which i'm using to get the path:
pub fn input(msg: &str, v: &mut String) {
println!("{}", msg);
stdin().read_line(v).unwrap();
}
When i take input like this:
let mut path = String::new();
input("What is the path to your file/directory?", &mut path);
The I try to make a Path with it, and check if it's a file:
let file = Path::new(&path);
println!("{}", file.is_file());
Which prints false
When I make the Path from a set String, it works:
let file = Path::new("../directory/file.js");
println!("{}", file.is_file());
Prints true
I'm sure that i'm giving it a valid file path in the input function. I've read the docs to try to find what i'm doing wrong but I can't
Any idea whats wrong here?
I would like to copy an entire directory to a location in a user's $HOME. Individually copying files to that directory is straightforward:
let contents = include_str!("resources/profiles/default.json");
let fpath = dpath.join(&fname);
fs::write(fpath, contents).expect(&format!("failed to create profile: {}", n));
I haven't found a way to adapt this to multiple files:
for n in ["default"] {
let fname = format!("{}{}", n, ".json");
let x = format!("resources/profiles/{}", fname).as_str();
let contents = include_str!(x);
let fpath = dpath.join(&fname);
fs::write(fpath, contents).expect(&format!("failed to create profile: {}", n));
}
...the compiler complains that x must be a string literal.
As far as I know, there are two options:
Write a custom macro.
Replicate the first code for each file I want to copy.
What is the best way of doing this?
I would create a build script that iterates through a directory, building up an array of tuples containing the name and another macro call to include the raw data:
use std::{
env,
error::Error,
fs::{self, File},
io::Write,
path::Path,
};
const SOURCE_DIR: &str = "some/path/to/include";
fn main() -> Result<(), Box<dyn Error>> {
let out_dir = env::var("OUT_DIR")?;
let dest_path = Path::new(&out_dir).join("all_the_files.rs");
let mut all_the_files = File::create(&dest_path)?;
writeln!(&mut all_the_files, r##"["##,)?;
for f in fs::read_dir(SOURCE_DIR)? {
let f = f?;
if !f.file_type()?.is_file() {
continue;
}
writeln!(
&mut all_the_files,
r##"("{name}", include_bytes!(r#"{name}"#)),"##,
name = f.path().display(),
)?;
}
writeln!(&mut all_the_files, r##"]"##,)?;
Ok(())
}
This has some weaknesses, namely that it requires the path to be expressible as a &str. Since you were already using include_string!, I don't think that's an extra requirement. This also means that the generated string has to be a valid Rust string. We use raw strings inside the generated file, but this can still fail if a filename were to contain the string "#. A better solution would probably use str::escape_default.
Since we are including files, I used include_bytes! instead of include_str!, but if you really needed to you can switch back. The raw bytes skips performing UTF-8 validation at compile time, so it's a small win.
Using it involves importing the generated value:
const ALL_THE_FILES: &[(&str, &[u8])] = &include!(concat!(env!("OUT_DIR"), "/all_the_files.rs"));
fn main() {
for (name, data) in ALL_THE_FILES {
println!("File {} is {} bytes", name, data.len());
}
}
See also:
How can I locate resources for testing with Cargo?
You can use include_dir macro.
use include_dir::{include_dir, Dir};
use std::path::Path;
const PROJECT_DIR: Dir = include_dir!(".");
// of course, you can retrieve a file by its full path
let lib_rs = PROJECT_DIR.get_file("src/lib.rs").unwrap();
// you can also inspect the file's contents
let body = lib_rs.contents_utf8().unwrap();
assert!(body.contains("SOME_INTERESTING_STRING"));
Using a macro:
macro_rules! incl_profiles {
( $( $x:expr ),* ) => {
{
let mut profs = Vec::new();
$(
profs.push(($x, include_str!(concat!("resources/profiles/", $x, ".json"))));
)*
profs
}
};
}
...
let prof_tups: Vec<(&str, &str)> = incl_profiles!("default", "python");
for (prof_name, prof_str) in prof_tups {
let fname = format!("{}{}", prof_name, ".json");
let fpath = dpath.join(&fname);
fs::write(fpath, prof_str).expect(&format!("failed to create profile: {}", prof_name));
}
Note: This is not dynamic. The files ("default" and "python") are specified in the call to the macro.
Updated: Use Vec instead of HashMap.