30
30
//! # }
31
31
//! ```
32
32
33
- use std:: collections:: HashMap ;
33
+ use std:: collections:: { BTreeMap , HashMap } ;
34
34
use std:: io;
35
35
use std:: path:: PathBuf ;
36
36
use std:: time:: Duration ;
@@ -149,6 +149,11 @@ struct Data {
149
149
/// Mark whether the item is valid when the modifier is pressed.
150
150
#[ serde( skip_serializing_if = "Option::is_none" ) ]
151
151
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 > > ,
152
157
}
153
158
154
159
/// The modifier settings for an [`Item`] when a modifier key is pressed.
@@ -161,20 +166,32 @@ pub struct Modifier {
161
166
data : Data ,
162
167
}
163
168
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
+
164
181
/// An Alfred script filter item.
165
182
#[ derive( Debug , Clone , Default , PartialEq , Eq , Serialize ) ]
166
183
pub struct Item {
184
+ /// A unique identifier for the item.
185
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
186
+ uid : Option < String > ,
187
+
167
188
/// The title displayed in the result row.
168
189
title : String ,
169
190
170
191
/// The subtitle displayed in the result row.
171
192
#[ serde( skip_serializing_if = "Option::is_none" ) ]
172
193
subtitle : Option < String > ,
173
194
174
- /// A unique identifier for the item.
175
- #[ serde( skip_serializing_if = "Option::is_none" ) ]
176
- uid : Option < String > ,
177
-
178
195
/// The argument which is passed through to the output.
179
196
#[ serde( skip_serializing_if = "Option::is_none" ) ]
180
197
arg : Option < Arg > ,
@@ -203,6 +220,10 @@ pub struct Item {
203
220
#[ serde( rename = "mods" , skip_serializing_if = "HashMap::is_empty" ) ]
204
221
modifiers : HashMap < Keys , Data > ,
205
222
223
+ /// The Universal Action items used when actioning the result.
224
+ #[ serde( skip_serializing_if = "Value::is_null" ) ]
225
+ action : Value ,
226
+
206
227
/// Defines the copied or large type text for this item.
207
228
#[ serde( skip_serializing_if = "Option::is_none" ) ]
208
229
text : Option < Text > ,
@@ -211,24 +232,40 @@ pub struct Item {
211
232
#[ serde( rename = "quicklookurl" , skip_serializing_if = "Option::is_none" ) ]
212
233
quicklook_url : Option < String > ,
213
234
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 > > ,
216
239
}
217
240
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`].
219
247
#[ derive( Debug , Clone , Default , PartialEq , Eq , Serialize ) ]
220
248
pub struct Output {
221
249
/// The interval in seconds after which to rerun the script filter.
222
250
#[ serde(
223
251
skip_serializing_if = "Option::is_none" ,
224
- serialize_with = "duration_as_secs "
252
+ serialize_with = "option_duration_as_secs "
225
253
) ]
226
254
rerun : Option < Duration > ,
227
255
228
256
/// Whether to skip Alfred's knowledge for this output.
229
257
#[ serde( rename = "skipknowledge" , skip_serializing_if = "Option::is_none" ) ]
230
258
skip_knowledge : Option < bool > ,
231
259
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
+
232
269
/// Each row item.
233
270
items : Vec < Item > ,
234
271
}
@@ -399,24 +436,69 @@ impl Modifier {
399
436
self . data . valid = Some ( valid) ;
400
437
self
401
438
}
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
+ }
402
469
}
403
470
404
- impl Item {
405
- /// Create a new item with the provided title .
471
+ impl Cache {
472
+ /// Create a new cache with the given duration .
406
473
#[ must_use]
407
- pub fn new ( title : impl Into < String > ) -> Self {
474
+ pub fn new ( duration : Duration ) -> Self {
408
475
Self {
409
- title : title . into ( ) ,
476
+ seconds : duration ,
410
477
..Self :: default ( )
411
478
}
412
479
}
413
480
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.
415
486
#[ 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 ) ;
418
489
self
419
490
}
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
+ }
420
502
421
503
/// Set the UID for this item.
422
504
///
@@ -434,6 +516,13 @@ impl Item {
434
516
self
435
517
}
436
518
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
+
437
526
/// Set the argument which is passed through the workflow to the connected
438
527
/// output action.
439
528
///
@@ -464,7 +553,7 @@ impl Item {
464
553
/// Set the icon displayed in the result row.
465
554
///
466
555
/// Workflows are run from their workflow folder, so you can reference icons
467
- /// stored in your workflow relatively .
556
+ /// stored in your workflow directory .
468
557
#[ must_use]
469
558
pub fn icon ( mut self , icon : Icon ) -> Self {
470
559
self . icon = Some ( icon) ;
@@ -519,40 +608,6 @@ impl Item {
519
608
self
520
609
}
521
610
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
-
556
611
/// Add a modifier key configuration.
557
612
///
558
613
/// This gives you control over how the modifier keys react. For example you
@@ -607,18 +662,91 @@ impl Item {
607
662
self . action = action. into ( ) ;
608
663
self
609
664
}
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
+ }
610
729
}
611
730
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 >
613
733
where
614
734
S : Serializer ,
615
735
{
616
736
match duration {
617
- Some ( d) => s . serialize_f32 ( d . as_secs_f32 ( ) ) ,
737
+ Some ( d) => duration_as_secs ( d , s ) ,
618
738
None => unreachable ! ( ) ,
619
739
}
620
740
}
621
741
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
+
622
750
impl Output {
623
751
/// Create a new output.
624
752
#[ must_use]
@@ -647,6 +775,37 @@ impl Output {
647
775
self
648
776
}
649
777
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
+
650
809
/// Extend the list of items to output.
651
810
pub fn items < I > ( & mut self , iter : I ) -> & mut Self
652
811
where
0 commit comments