├── .cargo └── config.toml ├── .gitignore ├── Cargo.toml ├── NON-LICENSE.md ├── README.md └── src ├── main.rs └── output.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | linker = "aarch64-none-linux-gnu-gcc" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | /target 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsfetch" 3 | version = "0.1.0" 4 | authors = ["Ash "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [features] 10 | pretty_output = ["prettytable-rs"] 11 | music_mpd = ["nixinfo/music_mpd"] 12 | music_playerctl = ["nixinfo/music_playerctl"] 13 | 14 | [dependencies] 15 | clap = "2.33.3" 16 | prettytable-rs = { version = "0.10.0", optional = true } 17 | nixinfo = { git = "https://github.com/Phate6660/nixinfo" } 18 | 19 | [profile.release] 20 | codegen-units = 1 21 | lto = "fat" 22 | -------------------------------------------------------------------------------- /NON-LICENSE.md: -------------------------------------------------------------------------------- 1 | Anybody, is allowed to do anything, with this software.
2 | *Any and all copyright restrictions are hereby revoked.* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## rsfetch 2 | 3 | This is a WIP rewrite of [rsfetch](https://github.com/rsfetch/rsfetch) from scratch.
4 | I've been really unhappy with the codebase now for various reasons.
5 | It's made development for rsfetch downright unpleasant at times. 6 | 7 | ---- 8 | 9 | A blazing fast (<20 ms) information fetching utility.
10 | According to [`hyperfine`](https://github.com/sharkdp/hyperfine) anyways. 11 | 12 | Table of Contents: 13 | 14 | - [Example Output](#example-output) 15 | - [Extra Info](#extra-info) 16 | - [Features](#features) 17 | - [Help](#help) 18 | - [TODO](#todo) 19 | 20 | ## Example Output 21 | 22 | `$ cargo run --features=music -- -cDdEeghkmMstuUp portage` 23 | 24 | ``` 25 | CPU: Intel Core i5-3470 CPU @ 3.20GHz 26 | Device: OptiPlex 7010 27 | Distro: Gentoo 28 | Editor: /usr/bin/emacsclient 29 | Environment: bspwm 30 | GPU: AMD/ATI Cedar Radeon HD 5000/6000/7350/8350 Series 31 | Hostname: gentoo 32 | Kernel: 5.4.48-ck-valley 33 | Memory: 15971 MB 34 | Packages: 87 (explicit), 570 (total) 35 | Shell: /bin/bash 36 | Terminal: xterm 37 | Uptime: 2d 9h 54m 38 | User: valley 39 | Music: System Of A Down - System Of A Down (1998) - Know 40 | ``` 41 | 42 | `$ cargo run --features=music,pretty_output -- -cDdEeghkmMstuUp portage -C 0` 43 | 44 | ``` 45 | 0──────────────────────────────────────────────────────────────────────0 46 | │ CPU │ Intel Core i5-3470 CPU @ 3.20GHz │ 47 | │ Distro │ Gentoo │ 48 | │ Device │ OptiPlex 7010 │ 49 | │ Editor │ /usr/bin/emacsclient │ 50 | │ Environment │ bspwm │ 51 | │ GPU │ AMD/ATI Cedar Radeon HD 5000/6000/7350/8350 Series │ 52 | │ Hostname │ gentoo │ 53 | │ Kernel │ 5.4.48-ck-valley │ 54 | │ Memory │ 15971 MB │ 55 | │ Packages │ 87 (explicit), 570 (total) │ 56 | │ Shell │ /bin/bash │ 57 | │ Terminal │ xterm │ 58 | │ Uptime │ 2d 9h 54m │ 59 | │ User │ valley │ 60 | │ Music │ System Of A Down - System Of A Down (1998) - Suite-Pee │ 61 | 0──────────────────────────────────────────────────────────────────────0 62 | ``` 63 | 64 | ## Extra info 65 | 66 | Crate deps and binary size depending on features for rsfetch: 67 | 68 | - No Features: 16 crates, 2.5 MB (not stripped), 868 KB (stripped) 69 | - `music`: 20 crates, 2.6 MB (not stripped), 940 KB (stripped) 70 | - `pretty_output`: 36 crates, 2.6 MB (not stripped), 1.0 MB (stripped) 71 | - `music,pretty_output`: 40 crates, 2.7 MB (not stripped), 1.1 MB (stripped) 72 | 73 | Crates explicitely used, and why: 74 | 75 | - `clap`: CLI framework 76 | - `mpd`: completely optional, used for the feature `music` 77 | - [`nixinfo`](https://github.com/Phate6660/nixinfo): contains all of the information gathering functions 78 | - `prettytable-rs`: completely optional, used for the feature `pretty_output` 79 | 80 | Tokei stats (cropped to save space): `tokei -t=rust .` 81 | 82 | ``` 83 | =============================================================================== 84 | Language Files Lines Code Comments Blanks 85 | =============================================================================== 86 | Rust 2 224 219 0 5 87 | =============================================================================== 88 | ``` 89 | 90 | ## Features 91 | 92 | Currently there are 2 features. They are completely optional.
93 | 94 | - `music`, this will cause nixinfo to pull in the `mpd` crate, and display the music info as: 95 | 96 | `artist - album (date) - title` 97 | 98 | - `pretty_output`, this will cause rsfetch to pull in the `prettytable-rs` crate, see example above for how it looks. 99 | 100 | ## Help 101 | 102 | `$ cargo run -- --help` 103 | 104 | ``` 105 | rsfetch 0.1.0 106 | Phate6660 107 | 108 | An info fetch tool written in Rust. Everything is off by default, enable what you want. 109 | 110 | USAGE: 111 | rsfetch [FLAGS] [OPTIONS] 112 | 113 | FLAGS: 114 | -c Display the model of the CPU. 115 | -D Display the name of the device. 116 | -d Display the name of the distro. 117 | -E Display the name of the user's editor. Must have the $EDITOR environmental variable set. 118 | -e Display the user's environment. First checks for a DE, before resorting to parsing your 119 | $HOME/.xinitrc for your WM. 120 | --help Prints help information 121 | -h Display the hostname of the device. 122 | -k Display the name of the kernel. 123 | -m Display free/total memory. 124 | -M Display currently playing music. Only mpd is supported. Must be built with the music feature. 125 | -s Display the name of the user's shell. 126 | -t Display the name of the user's terminal. 127 | -u Display the uptime. 128 | -U Display the name of the user. 129 | -V, --version Prints version information 130 | 131 | OPTIONS: 132 | -C, --corner Set the corner character. 133 | -p, --packages Display package count. 134 | -T, --temperature Display CPU temp for Raspberry Pi, must have CPU field enabled. 135 | ``` 136 | 137 | ## TODO 138 | 139 | - better pretty output by either handling it myself (scary!) or replacing `prettytable-rs` with a lighter crate 140 | - implement async 141 | - information gathering functions are split off into a separate lib (implemented -- `nixinfo`) 142 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "pretty_output")] 2 | #[macro_use] extern crate prettytable; 3 | 4 | use clap::Arg; 5 | mod output; 6 | 7 | fn main() { 8 | let matches = clap::App::new("rsfetch") 9 | .version("0.1.0") 10 | .author("Phate6660 ") 11 | .about("\nAn info fetch tool written in Rust. Everything is off by default, enable what you want.") 12 | .arg(Arg::with_name("corner") 13 | .short("C") 14 | .long("corner") 15 | .value_name("char") 16 | .help("Set the corner character.") 17 | .takes_value(true)) 18 | .arg(Arg::with_name("cpu") 19 | .short("c") 20 | .help("Display the model of the CPU.")) 21 | .arg(Arg::with_name("device") 22 | .short("D") 23 | .help("Display the name of the device.")) 24 | .arg(Arg::with_name("distro") 25 | .short("d") 26 | .help("Display the name of the distro.")) 27 | .arg(Arg::with_name("editor") 28 | .short("E") 29 | .help("Display the name of the user's editor. Must have the $EDITOR environmental variable set.")) 30 | .arg(Arg::with_name("environment") 31 | .short("e") 32 | .help("Display the user's environment. First checks for a DE, before resorting to parsing your $HOME/.xinitrc for your WM.")) 33 | .arg(Arg::with_name("gpu") 34 | .short("g") 35 | .help("Display the user's GPU. Currently requires `lspci` and `grep` to be installed until I can find a pure rust solution.")) 36 | .arg(Arg::with_name("hostname") 37 | .short("h") 38 | .help("Display the hostname of the device.")) 39 | .arg(Arg::with_name("kernel") 40 | .short("k") 41 | .help("Display the name of the kernel.")) 42 | .arg(Arg::with_name("memory") 43 | .short("m") 44 | .help("Display free/total memory.")) 45 | .arg(Arg::with_name("music") 46 | .short("M") 47 | .help("Display currently playing music. Only mpd is supported. Must be built with the music feature.")) 48 | .arg(Arg::with_name("packages") 49 | .short("p") 50 | .long("packages") 51 | .value_name("manager") 52 | .help("Display package count.") 53 | .takes_value(true)) 54 | .arg(Arg::with_name("shell") 55 | .short("s") 56 | .help("Display the name of the user's shell.")) 57 | .arg(Arg::with_name("temperature") 58 | .short("T") 59 | .long("temperature") 60 | .value_name("C/F") 61 | .help("Display CPU temp for Raspberry Pi, must have CPU field enabled.") 62 | .takes_value(true)) 63 | .arg(Arg::with_name("terminal") 64 | .short("t") 65 | .help("Display the name of the user's terminal.")) 66 | .arg(Arg::with_name("uptime") 67 | .short("u") 68 | .help("Display the uptime.")) 69 | .arg(Arg::with_name("user") 70 | .short("U") 71 | .help("Display the name of the user.")) 72 | .get_matches(); 73 | output::main(matches); 74 | } 75 | -------------------------------------------------------------------------------- /src/output.rs: -------------------------------------------------------------------------------- 1 | use clap::ArgMatches; 2 | use nixinfo::{ 3 | cpu, device, distro, env, environment, gpu, hostname, kernel, 4 | memory_total, memory_used, music, packages, temp, terminal, uptime, 5 | }; 6 | 7 | fn the_temp(matches: &ArgMatches) -> String { 8 | let unit = matches.value_of("temperature").unwrap(); 9 | let raw_temp_vec = temp().unwrap(); 10 | // TODO: Is there a better way to handle this without cloning? 11 | let raw_temp = raw_temp_vec[0].clone(); 12 | if unit == "C" { 13 | raw_temp.1 + "*C" 14 | } else if unit == "F" { 15 | let pre = raw_temp.1.parse::().unwrap() * 9.0 / 5.0 + 32.0; 16 | pre.to_string() + "*F" 17 | } else { 18 | format!("N/A ({} is not a supported unit)", unit) 19 | } 20 | } 21 | 22 | fn the_memory() -> String { 23 | let used_memory = memory_used().unwrap(); 24 | let total_memory = memory_total().unwrap(); 25 | format!("{used_memory} (used)/{total_memory} (total)") 26 | } 27 | 28 | #[cfg(feature = "pretty_output")] 29 | pub fn main(matches: ArgMatches) { 30 | let corner: char = if matches.is_present("corner") { 31 | matches.value_of("corner").unwrap().parse::().unwrap() 32 | } else { 33 | '+' 34 | }; 35 | let mut table = prettytable::Table::new(); 36 | let format = prettytable::format::FormatBuilder::new() 37 | .column_separator('│') 38 | .borders('│') 39 | .padding(1, 1) 40 | .separators( 41 | &[prettytable::format::LinePosition::Top], 42 | prettytable::format::LineSeparator::new('─', '─', corner, corner), 43 | ) 44 | .separators( 45 | &[prettytable::format::LinePosition::Bottom], 46 | prettytable::format::LineSeparator::new('─', '─', corner, corner), 47 | ) 48 | .build(); 49 | table.set_format(format); 50 | if matches.is_present("cpu") { 51 | if matches.is_present("temperature") { 52 | let temp = the_temp(&matches); 53 | let row = format!( 54 | "{} [{}]", 55 | cpu() 56 | .unwrap_or_else(|_| "N/A (could not read /proc/cpuinfo)".to_string()) 57 | .trim(), 58 | temp 59 | ); 60 | table.add_row(row!["CPU", &row]); 61 | } else { 62 | table.add_row(row![ 63 | "CPU", 64 | &cpu().unwrap_or_else(|_| "N/A (could not read /proc/cpuinfo)".to_string()) 65 | ]); 66 | } 67 | } 68 | if matches.is_present("device") { 69 | table.add_row(row!["Device", &device().unwrap_or_else(|_| "N/A (could not read /sys/devices/virtual/dmi/id/product_name nor /sys/firmware/devicetree/base/model)".to_string())]); 70 | } 71 | if matches.is_present("distro") { 72 | table.add_row(row!["Distro", &distro().unwrap_or_else(|_| "N/A (could not read /bedrock/etc/os-release, /etc/os-release, nor /usr/lib/os-release)".to_string())]); 73 | } 74 | if matches.is_present("editor") { 75 | table.add_row(row!["Editor", &env("EDITOR").unwrap()]); 76 | } 77 | if matches.is_present("environment") { 78 | table.add_row(row!["Environment", &environment().unwrap()]); 79 | } 80 | if matches.is_present("gpu") { 81 | let gpus = &gpu().unwrap(); 82 | for gpu in gpus { 83 | table.add_row(row!["GPU", gpu]); 84 | } 85 | } 86 | if matches.is_present("hostname") { 87 | table.add_row(row![ 88 | "Hostname", 89 | &hostname().unwrap_or_else(|_| "N/A (could not read /etc/hostname)".to_string()) 90 | ]); 91 | } 92 | if matches.is_present("kernel") { 93 | table.add_row(row![ 94 | "Kernel", 95 | &kernel() 96 | .unwrap_or_else(|_| "N/A (could not read /proc/sys/kernel/osrelease)".to_string()) 97 | ]); 98 | } 99 | if matches.is_present("memory") { 100 | table.add_row(row![ 101 | "Memory", 102 | &the_memory() 103 | ]); 104 | } 105 | if matches.is_present("packages") { 106 | let manager = matches.value_of("packages").unwrap(); 107 | table.add_row(row![ 108 | "Packages", 109 | &packages(manager).unwrap_or_else(|_| format!("N/A (could not run {})", manager)) 110 | ]); 111 | } 112 | if matches.is_present("shell") { 113 | table.add_row(row!["Shell", &env("SHELL").unwrap()]); 114 | } 115 | if matches.is_present("terminal") { 116 | table.add_row(row![ 117 | "Terminal", 118 | &terminal().unwrap_or_else( 119 | |_| "N/A (could not read the appropriate /proc/?/status)".to_string() 120 | ) 121 | ]); 122 | } 123 | if matches.is_present("uptime") { 124 | table.add_row(row![ 125 | "Uptime", 126 | &uptime().unwrap_or_else(|_| "N/A (could not read /proc/uptime)".to_string()) 127 | ]); 128 | } 129 | if matches.is_present("user") { 130 | table.add_row(row!["User", &env("USER").unwrap()]); 131 | } 132 | if matches.is_present("music") { 133 | #[cfg(any(feature = "music_mpd", feature = "music_playerctl"))] 134 | table.add_row(row!["Music", &music().unwrap()]); 135 | 136 | #[cfg(not(any(feature = "music_mpd", feature = "music_playerctl")))] 137 | table.add_row(row!["Music", &music()]); 138 | } 139 | table.printstd(); 140 | } 141 | 142 | #[cfg(not(feature = "pretty_output"))] 143 | pub fn main(matches: ArgMatches) { 144 | if matches.is_present("cpu") { 145 | if matches.is_present("temperature") { 146 | println!( 147 | "CPU: {} [{}]", 148 | cpu().unwrap_or_else(|_| "N/A (could not read /proc/cpuinfo)".to_string()), 149 | the_temp(&matches) 150 | ); 151 | } else { 152 | println!( 153 | "CPU: {}", 154 | cpu().unwrap_or_else(|_| "N/A (could not read /proc/cpuinfo)".to_string()) 155 | ); 156 | } 157 | } 158 | if matches.is_present("device") { 159 | println!("Device: {}", device().unwrap_or_else(|_| 160 | "N/A (could not read /sys/devices/virtual/dmi/id/product_name nor /sys/firmware/devicetree/base/model)" 161 | .to_string() 162 | )); 163 | } 164 | if matches.is_present("distro") { 165 | println!("Distro: {}", distro().unwrap_or_else(|_| 166 | "N/A (could not read /bedrock/etc/os-release, /etc/os-release, nor /usr/lib/os-release)" 167 | .to_string() 168 | )); 169 | } 170 | if matches.is_present("editor") { 171 | println!("Editor: {}", env("EDITOR").unwrap()); 172 | } 173 | if matches.is_present("environment") { 174 | println!("Environment: {}", environment().unwrap()); 175 | } 176 | if matches.is_present("gpu") { 177 | let gpus = gpu().unwrap(); 178 | for gpu in gpus { 179 | println!("GPU: {}", gpu); 180 | } 181 | } 182 | if matches.is_present("hostname") { 183 | println!( 184 | "Hostname: {}", 185 | hostname().unwrap_or_else(|_| "N/A (could not read /etc/hostname)".to_string()) 186 | ); 187 | } 188 | if matches.is_present("kernel") { 189 | println!( 190 | "Kernel: {}", 191 | kernel() 192 | .unwrap_or_else(|_| "N/A (could not read /proc/sys/kernel/osrelease)".to_string()) 193 | ); 194 | } 195 | if matches.is_present("memory") { 196 | println!( 197 | "Memory: {}", 198 | the_memory() 199 | ); 200 | } 201 | if matches.is_present("packages") { 202 | let manager = matches.value_of("packages").unwrap(); 203 | println!( 204 | "Packages: {}", 205 | packages(manager).unwrap_or_else(|_| format!("N/A (could not run {})", manager)) 206 | ); 207 | } 208 | if matches.is_present("shell") { 209 | println!("Shell: {}", env("SHELL").unwrap()); 210 | } 211 | if matches.is_present("terminal") { 212 | println!( 213 | "Terminal: {}", 214 | terminal().unwrap_or_else( 215 | |_| "N/A (could not read the appropriate /proc/?/status)".to_string() 216 | ) 217 | ); 218 | } 219 | if matches.is_present("uptime") { 220 | println!( 221 | "Uptime: {}", 222 | uptime().unwrap_or_else(|_| "N/A (could not read /proc/uptime)".to_string()) 223 | ); 224 | } 225 | if matches.is_present("user") { 226 | println!("User: {}", env("USER").unwrap()); 227 | } 228 | if matches.is_present("music") { 229 | #[cfg(any(feature = "music_mpd", feature = "music_playerctl"))] 230 | println!( 231 | "Music: {}", 232 | &music() 233 | .unwrap_or_else(|_| "N/A".to_string()) 234 | ); 235 | 236 | #[cfg(not(any(feature = "music_mpd", feature = "music_playerctl")))] 237 | println!("Music: {}", music()); 238 | } 239 | } 240 | --------------------------------------------------------------------------------