diff --git a/crates/rattler_menuinst/Cargo.toml b/crates/rattler_menuinst/Cargo.toml index c4050e060..c8f646baf 100644 --- a/crates/rattler_menuinst/Cargo.toml +++ b/crates/rattler_menuinst/Cargo.toml @@ -26,6 +26,8 @@ tempfile = { workspace = true } fs-err = { workspace = true } which = "7.0.0" known-folders = "1.2.0" +quick-xml = "0.37.1" +chrono = { workspace = true, features = ["clock"] } [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.3.9" diff --git a/crates/rattler_menuinst/src/lib.rs b/crates/rattler_menuinst/src/lib.rs index f8b6efbc3..5c0ad0001 100644 --- a/crates/rattler_menuinst/src/lib.rs +++ b/crates/rattler_menuinst/src/lib.rs @@ -39,6 +39,10 @@ pub enum MenuInstError { PlistError(#[from] plist::Error), #[error("Failed to sign plist: {0}")] SigningFailed(String), + + #[cfg(target_os = "linux")] + #[error("Failed to install menu item: {0}")] + XmlError(#[from] quick_xml::Error), } // Install menu items from a given schema file @@ -53,6 +57,11 @@ pub fn install_menuitems( let menu_inst: MenuInstSchema = serde_json::from_str(&text)?; let placeholders = BaseMenuItemPlaceholders::new(base_prefix, prefix, platform); + // if platform.is_linux() { + // #[cfg(target_os = "linux")] + // linux::install_menu(&menu_inst.menu_name, prefix, menu_mode)?; + // } + for item in menu_inst.menu_items { if platform.is_linux() { #[cfg(target_os = "linux")] diff --git a/crates/rattler_menuinst/src/linux.rs b/crates/rattler_menuinst/src/linux.rs index 8147c17f4..09b073124 100644 --- a/crates/rattler_menuinst/src/linux.rs +++ b/crates/rattler_menuinst/src/linux.rs @@ -6,6 +6,8 @@ use std::path::{Path, PathBuf}; use std::process::Command; use tempfile::TempDir; +mod menu_xml; + use rattler_conda_types::Platform; use rattler_shell::activation::{ActivationVariables, Activator}; use rattler_shell::shell; @@ -92,6 +94,24 @@ impl Directories { } } + pub fn ensure_directories_exist(&self) -> Result<(), MenuInstError> { + let paths = vec![ + self.menu_config_location.parent().unwrap().to_path_buf(), + self.desktop_entries_location.clone(), + self.directory_entry_location + .parent() + .unwrap() + .to_path_buf(), + ]; + + for path in paths { + tracing::debug!("Ensuring path {} exists", path.display()); + fs::create_dir_all(path)?; + } + + Ok(()) + } + pub fn desktop_file(&self) -> PathBuf { self.desktop_entries_location.join(format!( "{}_{}.desktop", @@ -101,6 +121,146 @@ impl Directories { } } +// pub struct LinuxMenuDirectory { +// name: String, +// directories: Directories, +// mode: MenuMode, +// } + +// impl LinuxMenuDirectory { +// pub fn new(directories: Directories) -> Self { +// // TODO +// let name = "test".to_string(); +// Self { name, directories, mode: MenuMode::User } +// } + +// pub fn create(&self) -> Result { +// self.directories.ensure_directories_exist()?; +// let path = self.write_directory_entry()?; + +// if self.is_valid_menu_file()? && self.has_this_menu()? { +// return Ok(path); +// } + +// self.ensure_menu_file()?; +// self.add_this_menu()?; +// Ok(path) +// } + +// pub fn remove(&self) -> Result { +// fs::remove_file(&self.directories.directory_entry_location).ok(); + +// // Check if any shortcuts still exist +// for entry in fs::read_dir(&self.directories.desktop_entries_location)? { +// let entry = entry?; +// let filename = entry.file_name(); +// let filename = filename.to_string_lossy(); +// if filename.starts_with(&format!("{}_", slugify(&self.directories.menu_name))) { +// return Ok(self.directories.directory_entry_location.clone()); +// } +// } + +// self.remove_this_menu()?; +// Ok(self.directories.directory_entry_location.clone()) +// } + +// fn write_directory_entry(&self) -> Result { +// let file = &self.directories.directory_entry_location; +// tracing::info!("Creating directory entry at {:?}", file); +// let writer = File::create(file)?; +// let mut writer = std::io::BufWriter::new(writer); + +// writeln!(writer, "[Desktop Entry]")?; +// writeln!(writer, "Type=Directory")?; +// writeln!(writer, "Encoding=UTF-8")?; +// writeln!(writer, "Name={}", self.name)?; + +// Ok(file.clone()) +// } + +// fn is_valid_menu_file(&self) -> Result { +// if !self.directories.menu_config_location.exists() { +// return Ok(false); +// } + +// let file = File::open(&self.directories.menu_config_location)?; +// let mut reader = Reader::from_reader(file); +// let mut buf = Vec::new(); + +// // Check if first element is Menu +// match reader.read_event_into(&mut buf)? { +// Event::Start(e) if e.name().as_ref() == b"Menu" => Ok(true), +// _ => Ok(false), +// } +// } + +// fn has_this_menu(&self) -> Result { +// let file = File::open(&self.directories.menu_config_location)?; +// let mut reader = Reader::from_reader(file); +// let mut buf = Vec::new(); +// let mut inside_menu = false; + +// loop { +// match reader.read_event_into(&mut buf)? { +// Event::Start(e) if e.name().as_ref() == b"Menu" => { +// inside_menu = true; +// } +// Event::Start(e) if e.name().as_ref() == b"Name" && inside_menu => { +// if let Event::Text(t) = reader.read_event_into(&mut buf)? { +// if t.unescape()?.into_owned() == self.directories.name { +// return Ok(true); +// } +// } +// } +// Event::Eof => break, +// _ => (), +// } +// buf.clear(); +// } +// Ok(false) +// } + +// fn ensure_menu_file(&self) -> Result<()> { +// if self.directories.menu_config_location.exists() && !self.directories.menu_config_location.is_file() { +// return Err(anyhow!("Menu config location is not a file!")); +// } + +// if self.directories.menu_config_location.is_file() { +// let now = Local::now(); +// let backup = format!("{}.{}", +// self.directories.menu_config_location.display(), +// now.format("%Y-%m-%d_%Hh%Mm%S") +// ); +// fs::copy(&self.directories.menu_config_location, backup)?; + +// if !self.is_valid_menu_file()? { +// fs::remove_file(&self.directories.menu_config_location)?; +// } +// } + +// if !self.directories.menu_config_location.exists() { +// self.new_menu_file()?; +// } +// Ok(()) +// } + +// fn new_menu_file(&self) -> Result<()> { +// tracing::info!("Creating {}", self.directories.menu_config_location.display()); +// let mut contents = String::from("Applications"); + +// if self.mode == MenuMode::User { +// contents.push_str(&format!( +// "{}", +// self.directories.system_menu_config_location.display() +// )); +// } +// contents.push_str("\n"); + +// fs::write(&self.directories.menu_config_location, contents)?; +// Ok(()) +// } +// } + impl LinuxMenu { fn new( menu_name: &str, @@ -188,20 +348,6 @@ impl LinuxMenu { res } - fn create_directory_entry(&self) -> Result<(), MenuInstError> { - let file = &self.directories.directory_entry_location; - tracing::info!("Creating directory entry at {:?}", file); - let writer = File::create(file)?; - let mut writer = std::io::BufWriter::new(writer); - - writeln!(writer, "[Desktop Entry]")?; - writeln!(writer, "Type=Directory")?; - writeln!(writer, "Encoding=UTF-8")?; - writeln!(writer, "Name={}", self.name)?; - - Ok(()) - } - fn create_desktop_entry(&self) -> Result<(), MenuInstError> { let file = self.location(); let writer = File::create(file)?; diff --git a/crates/rattler_menuinst/src/linux/menu_xml.rs b/crates/rattler_menuinst/src/linux/menu_xml.rs new file mode 100644 index 000000000..79770311c --- /dev/null +++ b/crates/rattler_menuinst/src/linux/menu_xml.rs @@ -0,0 +1,391 @@ +use chrono::Utc; +use fs_err::{self as fs, File}; +use quick_xml::events::Event; +use quick_xml::{Reader, Writer}; +use std::io::{BufReader, Write}; +use std::path::PathBuf; + +use crate::{slugify, MenuInstError, MenuMode}; + +pub struct MenuXml { + menu_config_location: PathBuf, + system_menu_config_location: PathBuf, + name: String, + mode: MenuMode, +} + +impl MenuXml { + pub fn new( + menu_config_location: PathBuf, + system_menu_config_location: PathBuf, + name: String, + mode: MenuMode, + ) -> Self { + Self { + menu_config_location, + system_menu_config_location, + name, + mode, + } + } + + pub fn try_open(path: &PathBuf) -> Result>, MenuInstError> { + let file = File::open(path)?; + let buf_reader = BufReader::new(file); + + Ok(Reader::from_reader(buf_reader)) + } + + pub fn remove_menu(&self) -> Result<(), MenuInstError> { + tracing::info!( + "Editing {} to remove {} config", + self.menu_config_location.display(), + self.name + ); + + let xml_content = fs::read_to_string(&self.menu_config_location)?; + let mut reader = Reader::from_str(&xml_content); + + let mut writer = Writer::new(Vec::new()); + let mut buf = Vec::new(); + let mut skip_menu = false; + let mut depth = 0; + + loop { + match reader.read_event_into(&mut buf)? { + Event::DocType(_) | Event::Text(_) if depth == 0 => continue, + Event::Start(e) => { + if e.name().as_ref() == b"Menu" { + depth += 1; + if depth == 1 { + // Always write the root Menu element + writer.write_event(Event::Start(e))?; + } else { + // Check if this is our target menu + let mut inner_buf = Vec::new(); + let mut is_target = false; + while let Ok(inner_event) = reader.read_event_into(&mut inner_buf) { + match inner_event { + Event::Start(se) if se.name().as_ref() == b"Name" => { + if let Event::Text(t) = reader.read_event_into(&mut inner_buf)? { + if t.unescape()?.into_owned() == self.name { + is_target = true; + break; + } + } + break; + } + Event::End(ee) if ee.name().as_ref() == b"Menu" => break, + _ => continue, + } + } + if !is_target { + writer.write_event(Event::Start(e))?; + } else { + skip_menu = true; + } + } + } else if !skip_menu { + writer.write_event(Event::Start(e))?; + } + } + Event::End(e) => { + if e.name().as_ref() == b"Menu" { + depth -= 1; + if depth == 0 || !skip_menu { + writer.write_event(Event::End(e))?; + } + if skip_menu && depth == 0 { + skip_menu = false; + } + } else if !skip_menu { + writer.write_event(Event::End(e))?; + } + } + Event::Text(e) if !skip_menu => { + writer.write_event(Event::Text(e))?; + } + Event::Eof => break, + e => { + if !skip_menu { + writer.write_event(e)?; + } + } + } + buf.clear(); + } + + self.write_menu_file(&writer.into_inner()) + } + + pub fn has_menu(&self) -> Result { + let mut reader = Self::try_open(&self.menu_config_location)?; + let mut buf = Vec::new(); + + loop { + match reader.read_event_into(&mut buf)? { + Event::Start(e) if e.name().as_ref() == b"Menu" => { + if self.is_target_menu(&mut reader, &mut buf)? { + return Ok(true); + } + } + Event::Eof => break, + _ => (), + } + buf.clear(); + } + Ok(false) + } + + pub fn add_menu(&self) -> Result<(), MenuInstError> { + tracing::info!( + "Editing {} to add {} config", + self.menu_config_location.display(), + self.name + ); + + let mut content = fs::read_to_string(&self.menu_config_location)?; + // let insert_pos = content.rfind("").ok_or_else(|| anyhow!("Invalid XML"))?; + let insert_pos = content.rfind("").unwrap(); + + let menu_entry = format!( + r#" + {} + {}.directory + + {} + + +"#, + self.name, + slugify(&self.name), + self.name + ); + + content.insert_str(insert_pos, &menu_entry); + self.write_menu_file(content.as_bytes()) + } + + pub fn is_valid_menu_file(&self) -> bool { + if let Ok(reader) = Self::try_open(&self.menu_config_location) { + let mut buf = Vec::new(); + let mut reader = reader; + + if let Ok(event) = reader.read_event_into(&mut buf) { + match event { + Event::Start(e) => return e.name().as_ref() == b"Menu", + _ => return false, + } + } + } + false + } + + fn write_menu_file(&self, content: &[u8]) -> Result<(), MenuInstError> { + tracing::info!("Writing {}", self.menu_config_location.display()); + let mut file = File::create(&self.menu_config_location)?; + + file.write_all(b"\n")?; + file.write_all(content)?; + file.write_all(b"\n")?; + Ok(()) + } + + pub fn ensure_menu_file(&self) -> Result<(), MenuInstError> { + if self.menu_config_location.exists() && !self.menu_config_location.is_file() { + panic!( + "Menu config location {} is not a file!", + self.menu_config_location.display() + ); + // return Err(anyhow!("Menu config location {} is not a file!", + // self.menu_config_location.display())); + } + + if self.menu_config_location.is_file() { + let timestamp = Utc::now().format("%Y-%m-%d_%Hh%Mm%S").to_string(); + let backup = format!("{}.{}", self.menu_config_location.display(), timestamp); + fs::copy(&self.menu_config_location, &backup)?; + + if !self.is_valid_menu_file() { + fs::remove_file(&self.menu_config_location)?; + } + } + + if !self.menu_config_location.exists() { + self.new_menu_file()?; + } + Ok(()) + } + + fn new_menu_file(&self) -> Result<(), MenuInstError> { + tracing::info!("Creating {}", self.menu_config_location.display()); + let mut content = String::from("Applications"); + + if self.mode == MenuMode::User { + content.push_str(&format!( + "{}", + self.system_menu_config_location.display() + )); + } + content.push_str("\n"); + + fs::write(&self.menu_config_location, content)?; + Ok(()) + } + + fn is_target_menu( + &self, + reader: &mut Reader, + buf: &mut Vec, + ) -> Result { + loop { + match reader.read_event_into(buf)? { + Event::Start(e) if e.name().as_ref() == b"Name" => { + if let Event::Text(t) = reader.read_event_into(buf)? { + return Ok(t.unescape()?.into_owned() == self.name); + } + } + Event::End(e) if e.name().as_ref() == b"Menu" => break, + _ => (), + } + } + Ok(false) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::TempDir; + + fn setup_test_dir() -> (TempDir, MenuXml) { + let temp_dir = TempDir::new().unwrap(); + let menu_config = temp_dir.path().join("applications.menu"); + let system_menu_config = temp_dir.path().join("system_applications.menu"); + + let menu_xml = MenuXml::new( + menu_config, + system_menu_config, + "Test Menu".to_string(), + MenuMode::User, + ); + + (temp_dir, menu_xml) + } + + #[test] + fn test_new_menu_file() { + let (_temp_dir, menu_xml) = setup_test_dir(); + menu_xml.new_menu_file().unwrap(); + assert!(menu_xml.is_valid_menu_file()); + } + + #[test] + fn test_add_and_remove_menu() { + let (_temp_dir, menu_xml) = setup_test_dir(); + menu_xml.new_menu_file().unwrap(); + + let system_menu_location = menu_xml.system_menu_config_location.display().to_string(); + + let res = fs::read_to_string(&menu_xml.menu_config_location).unwrap(); + let res = res.replace(&system_menu_location, "/path/to/system_menu"); + insta::assert_snapshot!(res); + + // Add menu + menu_xml.add_menu().unwrap(); + assert!(menu_xml.has_menu().unwrap()); + + let res = fs::read_to_string(&menu_xml.menu_config_location).unwrap(); + let res = res.replace(&system_menu_location, "/path/to/system_menu"); + insta::assert_snapshot!(res); + + // Remove menu + menu_xml.remove_menu().unwrap(); + + let res = fs::read_to_string(&menu_xml.menu_config_location).unwrap(); + let res = res.replace(&system_menu_location, "/path/to/system_menu"); + insta::assert_snapshot!(res); + assert!(!menu_xml.has_menu().unwrap()); + } + + #[test] + fn test_invalid_menu_file() { + let (_temp_dir, menu_xml) = setup_test_dir(); + + // Write invalid content + fs::write(&menu_xml.menu_config_location, "XML").unwrap(); + assert!(!menu_xml.is_valid_menu_file()); + } + + #[test] + fn test_ensure_menu_file() { + let (_temp_dir, menu_xml) = setup_test_dir(); + + // Test with non-existent file + menu_xml.ensure_menu_file().unwrap(); + assert!(menu_xml.menu_config_location.exists()); + assert!(menu_xml.is_valid_menu_file()); + + // Test with invalid file + fs::write(&menu_xml.menu_config_location, "XML").unwrap(); + menu_xml.ensure_menu_file().unwrap(); + assert!(menu_xml.is_valid_menu_file()); + } + + #[test] + fn test_remove_menu_xml_structure() { + let (_temp_dir, menu_xml) = setup_test_dir(); + + // Create initial menu file with content + let initial_content = r#" + + Applications + /path/to/system_menu + + Test Menu + test-menu.directory + + Test Menu + + + "#; + + fs::write(&menu_xml.menu_config_location, initial_content).unwrap(); + + // Remove the menu + menu_xml.remove_menu().unwrap(); + + // Read and verify the result + let result = fs::read_to_string(&menu_xml.menu_config_location).unwrap(); + + insta::assert_snapshot!(result); + } + + #[test] + // load file from test data (example.menu) and add a new entry, then remove it + fn test_add_and_remove_menu_xml_structure() { + let (_temp_dir, menu_xml) = setup_test_dir(); + + let test_data = crate::test::test_data(); + let schema_path = test_data.join("linux-menu/example.menu"); + + // Copy the example.menu file to the menu location + fs::copy(&schema_path, &menu_xml.menu_config_location).unwrap(); + + // Add the menu + menu_xml.add_menu().unwrap(); + + // Read and verify the result + let result = fs::read_to_string(&menu_xml.menu_config_location).unwrap(); + insta::assert_snapshot!(result); + + // Remove the menu + menu_xml.remove_menu().unwrap(); + + // Read and verify the result + let result = fs::read_to_string(&menu_xml.menu_config_location).unwrap(); + insta::assert_snapshot!(result); + } +} diff --git a/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu-2.snap b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu-2.snap new file mode 100644 index 000000000..c122a0a2c --- /dev/null +++ b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu-2.snap @@ -0,0 +1,14 @@ +--- +source: crates/rattler_menuinst/src/linux/menu_xml.rs +expression: res +--- + +Applications/path/to/system_menu + Test Menu + test-menu.directory + + Test Menu + + + diff --git a/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu-3.snap b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu-3.snap new file mode 100644 index 000000000..5e127145a --- /dev/null +++ b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu-3.snap @@ -0,0 +1,7 @@ +--- +source: crates/rattler_menuinst/src/linux/menu_xml.rs +expression: res +--- + +Applications/path/to/system_menu diff --git a/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu.snap b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu.snap new file mode 100644 index 000000000..ae45911f1 --- /dev/null +++ b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu.snap @@ -0,0 +1,5 @@ +--- +source: crates/rattler_menuinst/src/linux/menu_xml.rs +expression: res +--- +Applications/path/to/system_menu diff --git a/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu_xml_structure-2.snap b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu_xml_structure-2.snap new file mode 100644 index 000000000..6dc8da3ff --- /dev/null +++ b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu_xml_structure-2.snap @@ -0,0 +1,14 @@ +--- +source: crates/rattler_menuinst/src/linux/menu_xml.rs +expression: result +--- + + +Applications + + shinythings-webmirror.directory + + shinythings-webmirror.desktop + shinythings-webmirror-admin.desktop + diff --git a/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu_xml_structure.snap b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu_xml_structure.snap new file mode 100644 index 000000000..9471af7c2 --- /dev/null +++ b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__add_and_remove_menu_xml_structure.snap @@ -0,0 +1,25 @@ +--- +source: crates/rattler_menuinst/src/linux/menu_xml.rs +expression: result +--- + + + +Applications + + WebMirror + shinythings-webmirror.directory + + shinythings-webmirror.desktop + shinythings-webmirror-admin.desktop + + + Test Menu + test-menu.directory + + Test Menu + + + diff --git a/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__remove_menu_xml_structure.snap b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__remove_menu_xml_structure.snap new file mode 100644 index 000000000..a9dfafda7 --- /dev/null +++ b/crates/rattler_menuinst/src/linux/snapshots/rattler_menuinst__linux__menu_xml__tests__remove_menu_xml_structure.snap @@ -0,0 +1,10 @@ +--- +source: crates/rattler_menuinst/src/linux/menu_xml.rs +expression: result +--- + + + Applications + /path/to/system_menu + diff --git a/crates/rattler_menuinst/src/macos.rs b/crates/rattler_menuinst/src/macos.rs index ae7532fe1..d87aeb8ad 100644 --- a/crates/rattler_menuinst/src/macos.rs +++ b/crates/rattler_menuinst/src/macos.rs @@ -740,4 +740,4 @@ pub(crate) fn remove_menu_item( ) -> Result, MenuInstError> { let menu = MacOSMenu::new(prefix, macos_item, command, menu_mode, placeholders); menu.remove() -} \ No newline at end of file +} diff --git a/crates/rattler_menuinst/src/windows.rs b/crates/rattler_menuinst/src/windows.rs index 5f6d8252e..6f25b5111 100644 --- a/crates/rattler_menuinst/src/windows.rs +++ b/crates/rattler_menuinst/src/windows.rs @@ -1,9 +1,15 @@ -use std::{io::Write as _, path::{Path, PathBuf}}; +use std::{ + io::Write as _, + path::{Path, PathBuf}, +}; use knownfolders::UserHandle; use crate::{ - render::{BaseMenuItemPlaceholders, MenuItemPlaceholders}, schema::{Environment, MenuItemCommand, Windows}, utils::quote_args, MenuInstError, MenuMode + render::{BaseMenuItemPlaceholders, MenuItemPlaceholders}, + schema::{Environment, MenuItemCommand, Windows}, + utils::quote_args, + MenuInstError, MenuMode, }; use fs_err as fs; @@ -93,7 +99,12 @@ impl WindowsMenu { // TODO handle activation } - let args = self.command.command.iter().map(|elem| elem.resolve(&self.placeholders)).collect(); + let args = self + .command + .command + .iter() + .map(|elem| elem.resolve(&self.placeholders)) + .collect(); lines.push(lex::quote_args(args).join(" ")); lines.join("\n") @@ -111,18 +122,17 @@ impl WindowsMenu { } fn write_script(&self, path: Option) -> Result<(), MenuInstError> { - let path = path.unwrap_or_else(|| { - self.prefix.join("Menu").join(self.shortcut_filename(None)) - }); + let path = + path.unwrap_or_else(|| self.prefix.join("Menu").join(self.shortcut_filename(None))); if let Some(parent) = path.parent() { fs::create_dir_all(parent)?; } - + let mut file = fs::File::create(&path)?; file.write_all(self.script_content().as_bytes())?; - - Ok(()) + + Ok(()) } fn build_command(&self, with_arg1: bool) { @@ -147,7 +157,6 @@ impl WindowsMenu { } fn create_shortcut(&self, link_path: &Path, args: &str) -> Result<(), MenuInstError> { - // target_path, *arguments = self._process_command() // working_dir = self.render_key("working_dir") // if working_dir: diff --git a/crates/rattler_menuinst/src/windows/lex.rs b/crates/rattler_menuinst/src/windows/lex.rs index 8c20fd195..188f93de5 100644 --- a/crates/rattler_menuinst/src/windows/lex.rs +++ b/crates/rattler_menuinst/src/windows/lex.rs @@ -1,6 +1,7 @@ pub fn quote_args(args: Vec) -> Vec { if args.len() > 2 - && (args[0].to_uppercase().contains("CMD.EXE") || args[0].to_uppercase().contains("%COMSPEC%")) + && (args[0].to_uppercase().contains("CMD.EXE") + || args[0].to_uppercase().contains("%COMSPEC%")) && (args[1].to_uppercase() == "/K" || args[1].to_uppercase() == "/C") && args[2..].iter().any(|arg| arg.contains(' ')) { @@ -38,7 +39,7 @@ pub fn ensure_pad(name: &str, pad: char) -> String { #[cfg(test)] mod tests { - use super::{quote_args, quote_string, ensure_pad}; + use super::{ensure_pad, quote_args, quote_string}; #[test] fn test_quote_args() { @@ -71,4 +72,4 @@ mod tests { assert_eq!(ensure_pad("_conda_", '_'), "_conda_"); assert_eq!(ensure_pad("", '_'), ""); } -} \ No newline at end of file +} diff --git a/test-data/menuinst/linux-menu/example.menu b/test-data/menuinst/linux-menu/example.menu new file mode 100644 index 000000000..25e296c90 --- /dev/null +++ b/test-data/menuinst/linux-menu/example.menu @@ -0,0 +1,12 @@ + + +Applications + + WebMirror + shinythings-webmirror.directory + + shinythings-webmirror.desktop + shinythings-webmirror-admin.desktop + +