Skip to content

Commit e331280

Browse files
authored
Use prompt from pyvenv.cfg as name for venv (#207)
Extract the prompt from the `pyvenv.cfg` file to use as the name for the virtual environment. This change enhances the identification of virtual environments by utilizing the prompt defined in the configuration. Fixes #191
1 parent 845945b commit e331280

File tree

4 files changed

+86
-29
lines changed

4 files changed

+86
-29
lines changed

.vscode/settings.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
"git.branchProtection": [
1111
"main",
1212
"release/*"
13-
]
14-
}
13+
],
14+
"git.branchProtectionPrompt": "alwaysCommitToNewBranch"
15+
}

crates/pet-core/src/os_environment.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,5 @@ fn get_user_home() -> Option<PathBuf> {
152152
}
153153

154154
fn get_env_var(key: String) -> Option<String> {
155-
match env::var(key) {
156-
Ok(path) => Some(path),
157-
Err(_) => None,
158-
}
155+
env::var(key).ok()
159156
}

crates/pet-core/src/pyvenv_cfg.rs

Lines changed: 75 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,21 @@ pub struct PyVenvCfg {
2222
pub version: String,
2323
pub version_major: u64,
2424
pub version_minor: u64,
25+
pub prompt: Option<String>,
2526
}
2627

2728
impl PyVenvCfg {
28-
fn new(version: String, version_major: u64, version_minor: u64) -> Self {
29+
fn new(
30+
version: String,
31+
version_major: u64,
32+
version_minor: u64,
33+
prompt: Option<String>,
34+
) -> Self {
2935
Self {
3036
version,
3137
version_major,
3238
version_minor,
39+
prompt,
3340
}
3441
}
3542
pub fn find(path: &Path) -> Option<Self> {
@@ -79,40 +86,85 @@ fn find(path: &Path) -> Option<PathBuf> {
7986

8087
fn parse(file: &Path) -> Option<PyVenvCfg> {
8188
let contents = fs::read_to_string(file).ok()?;
89+
let mut version: Option<String> = None;
90+
let mut version_major: Option<u64> = None;
91+
let mut version_minor: Option<u64> = None;
92+
let mut prompt: Option<String> = None;
93+
8294
for line in contents.lines() {
83-
if !line.contains("version") {
84-
continue;
95+
if version.is_none() {
96+
if let Some((ver, major, minor)) = parse_version(line, &VERSION) {
97+
version = Some(ver);
98+
version_major = Some(major);
99+
version_minor = Some(minor);
100+
continue;
101+
}
102+
if let Some((ver, major, minor)) = parse_version(line, &VERSION_INFO) {
103+
version = Some(ver);
104+
version_major = Some(major);
105+
version_minor = Some(minor);
106+
continue;
107+
}
85108
}
86-
if let Some(cfg) = parse_version(line, &VERSION) {
87-
return Some(cfg);
109+
if prompt.is_none() {
110+
if let Some(p) = parse_prompt(line) {
111+
prompt = Some(p);
112+
}
88113
}
89-
if let Some(cfg) = parse_version(line, &VERSION_INFO) {
90-
return Some(cfg);
114+
if version.is_some() && prompt.is_some() {
115+
break;
91116
}
92117
}
93-
None
118+
119+
match (version, version_major, version_minor) {
120+
(Some(ver), Some(major), Some(minor)) => Some(PyVenvCfg::new(ver, major, minor, prompt)),
121+
_ => None,
122+
}
94123
}
95124

96-
fn parse_version(line: &str, regex: &Regex) -> Option<PyVenvCfg> {
125+
fn parse_version(line: &str, regex: &Regex) -> Option<(String, u64, u64)> {
97126
if let Some(captures) = regex.captures(line) {
98127
if let Some(value) = captures.get(1) {
99128
let version = value.as_str();
100-
let parts: Vec<&str> = version.splitn(3, ".").take(2).collect();
101-
// .expect() below is OK because the version regex
102-
// guarantees there are at least two digits.
103-
let version_major = parts[0]
104-
.parse()
105-
.expect("python major version to be an integer");
106-
let version_minor = parts[1]
107-
.parse()
108-
.expect("python minor version to be an integer");
109-
return Some(PyVenvCfg::new(
110-
version.to_string(),
111-
version_major,
112-
version_minor,
113-
));
129+
let parts: Vec<&str> = version.split('.').collect();
130+
if parts.len() >= 2 {
131+
let version_major = parts[0]
132+
.parse()
133+
.expect("python major version to be an integer");
134+
let version_minor = parts[1]
135+
.parse()
136+
.expect("python minor version to be an integer");
137+
return Some((version.to_string(), version_major, version_minor));
138+
}
114139
}
115140
}
141+
None
142+
}
116143

144+
fn parse_prompt(line: &str) -> Option<String> {
145+
let trimmed = line.trim();
146+
if trimmed.starts_with("prompt") {
147+
if let Some(eq_idx) = trimmed.find('=') {
148+
// let value = trimmed[eq_idx + 1..].trim();
149+
let mut name = trimmed[eq_idx + 1..].trim().to_string();
150+
// Strip any leading or trailing single or double quotes
151+
if name.starts_with('"') {
152+
name = name.trim_start_matches('"').to_string();
153+
}
154+
if name.ends_with('"') {
155+
name = name.trim_end_matches('"').to_string();
156+
}
157+
// Strip any leading or trailing single or double quotes
158+
if name.starts_with('\'') {
159+
name = name.trim_start_matches('\'').to_string();
160+
}
161+
if name.ends_with('\'') {
162+
name = name.trim_end_matches('\'').to_string();
163+
}
164+
if !name.is_empty() {
165+
return Some(name);
166+
}
167+
}
168+
}
117169
None
118170
}

crates/pet-venv/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,15 @@ impl Locator for Venv {
6363
if let Some(ref prefix) = prefix {
6464
symlinks.append(&mut find_executables(prefix));
6565
}
66+
67+
// Get the name from the prefix if it exists.
68+
let cfg = PyVenvCfg::find(env.executable.parent()?)
69+
.or_else(|| PyVenvCfg::find(&env.prefix.clone()?));
70+
let name = cfg.and_then(|cfg| cfg.prompt);
71+
6672
Some(
6773
PythonEnvironmentBuilder::new(Some(PythonEnvironmentKind::Venv))
74+
.name(name)
6875
.executable(Some(env.executable.clone()))
6976
.version(version)
7077
.prefix(prefix)

0 commit comments

Comments
 (0)