How to get `__dirname` like nodejs using rust [duplicate] - node.js

I want to read files from a config folder at the directory where the executable is located. I do that using the following functions:
use std::env;
// add part of path to te path gotten from fn get_exe_path();
fn get_file_path(path_to_file: &str) -> PathBuf {
let final_path = match get_exe_path() {
Ok(mut path) => {
path.push(path_to_file);
path
}
Err(err) => panic!("Path does not exists"),
};
final_path
}
// Get path to current executable
fn get_exe_path() -> Result<PathBuf, io::Error> {
//std::env::current_exe()
env::current_exe()
}
In my case, get_exe_path() will return C:\Users\User\Documents\Rust\Hangman\target\debug\Hangman.exe.
With get_file_path("Config\test.txt"), I want to append Config\test.txt To the above path. Then I get the following path to the file: C:\Users\User\Documents\Rust\Hangman\target\debug\Hangman.exe\Config\test.txt
The problem is that std::env::current_exe() will get the file name of the executable also and I do not need that. I only need the directory where it is located.
Question
The following the following function call should return C:\Users\User\Documents\Rust\Hangman\target\debug\Config\test.txt:
let path = get_file_path("Config\\test.txt");
How can I get the path from the current directory without the executable name like above example? Are there any other ways to do this than using std::env::current_exe()

PathBuf::pop is the mirror of PathBuf::push:
Truncates self to self.parent.
Returns false and does nothing if self.file_name is None. Otherwise,
returns true.
In your case:
use std::env;
use std::io;
use std::path::PathBuf;
fn inner_main() -> io::Result<PathBuf> {
let mut dir = env::current_exe()?;
dir.pop();
dir.push("Config");
dir.push("test.txt");
Ok(dir)
}
fn main() {
let path = inner_main().expect("Couldn't");
println!("{}", path.display());
}
There's also the possibility of using Path::parent:
Returns the Path without its final component, if there is one.
Returns None if the path terminates in a root or prefix.
In your case:
fn inner_main() -> io::Result<PathBuf> {
let exe = env::current_exe()?;
let dir = exe.parent().expect("Executable must be in some directory");
let mut dir = dir.join("Config");
dir.push("test.txt");
Ok(dir)
}
See also:
How to get the name of current program without the directory part?

Related

Rename script deleting images instead of renaming them

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(())
}

Replacing unwrap() with ? made code more complicated instead of simplifying it

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(())
}

How to read specific file from zip file

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(())
}

rust std::path::Path is_file() always returns false

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?

How do I use include_str! for multiple files or an entire directory?

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.

Resources