#![warn(missing_docs)]
#![doc(html_root_url = "https://docs.rs/alfred/4.0.0")]
#[macro_use]
extern crate serde_json;
pub mod json;
pub mod xml;
pub mod env;
use std::borrow::{Borrow, Cow};
use std::collections::HashMap;
use std::iter::FromIterator;
use std::hash::Hash;
pub use self::xml::XMLWriter;
#[derive(Clone,Debug,PartialEq,Eq)]
pub struct Item<'a> {
pub title: Cow<'a, str>,
pub subtitle: Option<Cow<'a, str>>,
pub icon: Option<Icon<'a>>,
pub uid: Option<Cow<'a, str>>,
pub arg: Option<Cow<'a, str>>,
pub type_: ItemType,
pub valid: bool,
pub autocomplete: Option<Cow<'a, str>>,
pub text_copy: Option<Cow<'a, str>>,
pub text_large_type: Option<Cow<'a, str>>,
pub quicklook_url: Option<Cow<'a, str>>,
pub modifiers: HashMap<Modifier, ModifierData<'a>>,
pub variables: HashMap<Cow<'a, str>, Cow<'a, str>>,
_priv: ()
}
impl<'a> Item<'a> {
pub fn new<S: Into<Cow<'a, str>>>(title: S) -> Item<'a> {
Item {
title: title.into(),
subtitle: None,
icon: None,
uid: None,
arg: None,
type_: ItemType::Default,
valid: true,
autocomplete: None,
text_copy: None,
text_large_type: None,
quicklook_url: None,
modifiers: HashMap::new(),
variables: HashMap::new(),
_priv: ()
}
}
}
#[derive(Clone,Debug)]
pub struct ItemBuilder<'a> {
item: Item<'a>
}
impl<'a> ItemBuilder<'a> {
pub fn new<S: Into<Cow<'a, str>>>(title: S) -> ItemBuilder<'a> {
ItemBuilder {
item: Item::new(title)
}
}
pub fn into_item(self) -> Item<'a> {
self.item
}
}
impl<'a> ItemBuilder<'a> {
pub fn title<S: Into<Cow<'a, str>>>(mut self, title: S) -> ItemBuilder<'a> {
self.set_title(title);
self
}
pub fn subtitle<S: Into<Cow<'a, str>>>(mut self, subtitle: S) -> ItemBuilder<'a> {
self.set_subtitle(subtitle);
self
}
pub fn subtitle_mod<S: Into<Cow<'a, str>>>(mut self, modifier: Modifier, subtitle: S)
-> ItemBuilder<'a> {
self.set_subtitle_mod(modifier, subtitle);
self
}
pub fn icon_path<S: Into<Cow<'a, str>>>(mut self, path: S) -> ItemBuilder<'a> {
self.set_icon_path(path);
self
}
pub fn icon_file<S: Into<Cow<'a, str>>>(mut self, path: S) -> ItemBuilder<'a> {
self.set_icon_file(path);
self
}
pub fn icon_filetype<S: Into<Cow<'a, str>>>(mut self, filetype: S) -> ItemBuilder<'a> {
self.set_icon_filetype(filetype);
self
}
pub fn icon_path_mod<S: Into<Cow<'a, str>>>(mut self, modifier: Modifier, path: S)
-> ItemBuilder<'a> {
self.set_icon_path_mod(modifier, path);
self
}
pub fn icon_file_mod<S: Into<Cow<'a, str>>>(mut self, modifier: Modifier, path: S)
-> ItemBuilder<'a> {
self.set_icon_file_mod(modifier, path);
self
}
pub fn icon_filetype_mod<S: Into<Cow<'a, str>>>(mut self, modifier: Modifier, filetype: S)
-> ItemBuilder<'a> {
self.set_icon_filetype_mod(modifier, filetype);
self
}
pub fn uid<S: Into<Cow<'a, str>>>(mut self, uid: S) -> ItemBuilder<'a> {
self.set_uid(uid);
self
}
pub fn arg<S: Into<Cow<'a, str>>>(mut self, arg: S) -> ItemBuilder<'a> {
self.set_arg(arg);
self
}
pub fn arg_mod<S: Into<Cow<'a, str>>>(mut self, modifier: Modifier, arg: S)
-> ItemBuilder<'a> {
self.set_arg_mod(modifier, arg);
self
}
pub fn type_(mut self, type_: ItemType) -> ItemBuilder<'a> {
self.set_type(type_);
self
}
pub fn valid(mut self, valid: bool) -> ItemBuilder<'a> {
self.set_valid(valid);
self
}
pub fn valid_mod(mut self, modifier: Modifier, valid: bool) -> ItemBuilder<'a> {
self.set_valid_mod(modifier, valid);
self
}
pub fn modifier<S: Into<Cow<'a, str>>, S2: Into<Cow<'a, str>>>(mut self,
modifier: Modifier,
subtitle: Option<S>,
arg: Option<S2>,
valid: bool,
icon: Option<Icon<'a>>)
-> ItemBuilder<'a> {
self.set_modifier(modifier, subtitle, arg, valid, icon);
self
}
pub fn autocomplete<S: Into<Cow<'a, str>>>(mut self, autocomplete: S) -> ItemBuilder<'a> {
self.set_autocomplete(autocomplete);
self
}
pub fn text_copy<S: Into<Cow<'a, str>>>(mut self, text: S) -> ItemBuilder<'a> {
self.set_text_copy(text);
self
}
pub fn text_large_type<S: Into<Cow<'a, str>>>(mut self, text: S) -> ItemBuilder<'a> {
self.set_text_large_type(text);
self
}
pub fn quicklook_url<S: Into<Cow<'a, str>>>(mut self, url: S) -> ItemBuilder<'a> {
self.set_quicklook_url(url);
self
}
pub fn variable<K,V>(mut self, key: K, value: V) -> ItemBuilder<'a>
where K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>
{
self.set_variable(key, value);
self
}
pub fn variables<I,K,V>(mut self, variables: I) -> ItemBuilder<'a>
where I: IntoIterator<Item=(K,V)>,
K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>
{
self.set_variables(variables);
self
}
pub fn variable_mod<K,V>(mut self, modifier: Modifier, key: K, value: V) -> ItemBuilder<'a>
where K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>
{
self.set_variable_mod(modifier, key, value);
self
}
pub fn variables_mod<I,K,V>(mut self, modifier: Modifier, variables: I) -> ItemBuilder<'a>
where I: IntoIterator<Item=(K,V)>,
K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>
{
self.set_variables_mod(modifier, variables);
self
}
}
impl<'a> ItemBuilder<'a> {
pub fn set_title<S: Into<Cow<'a, str>>>(&mut self, title: S) {
self.item.title = title.into();
}
pub fn set_subtitle<S: Into<Cow<'a, str>>>(&mut self, subtitle: S) {
self.item.subtitle = Some(subtitle.into());
}
pub fn unset_subtitle(&mut self) {
self.item.subtitle = None;
}
pub fn set_subtitle_mod<S: Into<Cow<'a, str>>>(&mut self, modifier: Modifier, subtitle: S) {
self.data_for_modifier(modifier).subtitle = Some(subtitle.into());
}
pub fn unset_subtitle_mod(&mut self, modifier: Modifier) {
use std::collections::hash_map::Entry;
if let Entry::Occupied(mut entry) = self.item.modifiers.entry(modifier) {
entry.get_mut().subtitle = None;
if entry.get().is_empty() {
entry.remove();
}
}
}
pub fn clear_subtitle(&mut self) {
self.item.subtitle = None;
for &modifier in ALL_MODIFIERS {
self.unset_subtitle_mod(modifier);
}
}
pub fn set_icon_path<S: Into<Cow<'a, str>>>(&mut self, path: S) {
self.item.icon = Some(Icon::Path(path.into()));
}
pub fn set_icon_file<S: Into<Cow<'a, str>>>(&mut self, path: S) {
self.item.icon = Some(Icon::File(path.into()));
}
pub fn set_icon_filetype<S: Into<Cow<'a, str>>>(&mut self, filetype: S) {
self.item.icon = Some(Icon::FileType(filetype.into()));
}
pub fn unset_icon(&mut self) {
self.item.icon = None;
}
pub fn set_icon_path_mod<S: Into<Cow<'a, str>>>(&mut self, modifier: Modifier, path: S) {
self.data_for_modifier(modifier).icon = Some(Icon::Path(path.into()));
}
pub fn set_icon_file_mod<S: Into<Cow<'a, str>>>(&mut self, modifier: Modifier, path: S) {
self.data_for_modifier(modifier).icon = Some(Icon::File(path.into()));
}
pub fn set_icon_filetype_mod<S: Into<Cow<'a, str>>>(&mut self, modifier: Modifier,
filetype: S) {
self.data_for_modifier(modifier).icon = Some(Icon::FileType(filetype.into()));
}
pub fn unset_icon_mod(&mut self, modifier: Modifier) {
use std::collections::hash_map::Entry;
if let Entry::Occupied(mut entry) = self.item.modifiers.entry(modifier) {
entry.get_mut().icon = None;
if entry.get().is_empty() {
entry.remove();
}
}
}
pub fn clear_icon(&mut self) {
self.item.icon = None;
for &modifier in ALL_MODIFIERS {
self.unset_icon_mod(modifier);
}
}
pub fn set_uid<S: Into<Cow<'a, str>>>(&mut self, uid: S) {
self.item.uid = Some(uid.into());
}
pub fn unset_uid(&mut self) {
self.item.uid = None;
}
pub fn set_arg<S: Into<Cow<'a, str>>>(&mut self, arg: S) {
self.item.arg = Some(arg.into());
}
pub fn unset_arg(&mut self) {
self.item.arg = None;
}
pub fn set_arg_mod<S: Into<Cow<'a, str>>>(&mut self, modifier: Modifier, arg: S) {
self.data_for_modifier(modifier).arg = Some(arg.into());
}
pub fn unset_arg_mod(&mut self, modifier: Modifier) {
use std::collections::hash_map::Entry;
if let Entry::Occupied(mut entry) = self.item.modifiers.entry(modifier) {
entry.get_mut().arg = None;
if entry.get().is_empty() {
entry.remove();
}
}
}
pub fn clear_arg(&mut self) {
self.item.arg = None;
for &modifier in ALL_MODIFIERS {
self.unset_arg_mod(modifier);
}
}
pub fn set_type(&mut self, type_: ItemType) {
self.item.type_ = type_;
}
pub fn set_valid(&mut self, valid: bool) {
self.item.valid = valid;
}
pub fn set_valid_mod(&mut self, modifier: Modifier, valid: bool) {
self.data_for_modifier(modifier).valid = Some(valid);
}
pub fn unset_valid_mod(&mut self, modifier: Modifier) {
use std::collections::hash_map::Entry;
if let Entry::Occupied(mut entry) = self.item.modifiers.entry(modifier) {
entry.get_mut().valid = None;
if entry.get().is_empty() {
entry.remove();
}
}
}
pub fn clear_valid(&mut self) {
self.item.valid = true;
for &modifier in ALL_MODIFIERS {
self.unset_valid_mod(modifier);
}
}
pub fn set_autocomplete<S: Into<Cow<'a, str>>>(&mut self, autocomplete: S) {
self.item.autocomplete = Some(autocomplete.into());
}
pub fn unset_autocomplete(&mut self) {
self.item.autocomplete = None;
}
pub fn set_modifier<S: Into<Cow<'a, str>>, S2: Into<Cow<'a, str>>>(&mut self,
modifier: Modifier,
subtitle: Option<S>,
arg: Option<S2>,
valid: bool,
icon: Option<Icon<'a>>) {
let data = ModifierData {
subtitle: subtitle.map(Into::into),
arg: arg.map(Into::into),
valid: Some(valid),
icon: icon,
variables: HashMap::new(),
_priv: ()
};
self.item.modifiers.insert(modifier, data);
}
pub fn unset_modifier(&mut self, modifier: Modifier) {
self.item.modifiers.remove(&modifier);
}
pub fn set_text_copy<S: Into<Cow<'a, str>>>(&mut self, text: S) {
self.item.text_copy = Some(text.into());
}
pub fn unset_text_copy(&mut self) {
self.item.text_copy = None;
}
pub fn set_text_large_type<S: Into<Cow<'a, str>>>(&mut self, text: S) {
self.item.text_large_type = Some(text.into());
}
pub fn unset_text_large_type(&mut self) {
self.item.text_large_type = None;
}
pub fn set_quicklook_url<S: Into<Cow<'a, str>>>(&mut self, url: S) {
self.item.quicklook_url = Some(url.into());
}
pub fn unset_quicklook_url(&mut self) {
self.item.quicklook_url = None;
}
pub fn set_variable<K,V>(&mut self, key: K, value: V)
where K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>
{
self.item.variables.insert(key.into(), value.into());
}
pub fn unset_variable<K: ?Sized>(&mut self, key: &K)
where Cow<'a, str>: Borrow<K>,
K: Hash + Eq
{
self.item.variables.remove(key);
}
pub fn set_variables<I,K,V>(&mut self, variables: I)
where I: IntoIterator<Item=(K,V)>,
K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>
{
self.item.variables = HashMap::from_iter(variables.into_iter()
.map(|(k,v)| (k.into(),v.into())));
}
pub fn unset_variables(&mut self) {
self.item.variables.clear()
}
pub fn set_variable_mod<K,V>(&mut self, modifier: Modifier, key: K, value: V)
where K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>
{
self.data_for_modifier(modifier).variables.insert(key.into(), value.into());
}
pub fn unset_variable_mod<K: ?Sized>(&mut self, modifier: Modifier, key: &K)
where Cow<'a, str>: Borrow<K>,
K: Hash + Eq
{
use std::collections::hash_map::Entry;
if let Entry::Occupied(mut entry) = self.item.modifiers.entry(modifier) {
entry.get_mut().variables.remove(key);
if entry.get().is_empty() {
entry.remove();
}
}
}
pub fn set_variables_mod<I,K,V>(&mut self, modifier: Modifier, variables: I)
where I: IntoIterator<Item=(K,V)>,
K: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>
{
self.data_for_modifier(modifier).variables =
HashMap::from_iter(variables.into_iter().map(|(k,v)| (k.into(), v.into())));
}
pub fn unset_variables_mod(&mut self, modifier: Modifier) {
use std::collections::hash_map::Entry;
if let Entry::Occupied(mut entry) = self.item.modifiers.entry(modifier) {
entry.get_mut().variables.clear();
if entry.get().is_empty() {
entry.remove();
}
}
}
pub fn clear_variables(&mut self) {
self.unset_variables();
for &modifier in ALL_MODIFIERS {
self.unset_variables_mod(modifier);
}
}
fn data_for_modifier(&mut self, modifier: Modifier) -> &mut ModifierData<'a> {
self.item.modifiers.entry(modifier).or_insert_with(Default::default)
}
}
#[derive(Copy,Clone,Debug,PartialEq,Eq,Hash)]
pub enum Modifier {
Command,
Option,
Control,
Shift,
Fn
}
const ALL_MODIFIERS: &'static [Modifier] = &[Modifier::Command, Modifier::Option,
Modifier::Control, Modifier::Shift, Modifier::Fn];
#[derive(Clone,Debug,PartialEq,Eq,Default)]
pub struct ModifierData<'a> {
pub subtitle: Option<Cow<'a, str>>,
pub arg: Option<Cow<'a, str>>,
pub valid: Option<bool>,
pub icon: Option<Icon<'a>>,
pub variables: HashMap<Cow<'a, str>, Cow<'a, str>>,
_priv: ()
}
impl<'a> ModifierData<'a> {
pub fn new() -> ModifierData<'a> {
Default::default()
}
fn is_empty(&self) -> bool {
self.subtitle.is_none()
&& self.arg.is_none()
&& self.valid.is_none()
&& self.icon.is_none()
&& self.variables.is_empty()
}
}
#[derive(Clone,Debug,PartialEq,Eq,Hash)]
pub enum Icon<'a> {
Path(Cow<'a, str>),
File(Cow<'a, str>),
FileType(Cow<'a, str>)
}
#[derive(Copy,Clone,Debug,PartialEq,Eq,Hash)]
pub enum ItemType {
Default,
File,
FileSkipCheck
}
#[test]
fn test_variables() {
let mut builder = ItemBuilder::new("Name");
builder.set_variable("fruit", "banana");
builder.set_variable("vegetable".to_owned(), Cow::Borrowed("carrot"));
let item = builder.clone().into_item();
assert_eq!(item.variables.get("fruit").as_ref().map(|x| x.as_ref()), Some("banana"));
assert_eq!(item.variables.get("vegetable").as_ref().map(|x| x.as_ref()), Some("carrot"));
assert_eq!(item.variables.get("meat"), None);
builder.unset_variable("fruit");
builder.unset_variable("vegetable");
let item = builder.into_item();
assert_eq!(item.variables, HashMap::new());
}