Compare commits

..

9 Commits
dev ... main

Author SHA1 Message Date
f7366898a4 Temporary fix for bag color erasure 2026-07-16 13:33:19 +05:30
7297a78fba Use Updated Library 2026-07-16 13:07:27 +05:30
905d3489f7 Improve Homedir detection
- Try passwd file
- Handle cases of empty strings
2026-01-23 11:31:38 +05:30
6fe31a8e46 Cargo fmt formatting 2026-01-23 11:01:06 +05:30
be7d75995e Update ssh env var checking
- Add `SSH_CLIENT`
- Change from `std::env::var` to `std::env::var_os`
2026-01-23 10:54:56 +05:30
e3b71a2afb Better way to check for root user 2026-01-22 22:22:54 +05:30
a2b1b1fbfb Reduce binary size for profile "release"
- Adjust `Cargo.toml`
2025-12-12 18:58:14 +05:30
58ecbd6a75 Seperated prompt and prompt-pwd into 2 binaries 2025-01-19 15:57:08 +05:30
d4d783580d Merge pull request 'dev' (#1) from dev into main
Reviewed-on: #1
2024-12-02 04:26:42 +00:00
10 changed files with 151 additions and 99 deletions

46
Cargo.lock generated
View File

@ -3,39 +3,39 @@
version = 4
[[package]]
name = "ansi_term"
version = "0.12.1"
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"winapi",
"windows-sys",
]
[[package]]
name = "prettyprompt"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"ansi_term",
"libc",
"nu-ansi-term",
]
[[package]]
name = "winapi"
version = "0.3.9"
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
"windows-link",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -1,7 +1,24 @@
[package]
name = "prettyprompt"
version = "0.2.0"
edition = "2021"
version = "0.3.0"
edition = "2024"
[dependencies]
ansi_term = "0.12"
#ansi_term = "0.12"
libc = "0.2.180"
nu-ansi-term = "0.50.3"
[[bin]]
name = "prettyprompt"
path = "src/prettyprompt.rs"
[[bin]]
name = "prettyprompt-pwd"
path = "src/prettyprompt_pwd.rs"
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true

View File

@ -35,7 +35,7 @@ A pretty shell prompt, written in rust.
## Current Limitations
- **Hard-Coded Configuration**: User customization is not available yet.
- **Exit Code Requirement**: Must pass the last command's exit code as a command-line argument.
- **Exit Code Requirement**: Must pass the last commands exit code as a command-line argument.
## Tested on
@ -59,7 +59,7 @@ cargo build --release
**Step 2. Add to `$PATH`**
- Option 1. Move the binary to a directory in your `$PATH`. Eg:
```bash
sudo mv target/release/prettyprompt /usr/bin/
sudo mv target/release/prettyprompt target/release/prettyprompt-pwd /usr/bin/
```
- Option 2. Add the directory containing the binary to `$PATH`
System-wide: `/etc/profile`
@ -96,13 +96,11 @@ end
```
### `zsh`
Use the precmd function to set the `PROMPT` variable with the output of `prettyprompt $?` as its value.
Export the `PS1` variable with the output of `prettyprompt $?` as its value.
User-specific: `~/.zshrc`
System-wide: `/etc/zsh/zshrc`
```zsh
precmd() {
PROMPT="$(prettyprompt $?)" # Notice the double quotes
}
```sh
export PS1='$(prettyprompt $?)' # Notice the single quotes
```
### Other shells

View File

@ -1,12 +1,12 @@
use ansi_term::ANSIGenericString;
use ansi_term::Colour::RGB;
use nu_ansi_term::AnsiGenericString;
use nu_ansi_term::Color::Rgb;
pub const ERR_SYMBOL: &str = "\u{276F}"; // Error indicator symbol: ""
pub const ERR_COL: ansi_term::Colour = RGB(255, 53, 94); // Error
pub const NORMIE_COL: ansi_term::Colour = RGB(0, 255, 180); // Success
pub const ERR_COL: nu_ansi_term::Color = Rgb(255, 53, 94); // Error
pub const NORMIE_COL: nu_ansi_term::Color = Rgb(0, 255, 180); // Success
// Error indicator
pub fn indicator(args: &[String]) -> ANSIGenericString<'static, str> {
pub fn indicator(args: &[String]) -> AnsiGenericString<'static, str> {
let exit_code = args.get(1).map_or("0", String::as_str); // Default to "0" (success, no error) if arg missing
if exit_code == "0" {
NORMIE_COL.paint(ERR_SYMBOL) // Success

View File

@ -1,13 +1,13 @@
use ansi_term::ANSIGenericString;
use ansi_term::Colour::RGB;
use nu_ansi_term::AnsiGenericString;
use nu_ansi_term::Color::Rgb;
use std::path::Path;
use std::process::Command;
pub const GIT_SYMBOL: &str = "\u{276F}"; // Git indicator symbol: ""
pub const MAIN_COL: ansi_term::Colour = RGB(178, 98, 44);
pub const DEV_COL: ansi_term::Colour = RGB(54, 159, 150);
pub const DEFAULT_BRANCH_COL: ansi_term::Colour = RGB(255, 255, 255);
pub const NORMIE_COL: ansi_term::Colour = RGB(82, 82, 82);
pub const MAIN_COL: nu_ansi_term::Color = Rgb(178, 98, 44);
pub const DEV_COL: nu_ansi_term::Color = Rgb(54, 159, 150);
pub const DEFAULT_BRANCH_COL: nu_ansi_term::Color = Rgb(255, 255, 255);
pub const NORMIE_COL: nu_ansi_term::Color = Rgb(82, 82, 82);
/// Returns the repo's root and branch, if present
pub fn info() -> Option<(String, String)> {
@ -36,7 +36,7 @@ pub fn repo_name(path: &str) -> String {
}
/// Git branch indicator
pub fn indicator(branch: Option<String>) -> ANSIGenericString<'static, str> {
pub fn indicator(branch: Option<String>) -> AnsiGenericString<'static, str> {
match branch {
Some(b) => match b.as_str() {
"main" => MAIN_COL.paint(GIT_SYMBOL),

View File

@ -1,15 +1,26 @@
use crate::indicators::git::{repo_name, MAIN_COL};
use ansi_term::ANSIGenericString;
use ansi_term::Colour::RGB;
use crate::indicators::git::{MAIN_COL, repo_name};
use nu_ansi_term::AnsiGenericString;
use nu_ansi_term::Color::Rgb;
use std::env::current_dir;
pub const UNKNOWN_PATH: &str = "\u{2248}"; // "≈"
pub const PATH_COL: ansi_term::Colour = RGB(82, 82, 82);
pub const REPO_COL: ansi_term::Colour = RGB(55, 120, 130);
pub const PATH_COL: nu_ansi_term::Color = Rgb(82, 82, 82);
pub const REPO_COL: nu_ansi_term::Color = Rgb(55, 120, 130);
/// Find the current user's home directory.
fn home_dir() -> String {
std::env::var("HOME").map_or_else(|_| String::new(), |path| path.to_string())
fn home_dir() -> Option<String> {
// Try `passwd` file first
unsafe {
let pw = libc::getpwuid(libc::geteuid());
if !pw.is_null() {
let dir = std::ffi::CStr::from_ptr((*pw).pw_dir);
if let Ok(s) = dir.to_str() {
return Some(s.to_owned());
}
}
}
// Fallback: `$HOME` env var
std::env::var("HOME").ok()
}
/// Returns the full path of the current directory as a string.
@ -27,7 +38,12 @@ fn remove_repo(pwd_path: &str, repo_path: &str) -> String {
/// Replace the 'home directory' part of the path with a the '~' symbol
fn replace_home(path: &str) -> String {
path.replacen(&home_dir(), "~", 1)
if let Some(home) = home_dir() {
if path.starts_with(&home) {
return path.replacen(&home, "~", 1);
}
}
path.to_owned()
}
fn short(path: &str, slash_limit: u8) -> String {
@ -57,7 +73,7 @@ pub fn pwd(
shorten_path: bool,
replace_repo: bool,
git_repo: Option<String>,
) -> ANSIGenericString<'static, str> {
) -> AnsiGenericString<'static, str> {
let mut slash_limit: u8 = 3; // Max number of slashes
let mut path = full_path(); // Get the full path of he current directory
@ -74,11 +90,19 @@ pub fn pwd(
REPO_COL.paint(repo_name) // In the root dir of the repo
} else {
// In a subdir inside the repo
format!(
/*format!(
"{}{}{}",
REPO_COL.paint(repo_name), // Repo name
MAIN_COL.paint(" \u{F02A2} "), // Seperator
MAIN_COL.paint(" \u{F02A2} "), // Separator
PATH_COL.italic().paint(path) // Sub-directory
)*/
format!(
"\x1b[38;2;55;120;130m{}\
\x1b[38;2;178;98;44m \u{F02A2} \
\x1b[3;38;2;82;82;82m{}\
\x1b[39;23m",
repo_name,
path
)
.into()
};

View File

@ -1,19 +1,21 @@
use ansi_term::ANSIGenericString;
use ansi_term::Colour::RGB;
use std::env::var; // For environment variables
use nu_ansi_term::AnsiGenericString;
use nu_ansi_term::Color::Rgb;
use std::env::var_os; // For environment variables
pub const SSH_SYMBOL: &str = "\u{276F}"; // SSH indicator symbol: ""
pub const SSH_COL: ansi_term::Colour = RGB(255, 149, 0); // In SSH session
pub const NORMIE_COL: ansi_term::Colour = RGB(82, 82, 82); // Non-SSH session
const SSH_ENV_VARS: [&str; 2] = ["SSH_TTY", "SSH_CONNECTION"]; // Environment variables normally present in SSH sessions
pub const SSH_COL: nu_ansi_term::Color = Rgb(255, 149, 0); // In SSH session
pub const NORMIE_COL: nu_ansi_term::Color = Rgb(82, 82, 82); // Non-SSH session
const SSH_ENV_VARS: [&str; 3] = ["SSH_TTY", "SSH_CONNECTION", "SSH_CLIENT"]; // Environment variables normally present in SSH sessions
/// Checks if current session is an SSH session
fn is_ssh_session() -> bool {
SSH_ENV_VARS.iter().any(|&var_name| var(var_name).is_ok()) // any() iterates through the iter and stops at first `true`. Equal to `||`(`or` condition)
SSH_ENV_VARS
.iter()
.any(|&var_name| var_os(var_name).is_some()) // any() iterates through the iter and stops at first `true`. Equal to `||`(`or` condition)
}
/// SSH shell indicator
pub fn indicator() -> ANSIGenericString<'static, str> {
pub fn indicator() -> AnsiGenericString<'static, str> {
let is_ssh: bool = is_ssh_session();
if is_ssh {
SSH_COL.paint(SSH_SYMBOL)

View File

@ -1,21 +1,20 @@
use ansi_term::ANSIGenericString;
use ansi_term::Colour::RGB;
use std::env::var; // For environment variables
use nu_ansi_term::AnsiGenericString;
use nu_ansi_term::Color::Rgb;
use libc::geteuid;
pub const USER_SYMBOL: &str = "\u{276F}"; // User indicator symbol: ""
pub const ROOT_COL: ansi_term::Colour = RGB(255, 53, 94); // If the user is root
pub const NORMIE_COL: ansi_term::Colour = RGB(0, 255, 180); // Regular user
const ROOT_USER: &str = "root"; // Root username constant
pub const ROOT_COL: nu_ansi_term::Color = Rgb(255, 53, 94); // If the user is root
pub const NORMIE_COL: nu_ansi_term::Color = Rgb(0, 255, 180); // Regular user
/// Username of current user
pub fn username() -> String {
var("USER").unwrap_or_else(|_| "UnknownUser".to_string())
// Check if the current user is root
pub fn is_root() -> bool {
unsafe { geteuid() == 0 } // Uid for root is 0
}
/// Root user indicator
pub fn indicator() -> ANSIGenericString<'static, str> {
let user = username();
if user == ROOT_USER {
pub fn indicator() -> AnsiGenericString<'static, str> {
if is_root() {
ROOT_COL.paint(USER_SYMBOL)
} else {
NORMIE_COL.paint(USER_SYMBOL)

View File

@ -1,19 +1,18 @@
mod indicators {
pub mod error;
pub mod git;
pub mod pwd;
pub mod ssh;
pub mod user;
}
use crate::indicators::{error, git, pwd, ssh, user};
use ansi_term::{ANSIGenericString, ANSIGenericStrings};
use crate::indicators::{error, git, ssh, user};
use nu_ansi_term::{AnsiGenericString, AnsiGenericStrings};
// Add a component to the prompt if the condition is true.
fn add_component(
components: &mut Vec<ANSIGenericString<'static, str>>, // Vector to hold components of the prompt.
components: &mut Vec<AnsiGenericString<'static, str>>, // Vector to hold components of the prompt.
condition: bool, // Condition to add the component
component_fn: impl FnOnce() -> ANSIGenericString<'static, str>, // Function to create the component(takes no arguments, returns `ANSIGenericString`) is passed here.
component_fn: impl FnOnce() -> AnsiGenericString<'static, str>, // Function to create the component(takes no arguments, returns `AnsiGenericString`) is passed here.
) {
if condition {
components.push(component_fn()); // Push the generated component to the vector.
@ -24,20 +23,16 @@ fn main() {
let cmd_args: Vec<String> = std::env::args().collect(); // Command-line args
// Vector to hold the different parts of the prompt.
let mut components: Vec<ANSIGenericString<'static, str>> = Vec::new(); // The components will be concatenated into a single string in the end.
let mut components: Vec<AnsiGenericString<'static, str>> = Vec::new(); // The components will be concatenated into a single string in the end.
// Hard-coded configuration. This will be replaced by a configuration file in a future version
let indicate_user: bool = true;
let indicate_ssh: bool = true;
let indicate_err: bool = true;
let indicate_git_branch: bool = true;
let show_pwd: bool = true;
let abbrev_home: bool = true;
let shorten_path: bool = true;
let replace_repo: bool = true;
// Conditionally fetch Git-related info if required, or set to None
let git_info: Option<(String, String)> = if indicate_git_branch || replace_repo {
let git_info: Option<(String, String)> = if indicate_git_branch {
git::info()
} else {
None
@ -53,17 +48,8 @@ fn main() {
error::indicator(&cmd_args)
});
// Insert `pwd` at the beginning of the prompt
if show_pwd {
let repo_path = git_info.map(|info| info.0);
components.insert(
0,
pwd::pwd(abbrev_home, shorten_path, replace_repo, repo_path),
);
}
// Finally, combine the parts into a single prompts string
let prompt: ANSIGenericStrings<'_, str> = ANSIGenericStrings(&components[..]);
let prompt: AnsiGenericStrings<'_, str> = AnsiGenericStrings(&components[..]);
// `print!()` prevents an extra newline, unlike `println!()`
print!("{prompt} "); // A trailing space for aesthetic formatting.
}

26
src/prettyprompt_pwd.rs Normal file
View File

@ -0,0 +1,26 @@
mod indicators {
pub mod git;
pub mod pwd;
}
use crate::indicators::{git, pwd};
fn main() {
// Hard-coded configuration. This will be replaced by a configuration file in a future version
let indicate_git_branch: bool = true;
let abbrev_home: bool = true;
let shorten_path: bool = true;
let replace_repo: bool = true;
// Conditionally fetch Git-related info if required, or set to None
let git_info: Option<(String, String)> = if indicate_git_branch || replace_repo {
git::info()
} else {
None
};
let repo_path = git_info.map(|info| info.0);
let promt_pwd = pwd::pwd(abbrev_home, shorten_path, replace_repo, repo_path);
print!("{promt_pwd}"); // A trailing space for aesthetic formatting.
}