1#![forbid(clippy::style)]
2#![forbid(clippy::perf)]
3#![deny(clippy::pedantic)]
4#![forbid(double_negations)]
5#![forbid(clippy::allow_attributes_without_reason)]
6
7#[cfg(windows)] mod windows;
8#[cfg(unix)] mod xdg;
9
10use std::ffi::OsStr;
11use thiserror::Error;
12use std::path::{Path, PathBuf};
13use std::string::FromUtf16Error;
14
15#[derive(Debug, Error)]
16pub enum Error {
17 #[error("Unsupported platform")]
18 Unsupported,
19 #[error("IO: {0}")]
20 Io(#[from] std::io::Error),
21 #[error("UTF-16: {0}")]
22 Utf16(#[from] FromUtf16Error)
23}
24
25#[derive(Debug, Clone, Hash, Eq, PartialEq)]
26pub struct Link<'a>(Options<'a>);
27
28#[allow(unreachable_code, reason = "Fallback for uncovered platforms")]
29const fn get_platform() -> &'static dyn Platform {
30 #[cfg(windows)] return &windows::Windows;
31 #[cfg(unix)] if !cfg!(target_os = "macos") {
32 return &xdg::Xdg;
33 }
34
35 &Unsupported
36}
37
38impl<'a> Link<'a> {
40 #[must_use]
42 pub fn targeting(target: &'a Path) -> Self {
43 let mut options = Options {
44 target: Path::new(""),
45 at: PathBuf::default(),
46 working_directory: None,
47 icon: None,
48 arguments: None,
49 name: None,
50 };
51 options.target = target;
52 Self(options)
53 }
54
55 #[must_use]
57 pub fn set_name(&mut self, name: &'a str) -> &mut Self {
58 self.0.name = Some(name);
59 self
60 }
61
62 #[must_use]
64 pub fn set_icon(&mut self, icon: &'a Path) -> &mut Self {
65 self.0.icon = Some(icon);
66 self
67 }
68
69 pub fn set_arguments(&mut self, arguments: &'a str) -> &mut Self {
71 self.0.arguments = Some(arguments);
72 self
73 }
74
75 pub fn set_working_directory(&mut self, working_directory: &'a Path) -> &mut Self {
77 self.0.working_directory = Some(working_directory);
78 self
79 }
80
81 pub fn save(&mut self, at: &Path) -> Result<(), Error> {
88 at.clone_into(&mut self.0.at);
89 get_platform().create(&mut self.0)
90 }
91
92 pub fn save_to_menu(&mut self, name: &OsStr) -> Result<(), Error> {
97 self.save(&get_platform().get_menu_path(name)?)
98 }
99}
100
101#[derive(Debug, Clone, Hash, Eq, PartialEq)]
102struct Options<'a> {
103 name: Option<&'a str>,
104 target: &'a Path,
105 arguments: Option<&'a str>,
106 at: PathBuf,
107 icon: Option<&'a Path>,
108 working_directory: Option<&'a Path>,
109}
110
111trait Platform {
112 fn create(&self, options: &mut Options) -> Result<(), Error>;
113
114 fn get_menu_path(&self, name: &OsStr) -> Result<PathBuf, Error>;
115}
116
117struct Unsupported;
118
119impl Platform for Unsupported {
120 fn create(&self, _: &mut Options) -> Result<(), Error> {
121 Err(Error::Unsupported)
122 }
123
124 fn get_menu_path(&self, _: &OsStr) -> Result<PathBuf, Error> {
125 Err(Error::Unsupported)
126 }
127}