1use std::collections::HashMap;
2use std::env;
3use std::fmt;
4use std::fs;
5use std::io::Write;
6use std::path::{Path, PathBuf};
7
8use anyhow::Result;
9use serde::Serialize;
10use serde_json::json;
11
12#[derive(Debug)]
13pub struct Workflow {
14 items: Vec<Item>,
15}
16
17impl Workflow {
18 pub fn new<F>(f: F) -> Self
19 where
20 F: Fn() -> Result<Vec<Item>>,
21 {
22 let items = f().unwrap_or_else(|err| {
23 let mut items: Vec<_> = err
24 .chain()
25 .map(|cause| Item::new(cause.to_string()))
26 .collect();
27 if let Ok(error_icon) = error_icon() {
28 items[0].icon = Some(error_icon);
29 }
30 items
31 });
32
33 Workflow { items }
34 }
35}
36
37impl fmt::Display for Workflow {
38 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39 write!(f, "{}", json!({ "items": self.items }))
40 }
41}
42
43#[derive(Clone, Debug, Serialize)]
44pub struct Item {
45 #[serde(skip_serializing_if = "Option::is_none")]
46 uid: Option<String>,
47 title: String,
48 #[serde(skip_serializing_if = "Option::is_none")]
49 subtitle: Option<String>,
50 #[serde(skip_serializing_if = "Option::is_none")]
51 arg: Option<String>,
52 #[serde(skip_serializing_if = "Option::is_none")]
53 icon: Option<Icon>,
54 #[serde(skip_serializing_if = "Option::is_none")]
55 valid: Option<bool>,
56 #[serde(skip_serializing_if = "Option::is_none")]
57 variables: Option<HashMap<String, String>>,
58}
59
60impl Item {
61 pub fn new<S: Into<String>>(title: S) -> Self {
62 Item {
63 uid: None,
64 title: title.into(),
65 subtitle: None,
66 arg: None,
67 icon: None,
68 valid: None,
69 variables: None,
70 }
71 }
72
73 pub fn uid(mut self, uid: &str) -> Self {
74 self.uid = Some(uid.into());
75 self
76 }
77
78 pub fn subtitle(mut self, subtitle: &str) -> Self {
79 self.subtitle = Some(subtitle.into());
80 self
81 }
82
83 pub fn arg(mut self, arg: &str) -> Self {
84 self.arg = Some(arg.into());
85 self
86 }
87
88 pub fn icon<I: Into<Icon>>(mut self, icon: I) -> Self {
89 self.icon = Some(icon.into());
90 self
91 }
92
93 pub fn valid(mut self, valid: bool) -> Self {
94 self.valid = Some(valid);
95 self
96 }
97
98 pub fn variables(mut self, variables: HashMap<String, String>) -> Self {
99 self.variables = Some(variables);
100 self
101 }
102}
103
104#[derive(Clone, Debug, Serialize)]
105pub struct Icon {
106 pub path: PathBuf,
107}
108
109impl<'a> From<&'a str> for Icon {
110 fn from(s: &str) -> Self {
111 let path = s.into();
112 Self { path }
113 }
114}
115
116impl<'a> From<&'a Path> for Icon {
117 fn from(p: &Path) -> Self {
118 let path = p.into();
119 Self { path }
120 }
121}
122
123fn cache_path() -> Result<PathBuf> {
124 let var = env::var("alfred_workflow_cache")?;
125 let path = PathBuf::from(var);
126 if !path.exists() {
127 fs::create_dir_all(&path)?;
128 }
129 Ok(path)
130}
131
132pub fn cached<F>(file_name: &str, f: F) -> Result<PathBuf>
133where
134 F: Fn() -> Result<Vec<u8>>,
135{
136 let file_path = cache_path()?.join(file_name);
137 if file_path.exists() {
138 return Ok(file_path);
139 }
140
141 let data = f()?;
142 let mut file = fs::File::create(file_path.clone())?;
143 file.write_all(&data)?;
144 Ok(file_path)
145}
146
147fn error_icon() -> Result<Icon> {
148 let error_png = include_bytes!("error.png");
149
150 let path_buf = cached(".alphred.error", || Ok(error_png.to_vec()))?;
151 Ok(path_buf.as_path().into())
152}