Skip to content

Commit 786d7c6

Browse files
committed
Support variables and cache settings
1 parent 1050921 commit 786d7c6

File tree

1 file changed

+212
-53
lines changed

1 file changed

+212
-53
lines changed

src/lib.rs

Lines changed: 212 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
//! # }
3131
//! ```
3232
33-
use std::collections::HashMap;
33+
use std::collections::{BTreeMap, HashMap};
3434
use std::io;
3535
use std::path::PathBuf;
3636
use std::time::Duration;
@@ -149,6 +149,11 @@ struct Data {
149149
/// Mark whether the item is valid when the modifier is pressed.
150150
#[serde(skip_serializing_if = "Option::is_none")]
151151
valid: Option<bool>,
152+
153+
/// Variables which are passed out of the script filter object if this
154+
/// modifier is used to action the result.
155+
#[serde(skip_serializing_if = "Option::is_none")]
156+
variables: Option<BTreeMap<String, String>>,
152157
}
153158

154159
/// The modifier settings for an [`Item`] when a modifier key is pressed.
@@ -161,20 +166,32 @@ pub struct Modifier {
161166
data: Data,
162167
}
163168

169+
/// The cache settings for an [`Output`].
170+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
171+
pub struct Cache {
172+
/// The cache duration in seconds.
173+
#[serde(serialize_with = "duration_as_secs")]
174+
seconds: Duration,
175+
176+
/// Whether to show the cache and call the script in the background as well.
177+
#[serde(rename = "loosereload", skip_serializing_if = "Option::is_none")]
178+
loose_reload: Option<bool>,
179+
}
180+
164181
/// An Alfred script filter item.
165182
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
166183
pub struct Item {
184+
/// A unique identifier for the item.
185+
#[serde(skip_serializing_if = "Option::is_none")]
186+
uid: Option<String>,
187+
167188
/// The title displayed in the result row.
168189
title: String,
169190

170191
/// The subtitle displayed in the result row.
171192
#[serde(skip_serializing_if = "Option::is_none")]
172193
subtitle: Option<String>,
173194

174-
/// A unique identifier for the item.
175-
#[serde(skip_serializing_if = "Option::is_none")]
176-
uid: Option<String>,
177-
178195
/// The argument which is passed through to the output.
179196
#[serde(skip_serializing_if = "Option::is_none")]
180197
arg: Option<Arg>,
@@ -203,6 +220,10 @@ pub struct Item {
203220
#[serde(rename = "mods", skip_serializing_if = "HashMap::is_empty")]
204221
modifiers: HashMap<Keys, Data>,
205222

223+
/// The Universal Action items used when actioning the result.
224+
#[serde(skip_serializing_if = "Value::is_null")]
225+
action: Value,
226+
206227
/// Defines the copied or large type text for this item.
207228
#[serde(skip_serializing_if = "Option::is_none")]
208229
text: Option<Text>,
@@ -211,24 +232,40 @@ pub struct Item {
211232
#[serde(rename = "quicklookurl", skip_serializing_if = "Option::is_none")]
212233
quicklook_url: Option<String>,
213234

214-
#[serde(skip_serializing_if = "Value::is_null")]
215-
action: Value,
235+
/// Variables which are passed out of the script filter object if this
236+
/// modifier is used to action the result.
237+
#[serde(skip_serializing_if = "Option::is_none")]
238+
variables: Option<BTreeMap<String, String>>,
216239
}
217240

218-
/// The output of a workflow (i.e. input for the script filter)
241+
/// The Alfred script filter workflow output.
242+
///
243+
/// A script filter is required to return zero or more [`Item`]s. Each [`Item`]
244+
/// describes a result row displayed in Alfred. The three obvious elements are
245+
/// the ones you see in an Alfred result row - [`Item::new`], [`Item::subtitle`]
246+
/// and [`Item::icon`].
219247
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
220248
pub struct Output {
221249
/// The interval in seconds after which to rerun the script filter.
222250
#[serde(
223251
skip_serializing_if = "Option::is_none",
224-
serialize_with = "duration_as_secs"
252+
serialize_with = "option_duration_as_secs"
225253
)]
226254
rerun: Option<Duration>,
227255

228256
/// Whether to skip Alfred's knowledge for this output.
229257
#[serde(rename = "skipknowledge", skip_serializing_if = "Option::is_none")]
230258
skip_knowledge: Option<bool>,
231259

260+
/// Variables which are passed out of the script filter object if this
261+
/// modifier is used to action the result.
262+
#[serde(skip_serializing_if = "Option::is_none")]
263+
variables: Option<BTreeMap<String, String>>,
264+
265+
/// The cache settings for this output.
266+
#[serde(skip_serializing_if = "Option::is_none")]
267+
cache: Option<Cache>,
268+
232269
/// Each row item.
233270
items: Vec<Item>,
234271
}
@@ -399,24 +436,69 @@ impl Modifier {
399436
self.data.valid = Some(valid);
400437
self
401438
}
439+
440+
/// Override the variables when the item is actioned with this modifier.
441+
///
442+
/// See [`Output::variables`] for more information.
443+
///
444+
/// - If not set, inherits the item variables.
445+
///
446+
/// - If set, overrides the item's variables.
447+
/// ```
448+
/// let m = Modifier::new(Key::Command).variables([("key1", "value1")]);
449+
/// ```
450+
///
451+
/// - If set to an empty object, no variables are passed out.
452+
/// ```
453+
/// let m = Modifier::new(Key::Command).variables([]);
454+
/// ```
455+
#[must_use]
456+
pub fn variables<K, V>(mut self, variables: impl IntoIterator<Item = (K, V)>) -> Self
457+
where
458+
K: Into<String>,
459+
V: Into<String>,
460+
{
461+
self.data.variables = Some(
462+
variables
463+
.into_iter()
464+
.map(|(k, v)| (k.into(), v.into()))
465+
.collect(),
466+
);
467+
self
468+
}
402469
}
403470

404-
impl Item {
405-
/// Create a new item with the provided title.
471+
impl Cache {
472+
/// Create a new cache with the given duration.
406473
#[must_use]
407-
pub fn new(title: impl Into<String>) -> Self {
474+
pub fn new(duration: Duration) -> Self {
408475
Self {
409-
title: title.into(),
476+
seconds: duration,
410477
..Self::default()
411478
}
412479
}
413480

414-
/// Set the subtitle for this item.
481+
/// Set the loose reload value.
482+
///
483+
/// If set then Alfred will try to show any cached data first. If it's
484+
/// determined to be stale, the script runs in the background and replaces
485+
/// results with the new data when it becomes available.
415486
#[must_use]
416-
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
417-
self.subtitle = Some(subtitle.into());
487+
pub fn loose_reload(mut self, loose_reload: bool) -> Self {
488+
self.loose_reload = Some(loose_reload);
418489
self
419490
}
491+
}
492+
493+
impl Item {
494+
/// Create a new item with the provided title.
495+
#[must_use]
496+
pub fn new(title: impl Into<String>) -> Self {
497+
Self {
498+
title: title.into(),
499+
..Self::default()
500+
}
501+
}
420502

421503
/// Set the UID for this item.
422504
///
@@ -434,6 +516,13 @@ impl Item {
434516
self
435517
}
436518

519+
/// Set the subtitle for this item.
520+
#[must_use]
521+
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
522+
self.subtitle = Some(subtitle.into());
523+
self
524+
}
525+
437526
/// Set the argument which is passed through the workflow to the connected
438527
/// output action.
439528
///
@@ -464,7 +553,7 @@ impl Item {
464553
/// Set the icon displayed in the result row.
465554
///
466555
/// Workflows are run from their workflow folder, so you can reference icons
467-
/// stored in your workflow relatively.
556+
/// stored in your workflow directory.
468557
#[must_use]
469558
pub fn icon(mut self, icon: Icon) -> Self {
470559
self.icon = Some(icon);
@@ -519,40 +608,6 @@ impl Item {
519608
self
520609
}
521610

522-
/// Set the text the user will get when copying the selected result row with
523-
/// ⌘C or displaying large type with ⌘L.
524-
///
525-
/// If these are not defined, you will inherit Alfred's standard behaviour
526-
/// where the arg is copied to the Clipboard or used for Large Type.
527-
#[must_use]
528-
pub fn copy_text(mut self, copy: impl Into<String>) -> Self {
529-
self.text.get_or_insert_with(Text::default).copy = Some(copy.into());
530-
self
531-
}
532-
533-
/// Set the text the user will get when displaying large type with ⌘L.
534-
///
535-
/// If this is not defined, you will inherit Alfred's standard behaviour
536-
/// where the arg is used for Large Type.
537-
#[must_use]
538-
pub fn large_type_text(mut self, large_type: impl Into<String>) -> Self {
539-
self.text.get_or_insert_with(Text::default).large_type = Some(large_type.into());
540-
self
541-
}
542-
543-
/// Set the Quick Look URL for the item.
544-
///
545-
/// This will be visible if the user uses the Quick Look feature within
546-
/// Alfred (tapping shift, or ⌘Y). This field will also accept a file path,
547-
/// both absolute and relative to home using ~/.
548-
///
549-
/// If absent, Alfred will attempt to use the arg as the quicklook URL.
550-
#[must_use]
551-
pub fn quicklook_url(mut self, quicklook_url: impl Into<String>) -> Self {
552-
self.quicklook_url = Some(quicklook_url.into());
553-
self
554-
}
555-
556611
/// Add a modifier key configuration.
557612
///
558613
/// This gives you control over how the modifier keys react. For example you
@@ -607,18 +662,91 @@ impl Item {
607662
self.action = action.into();
608663
self
609664
}
665+
666+
/// Set the text the user will get when copying the selected result row with
667+
/// ⌘C or displaying large type with ⌘L.
668+
///
669+
/// If these are not defined, you will inherit Alfred's standard behaviour
670+
/// where the arg is copied to the Clipboard or used for Large Type.
671+
#[must_use]
672+
pub fn copy_text(mut self, copy: impl Into<String>) -> Self {
673+
self.text.get_or_insert_with(Text::default).copy = Some(copy.into());
674+
self
675+
}
676+
677+
/// Set the text the user will get when displaying large type with ⌘L.
678+
///
679+
/// If this is not defined, you will inherit Alfred's standard behaviour
680+
/// where the arg is used for Large Type.
681+
#[must_use]
682+
pub fn large_type_text(mut self, large_type: impl Into<String>) -> Self {
683+
self.text.get_or_insert_with(Text::default).large_type = Some(large_type.into());
684+
self
685+
}
686+
687+
/// Set the Quick Look URL for the item.
688+
///
689+
/// This will be visible if the user uses the Quick Look feature within
690+
/// Alfred (tapping shift, or ⌘Y). This field will also accept a file path,
691+
/// both absolute and relative to home using ~/.
692+
///
693+
/// If absent, Alfred will attempt to use the arg as the quicklook URL.
694+
#[must_use]
695+
pub fn quicklook_url(mut self, quicklook_url: impl Into<String>) -> Self {
696+
self.quicklook_url = Some(quicklook_url.into());
697+
self
698+
}
699+
700+
/// Override the variables when the item is actioned.
701+
///
702+
/// See [`Output::variables`] for more information.
703+
///
704+
/// - If not set, inherits the output variables.
705+
///
706+
/// - If set, overrides the output variables.
707+
/// ```
708+
/// let item = Item::new("title").variables([("key1", "value1")]);
709+
/// ```
710+
///
711+
/// - If set to an empty object, no variables are passed out.
712+
/// ```
713+
/// let item = Item::new("title").variables([]);
714+
/// ```
715+
#[must_use]
716+
pub fn variables<K, V>(mut self, variables: impl IntoIterator<Item = (K, V)>) -> Self
717+
where
718+
K: Into<String>,
719+
V: Into<String>,
720+
{
721+
self.variables = Some(
722+
variables
723+
.into_iter()
724+
.map(|(k, v)| (k.into(), v.into()))
725+
.collect(),
726+
);
727+
self
728+
}
610729
}
611730

612-
fn duration_as_secs<S>(duration: &Option<Duration>, s: S) -> Result<S::Ok, S::Error>
731+
#[inline]
732+
fn option_duration_as_secs<S>(duration: &Option<Duration>, s: S) -> Result<S::Ok, S::Error>
613733
where
614734
S: Serializer,
615735
{
616736
match duration {
617-
Some(d) => s.serialize_f32(d.as_secs_f32()),
737+
Some(d) => duration_as_secs(d, s),
618738
None => unreachable!(),
619739
}
620740
}
621741

742+
#[inline]
743+
fn duration_as_secs<S>(duration: &Duration, s: S) -> Result<S::Ok, S::Error>
744+
where
745+
S: Serializer,
746+
{
747+
s.serialize_f32(duration.as_secs_f32())
748+
}
749+
622750
impl Output {
623751
/// Create a new output.
624752
#[must_use]
@@ -647,6 +775,37 @@ impl Output {
647775
self
648776
}
649777

778+
/// Set the variables which are passed out of the script filter object.
779+
///
780+
/// These remain accessible throughout the current session as environment
781+
/// variables. In addition, they are passed back in when the script reruns
782+
/// within the same session. This can be used for managing state between
783+
/// runs as the user types input or when the script is set to re-run after
784+
/// an interval.
785+
///
786+
/// These can be overridden on a per-item or per-modifier basis. See
787+
/// [`Item::variables`] and [`Modifier::variables`].
788+
#[must_use]
789+
pub fn variables<K, V>(mut self, variables: impl IntoIterator<Item = (K, V)>) -> Self
790+
where
791+
K: Into<String>,
792+
V: Into<String>,
793+
{
794+
self.variables = Some(
795+
variables
796+
.into_iter()
797+
.map(|(k, v)| (k.into(), v.into()))
798+
.collect(),
799+
);
800+
self
801+
}
802+
803+
/// Set the cache settings for this output.
804+
pub fn cache(&mut self, cache: Cache) -> &mut Self {
805+
self.cache = Some(cache);
806+
self
807+
}
808+
650809
/// Extend the list of items to output.
651810
pub fn items<I>(&mut self, iter: I) -> &mut Self
652811
where

0 commit comments

Comments
 (0)