Repository: uutils/procps
Pull request #70 (2024-04-24 22:38:04)

implement free features

D3V1LC0D3R: wait im stupid i forgot to test whether it works for count = 1 🥴
D3V1LC0D3R: i dont know how important that is but our free doesnt throw an error for count is 0
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/uutils/procps/pull/70?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils) Report
Attention: Patch coverage is `58.33333%` with `25 lines` in your changes are missing coverage. Please review.
> Project coverage is 44.09%. Comparing base [(`c0386f1`)](https://app.codecov.io/gh/uutils/procps/commit/c0386f1db79c38b5db19746a4df465052244ac26?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils) to head [(`9d75109`)](https://app.codecov.io/gh/uutils/procps/pull/70?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils).
> Report is 1 commits behind head on main.

| [Files](https://app.codecov.io/gh/uutils/procps/pull/70?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils) | Patch % | Lines |
|---|---|---|
| [src/uu/free/src/free.rs](https://app.codecov.io/gh/uutils/procps/pull/70?src=pr&el=tree&filepath=src%2Fuu%2Ffree%2Fsrc%2Ffree.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils#diff-c3JjL3V1L2ZyZWUvc3JjL2ZyZWUucnM=) | 58.33% | [19 Missing and 6 partials :warning: ](https://app.codecov.io/gh/uutils/procps/pull/70?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##            main      #70       +/-   ##
==========================================
+ Coverage   0.10%   44.09%   +43.98%     
==========================================
  Files         18       18               
  Lines       1830     2166      +336     
  Branches     239      304       +65     
==========================================
+ Hits           2      955      +953     
+ Misses      1827     1048      -779     
- Partials       1      163      +162     
```

| [Flag](https://app.codecov.io/gh/uutils/procps/pull/70/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils) | Coverage Δ | |
|---|---|---|
| [ubuntu_latest](https://app.codecov.io/gh/uutils/procps/pull/70/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils) | `44.09% <58.33%> (?)` | |
| [windows_latest](https://app.codecov.io/gh/uutils/procps/pull/70/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils) | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils#carryforward-flags-in-the-pull-request-comment) to find out more.


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/uutils/procps/pull/70?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uutils).

D3V1LC0D3R: i just realized that swap and total have no human output, gotta fix that
sylvestre: please add tests in https://github.com/uutils/procps/blob/main/tests/by-util/test_free.rs
thanks

D3V1LC0D3R: @sylvestre i added some basic tests but they dont cover combinations such as -th
D3V1LC0D3R: @sylvestre will this pr get merged?
sylvestre: probably but please be a bit patient :)

D3V1LC0D3R: @sylvestre sry xD i was just a bit bored so i cleaned up the code a bit and implemented --lohi, committing after i tested it a bit :)
cakebaker: Why do you think `--lohi` doesn't work with the original? And your implementation works?

I have my doubts that your implementation works because when I look at your changes, I see no changes made to `parse_meminfo()`. And the keys `HighTotal`, `HighFree`, `LowTotal`, and `LowFree` from `/proc/meminfo` are never read.
D3V1LC0D3R: parse_meminfo is called in the match
sylvestre: According to code coverage, it seems that a bunch of lines aren't covered by tests. could you please extend the tests ? thanks

cakebaker: @D3V1LC0D3R yes, `parse_meminfo()` is called, though I'm talking about it's implementation. If you look at it, the keys I mentioned above are not handled by this function (nor anywhere else in the program), even though those keys look like they contain the `--lohi` information, hence my skepticism ;-)

I think it is easy to mistake the output of `free --lohi` as incorrect. Here is the output from my machine:
```
               total        used        free      shared  buff/cache   available
Mem:         8024760     3501244      964364      561908     4434544     4523516
Low:         8024760     7060396      964364
High:              0           0           0
```
Obviously, the "High" values must be incorrect, they must be greater or equal to the "Low" values. However, "Low" and "High" refer to different memory regions and as there is no high memory on my machine (and the aforementioned keys are not in `/proc/meminfo`), it is shown as `0`.
D3V1LC0D3R: @cakebaker i thought --lohi was just info about the highest and lowest the memory has been while running free
D3V1LC0D3R: so i moved my incorrect `--lohi` to `--minmax` and implemented `--lohi` according to the kernel docs. i dont have a mac so somebody else has to implement that. the sysinfo library doesnt have any info on that as far as i can see.
D3V1LC0D3R: now free should be complete
D3V1LC0D3R: @sylvestre @cakebaker are there any problems with this pr?
D3V1LC0D3R: @sylvestre anything else?
sylvestre: Code coverage seems to complain that a few lines aren't covered by test, could you please add a few tests? thanks

D3V1LC0D3R: @sylvestre i think that is out of date. how can i check that locally? cargo test only tests and doesnt provide any coverage info as far as i know.
D3V1LC0D3R: i found the problem. i forgot to remove the test of --minmax
cakebaker: I think there is still some misunderstanding about `--lohi` and thus its implementation is incorrect :|

Here's what I get on my machine:
```
$ cargo run free --lohi
               total        used        free      shared  buff/cache   available
Mem:         8024716     3797944     1526748      588260     3632572     4226772
Low:               0           0           0
High:              0           0           0
Swap:              0           0           0
$ free --lohi
               total        used        free      shared  buff/cache   available
Mem:         8024716     3774532     1550112      585096     3629456     4250184
Low:         8024716     6474604     1550112
High:              0           0           0
Swap:              0           0           0
```
In the code, you read `HighTotal` and the other related keys from `/proc/meminfo`. That's correct, however, it's possible that those keys are not available in `/proc/meminfo`, like on my machine. And this case is currently not handled.
D3V1LC0D3R: @cakebaker it should be fixed now from what i understand
D3V1LC0D3R: i totally forgot about the macos part

Patch:
diff --git a/src/uu/free/src/free.rs b/src/uu/free/src/free.rs
index e45875b..ecb2f65 100644
--- a/src/uu/free/src/free.rs
+++ b/src/uu/free/src/free.rs
@@ -24,14 +24,18 @@ use std::env;
 use std::fs;
 #[cfg(target_os = "linux")]
 use std::io::Error;
+use std::ops::Mul;
 use std::process;
+use std::thread::sleep;
+use std::time::Duration;
+use std::u64;
 use uucore::{error::UResult, format_usage, help_about, help_usage};
 
 const ABOUT: &str = help_about!("free.md");
 const USAGE: &str = help_usage!("free.md");
 
 /// The unit of number is [UnitMultiplier::Bytes]
-#[derive(Default)]
+#[derive(Default, Clone)]
 struct MemInfo {
     total: u64,
     free: u64,
@@ -43,10 +47,17 @@ struct MemInfo {
     swap_free: u64,
     swap_used: u64,
     reclaimable: u64,
+    low_total: u64,
+    low_free: u64,
+    high_total: u64,
+    high_free: u64,
+    commit_limit: u64,
+    committed: u64,
 }
 
 #[cfg(target_os = "linux")]
 fn parse_meminfo() -> Result<MemInfo, Error> {
+    // kernel docs: https://www.kernel.org/doc/html/latest/filesystems/proc.html#meminfo
     let contents = fs::read_to_string("/proc/meminfo")?;
     let mut mem_info = MemInfo::default();
 
@@ -63,10 +74,27 @@ fn parse_meminfo() -> Result<MemInfo, Error> {
                 "SwapTotal" => mem_info.swap_total = parsed_value,
                 "SwapFree" => mem_info.swap_free = parsed_value,
                 "SReclaimable" => mem_info.reclaimable = parsed_value,
+                "LowTotal" => mem_info.low_total = parsed_value,
+                "LowFree" => mem_info.low_free = parsed_value,
+                "HighTotal" => mem_info.high_total = parsed_value,
+                "HighFree" => mem_info.high_free = parsed_value,
+                "CommitLimit" => mem_info.commit_limit = parsed_value,
+                "Committed_AS" => mem_info.committed = parsed_value,
                 _ => {}
             }
         }
     }
+    // as far as i understand the kernel doc everything that is not highmem (out of all the memory) is lowmem
+    // from kernel doc: "Highmem is all memory above ~860MB of physical memory."
+    // it would be better to implement this via optionals, etc. but that would require a refactor so lets not do that right now
+
+    if mem_info.low_total == u64::default() {
+        mem_info.low_total = mem_info.total - mem_info.high_total;
+    }
+
+    if mem_info.low_free == u64::default() {
+        mem_info.low_free = mem_info.free - mem_info.high_free;
+    }
 
     mem_info.swap_used = mem_info.swap_total - mem_info.swap_free;
 
@@ -90,6 +118,12 @@ fn parse_meminfo() -> Result<MemInfo, Box<dyn std::error::Error>> {
         swap_free: sys.free_swap(),
         swap_used: sys.total_swap().saturating_sub(sys.free_swap()),
         reclaimable: 0,
+        low_total: 0,
+        low_free: 0,
+        high_total: 0,
+        high_free: 0,
+        commit_limit: 0,
+        committed: 0,
     };
 
     Ok(mem_info)
@@ -101,88 +135,160 @@ fn parse_meminfo() -> Result<MemInfo, Box<dyn std::error::Error>> {
     Ok(MemInfo::default())
 }
 
+// print total - used - free combo that is used for everything except memory for now
+// free can be negative if the memory is overcommitted so it has to be signed
+fn tuf_combo<F>(name: &str, total: u64, used: u64, free: i128, f: F)
+where
+    F: Fn(u64) -> String,
+{
+    // ugly hack to convert negative values
+    let free_str: String = if free < 0 {
+        "-".to_owned() + &f((-free) as u64)
+    } else {
+        f(free as u64)
+    };
+
+    println!("{:8}{:>12}{:>12}{:>12}", name, f(total), f(used), free_str);
+}
+
 #[uucore::main]
 pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     let matches = uu_app().try_get_matches_from(args)?;
-    let wide = matches.get_flag("wide");
 
+    let wide = matches.get_flag("wide");
     let human = matches.get_flag("human");
-
     let si = matches.get_flag("si");
+    let total = matches.get_flag("total");
+    let lohi = matches.get_flag("lohi");
+    let count_flag = matches.get_one("count");
+    let mut count: u64 = count_flag.unwrap_or(&1_u64).to_owned();
+    let seconds_flag = matches.get_one("seconds");
+    let seconds: f64 = seconds_flag.unwrap_or(&1.0_f64).to_owned();
+    let committed = matches.get_flag("committed");
+    let one_line = matches.get_flag("line");
 
+    let dur = Duration::from_nanos(seconds.mul(1_000_000_000.0).round() as u64);
     let convert = detect_unit(&matches);
 
-    match parse_meminfo() {
-        Ok(mem_info) => {
-            let buff_cache = if wide {
-                mem_info.buffers
-            } else {
-                mem_info.buffers + mem_info.cached
-            };
-            let cache = if wide { mem_info.cached } else { 0 };
-            let used = mem_info.total - mem_info.available;
-
-            if wide {
-                wide_header();
-                if human {
-                    println!(
-                        "{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
-                        "Mem:",
-                        humanized(mem_info.total, si),
-                        humanized(used, si),
-                        humanized(mem_info.free, si),
-                        humanized(mem_info.shared, si),
-                        humanized(buff_cache, si),
-                        humanized(cache + mem_info.reclaimable, si),
-                        humanized(mem_info.available, si),
-                    )
+    let infinite: bool = count_flag.is_none() && seconds_flag.is_some();
+
+    while count > 0 || infinite {
+        // prevent underflow
+        if !infinite {
+            count -= 1;
+        }
+        match parse_meminfo() {
+            Ok(mem_info) => {
+                let buff_cache = if wide {
+                    mem_info.buffers
                 } else {
+                    mem_info.buffers + mem_info.cached
+                };
+                let cache = if wide { mem_info.cached } else { 0 };
+                let used = mem_info.total - mem_info.available;
+
+                // function that converts the number to the correct string
+                let n2s = |x| {
+                    if human {
+                        humanized(x, si)
+                    } else {
+                        convert(x).to_string()
+                    }
+                };
+                if one_line {
                     println!(
-                        "{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
-                        "Mem:",
-                        convert(mem_info.total),
-                        convert(used),
-                        convert(mem_info.free),
-                        convert(mem_info.shared),
-                        convert(buff_cache),
-                        convert(cache + mem_info.reclaimable),
-                        convert(mem_info.available),
-                    )
-                }
-            } else {
-                header();
-                if human {
-                    println!(
-                        "{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
-                        "Mem:",
-                        humanized(mem_info.total, si),
-                        humanized(used, si),
-                        humanized(mem_info.free, si),
-                        humanized(mem_info.shared, si),
-                        humanized(buff_cache + mem_info.reclaimable, si),
-                        humanized(mem_info.available, si),
-                    )
+                        "{:8}{:>12} {:8}{:>12}  {:8}{:>12} {:8}{:>12}",
+                        "SwapUse",
+                        n2s(mem_info.swap_used),
+                        "CacheUse",
+                        n2s(buff_cache + mem_info.reclaimable),
+                        "MemUse",
+                        n2s(used),
+                        "MemFree",
+                        n2s(mem_info.free)
+                    );
                 } else {
-                    println!(
-                        "{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
-                        "Mem:",
-                        convert(mem_info.total),
-                        convert(used),
-                        convert(mem_info.free),
-                        convert(mem_info.shared),
-                        convert(buff_cache + mem_info.reclaimable),
-                        convert(mem_info.available),
-                    )
+                    if wide {
+                        wide_header();
+                        println!(
+                            "{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
+                            "Mem:",
+                            n2s(mem_info.total),
+                            n2s(used),
+                            n2s(mem_info.free),
+                            n2s(mem_info.shared),
+                            n2s(buff_cache),
+                            n2s(cache + mem_info.reclaimable),
+                            n2s(mem_info.available),
+                        );
+                    } else {
+                        header();
+                        println!(
+                            "{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
+                            "Mem:",
+                            n2s(mem_info.total),
+                            n2s(used),
+                            n2s(mem_info.free),
+                            n2s(mem_info.shared),
+                            n2s(buff_cache + mem_info.reclaimable),
+                            n2s(mem_info.available),
+                        )
+                    }
+
+                    if lohi {
+                        tuf_combo(
+                            "Low:",
+                            mem_info.low_total,
+                            mem_info.low_total - mem_info.low_free,
+                            mem_info.low_free.into(),
+                            n2s,
+                        );
+                        tuf_combo(
+                            "High:",
+                            mem_info.high_total,
+                            mem_info.high_total - mem_info.high_free,
+                            mem_info.high_free.into(),
+                            n2s,
+                        );
+                    }
+
+                    tuf_combo(
+                        "Swap:",
+                        mem_info.swap_total,
+                        mem_info.swap_used,
+                        mem_info.swap_free.into(),
+                        n2s,
+                    );
+                    if total {
+                        tuf_combo(
+                            "Total:",
+                            mem_info.total + mem_info.swap_total,
+                            used + mem_info.swap_used,
+                            (mem_info.free + mem_info.swap_free).into(),
+                            n2s,
+                        );
+                    }
+
+                    if committed {
+                        tuf_combo(
+                            "Comm:",
+                            mem_info.commit_limit,
+                            mem_info.committed,
+                            (mem_info.commit_limit as i128) - (mem_info.committed as i128),
+                            n2s,
+                        );
+                    }
                 }
             }
-            println!(
-                "{:8}{:>12}{:>12}{:>12}",
-                "Swap:", mem_info.swap_total, mem_info.swap_used, mem_info.swap_free
-            );
+            Err(e) => {
+                eprintln!("free: failed to read memory info: {}", e);
+                process::exit(1);
+            }
         }
-        Err(e) => {
-            eprintln!("free: failed to read memory info: {}", e);
-            process::exit(1);
+        if count > 0 || infinite {
+            // the original free prints a newline everytime before waiting for the next round
+            println!();
+            sleep(dur);
         }
     }
 
@@ -200,20 +306,33 @@ pub fn uu_app() -> Command {
             "bytes", "kilo", "mega", "giga", "tera", "peta", "kibi", "mebi", "gibi", "tebi", "pebi",
         ]))
         .args([
-            arg!(-b --bytes "show output in bytes").action(ArgAction::SetTrue),
-            arg!(   --kilo  "show output in kilobytes").action(ArgAction::SetFalse),
-            arg!(   --mega  "show output in megabytes").action(ArgAction::SetTrue),
-            arg!(   --giga  "show output in gigabytes").action(ArgAction::SetTrue),
-            arg!(   --tera  "show output in terabytes").action(ArgAction::SetTrue),
-            arg!(   --peta  "show output in petabytes").action(ArgAction::SetTrue),
-            arg!(-k --kibi  "show output in kibibytes").action(ArgAction::SetTrue),
-            arg!(-m --mebi  "show output in mebibytes").action(ArgAction::SetTrue),
-            arg!(-g --gibi  "show output in gibibytes").action(ArgAction::SetTrue),
-            arg!(   --tebi  "show output in tebibytes").action(ArgAction::SetTrue),
-            arg!(   --pebi  "show output in pebibytes").action(ArgAction::SetTrue),
-            arg!(-h --human "show human-readable output").action(ArgAction::SetTrue),
-            arg!(   --si    "use powers of 1000 not 1024").action(ArgAction::SetFalse),
-            // arg!(-L --line  "show output on a single line"),
+            arg!(-b --bytes  "show output in bytes").action(ArgAction::SetTrue),
+            arg!(   --kilo   "show output in kilobytes").action(ArgAction::SetFalse),
+            arg!(   --mega   "show output in megabytes").action(ArgAction::SetTrue),
+            arg!(   --giga   "show output in gigabytes").action(ArgAction::SetTrue),
+            arg!(   --tera   "show output in terabytes").action(ArgAction::SetTrue),
+            arg!(   --peta   "show output in petabytes").action(ArgAction::SetTrue),
+            arg!(-k --kibi   "show output in kibibytes").action(ArgAction::SetTrue),
+            arg!(-m --mebi   "show output in mebibytes").action(ArgAction::SetTrue),
+            arg!(-g --gibi   "show output in gibibytes").action(ArgAction::SetTrue),
+            arg!(   --tebi   "show output in tebibytes").action(ArgAction::SetTrue),
+            arg!(   --pebi   "show output in pebibytes").action(ArgAction::SetTrue),
+            arg!(-h --human  "show human-readable output").action(ArgAction::SetTrue),
+            arg!(   --si     "use powers of 1000 not 1024").action(ArgAction::SetFalse),
+            arg!(-l --lohi   "show detailed low and high memory statistics")
+                .action(ArgAction::SetTrue),
+            arg!(-t --total "show total for RAM + swap").action(ArgAction::SetTrue),
+            arg!(-v --committed "show committed memory and commit limit")
+                .action(ArgAction::SetTrue),
+            // accept 1 as well as 0.5, 0.55, ...
+            arg!(-s --seconds "repeat printing every N seconds")
+                .action(ArgAction::Set)
+                .value_parser(clap::value_parser!(f64)),
+            // big int because predecesor accepts them as well (some scripts might have huge values as some sort of infinite)
+            arg!(-c --count "repeat printing N times, then exit")
+                .action(ArgAction::Set)
+                .value_parser(clap::value_parser!(u64)),
+            arg!(-L --line "show output on a single line").action(ArgAction::SetTrue),
         ])
         .arg(
             Arg::new("wide")


Test patch:
diff --git a/tests/by-util/test_free.rs b/tests/by-util/test_free.rs
index 90f5cd1..42ea8d4 100644
--- a/tests/by-util/test_free.rs
+++ b/tests/by-util/test_free.rs
@@ -9,6 +9,8 @@ use regex::Regex;
 
 use crate::common::util::TestScenario;
 
+// TODO: make tests combineable (e.g. test --total --human)
+
 #[test]
 fn test_invalid_arg() {
     new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
@@ -27,6 +29,54 @@ fn test_free_wide() {
     assert!(!result.stdout_str().contains("buff/cache"));
 }
 
+#[test]
+fn test_free_total() {
+    let result = new_ucmd!().arg("-t").succeeds();
+    assert_eq!(result.stdout_str().lines().count(), 4);
+    assert!(result
+        .stdout_str()
+        .lines()
+        .last()
+        .unwrap()
+        .starts_with("Total:"))
+}
+
+#[test]
+fn test_free_count() {
+    let result = new_ucmd!().args(&["-c", "2", "-s", "0"]).succeeds();
+    assert_eq!(result.stdout_str().lines().count(), 7);
+}
+
+#[test]
+fn test_free_lohi() {
+    let result = new_ucmd!().arg("--lohi").succeeds();
+    assert_eq!(result.stdout_str().lines().count(), 5);
+    let lines = result.stdout_str().lines().collect::<Vec<&str>>();
+    assert!(lines[2].starts_with("Low:"));
+    assert!(lines[3].starts_with("High:"));
+}
+
+#[test]
+fn test_free_committed() {
+    let result = new_ucmd!().arg("-v").succeeds();
+    assert_eq!(result.stdout_str().lines().count(), 4);
+    assert!(result
+        .stdout_str()
+        .lines()
+        .last()
+        .unwrap()
+        .starts_with("Comm:"))
+}
+
+#[test]
+fn test_free_always_one_line() {
+    // -L should ignore all other parameters and always print one line
+    let result = new_ucmd!().arg("-hltvwL").succeeds();
+    let stdout = result.stdout_str().lines().collect::<Vec<&str>>();
+    assert_eq!(stdout.len(), 1);
+    assert!(stdout[0].starts_with("SwapUse"));
+}
+
 #[test]
 fn test_free_column_format() {
     let re_head_str = r"^ {15}total {8}used {8}free {6}shared {2}buff/cache {3}available$";
diff --git a/tests/common/util.rs b/tests/common/util.rs
index e2e9dc0..e361ce9 100644
--- a/tests/common/util.rs
+++ b/tests/common/util.rs
@@ -993,7 +993,7 @@ impl AtPath {
 
     pub fn relative_symlink_file(&self, original: &str, link: &str) {
         #[cfg(windows)]
-        let original = original.replace('/', &MAIN_SEPARATOR_STR);
+        let original = original.replace('/', MAIN_SEPARATOR_STR);
         log_info(
             "symlink",
             format!("{},{}", &original, &self.plus_as_string(link)),
@@ -1015,7 +1015,7 @@ impl AtPath {
 
     pub fn relative_symlink_dir(&self, original: &str, link: &str) {
         #[cfg(windows)]
-        let original = original.replace('/', &MAIN_SEPARATOR_STR);
+        let original = original.replace('/', MAIN_SEPARATOR_STR);
         log_info(
             "symlink",
             format!("{},{}", &original, &self.plus_as_string(link)),