#![feature(unsafe_destructor,into_cow)]
#![warn(missing_docs)]
use std::borrow::{Cow, IntoCow};
use std::collections::HashMap;
use std::error;
use std::fmt;
use std::io;
use std::io::prelude::*;
use std::mem;
use std::sync;
#[derive(PartialEq,Eq,Clone)]
pub struct Item<'a> {
pub title: Cow<'a, str>,
pub subtitle: HashMap<Option<Modifier>,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>>,
}
impl<'a> Item<'a> {
pub fn new<S: IntoCow<'a, str>>(title: S) -> Item<'a> {
Item {
title: title.into_cow(),
subtitle: HashMap::new(),
icon: None,
uid: None,
arg: None,
type_: ItemType::Default,
valid: true,
autocomplete: None,
text_copy: None,
text_large_type: None,
}
}
}
#[derive(Clone)]
pub struct ItemBuilder<'a> {
item: Item<'a>
}
impl<'a> ItemBuilder<'a> {
pub fn new<S: IntoCow<'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: IntoCow<'a, str>>(mut self, title: S) -> ItemBuilder<'a> {
self.set_title(title);
self
}
pub fn subtitle<S: IntoCow<'a, str>>(mut self, subtitle: S) -> ItemBuilder<'a> {
self.set_subtitle(subtitle);
self
}
pub fn subtitle_mod<S: IntoCow<'a, str>>(mut self, modifier: Modifier, subtitle: S)
-> ItemBuilder<'a> {
self.set_subtitle_mod(modifier, subtitle);
self
}
pub fn icon_path<S: IntoCow<'a, str>>(mut self, path: S) -> ItemBuilder<'a> {
self.set_icon_path(path);
self
}
pub fn icon_file<S: IntoCow<'a, str>>(mut self, path: S) -> ItemBuilder<'a> {
self.set_icon_file(path);
self
}
pub fn icon_filetype<S: IntoCow<'a, str>>(mut self, filetype: S) -> ItemBuilder<'a> {
self.set_icon_filetype(filetype);
self
}
pub fn uid<S: IntoCow<'a, str>>(mut self, uid: S) -> ItemBuilder<'a> {
self.set_uid(uid);
self
}
pub fn arg<S: IntoCow<'a, str>>(mut self, arg: S) -> ItemBuilder<'a> {
self.set_arg(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 autocomplete<S: IntoCow<'a, str>>(mut self, autocomplete: S) -> ItemBuilder<'a> {
self.set_autocomplete(autocomplete);
self
}
pub fn text_copy<S: IntoCow<'a, str>>(mut self, text: S) -> ItemBuilder<'a> {
self.set_text_copy(text);
self
}
pub fn text_large_type<S: IntoCow<'a, str>>(mut self, text: S) -> ItemBuilder<'a> {
self.set_text_large_type(text);
self
}
}
impl<'a> ItemBuilder<'a> {
pub fn set_title<S: IntoCow<'a, str>>(&mut self, title: S) {
self.item.title = title.into_cow();
}
pub fn set_subtitle<S: IntoCow<'a, str>>(&mut self, subtitle: S) {
self.item.subtitle.insert(None, subtitle.into_cow());
}
pub fn unset_subtitle(&mut self) {
self.item.subtitle.remove(&None);
}
pub fn set_subtitle_mod<S: IntoCow<'a, str>>(&mut self, modifier: Modifier, subtitle: S) {
self.item.subtitle.insert(Some(modifier), subtitle.into_cow());
}
pub fn unset_subtitle_mod(&mut self, modifier: Modifier) {
self.item.subtitle.remove(&Some(modifier));
}
pub fn clear_subtitle(&mut self) {
self.item.subtitle.clear();
}
pub fn set_icon_path<S: IntoCow<'a, str>>(&mut self, path: S) {
self.item.icon = Some(Icon::Path(path.into_cow()));
}
pub fn set_icon_file<S: IntoCow<'a, str>>(&mut self, path: S) {
self.item.icon = Some(Icon::File(path.into_cow()));
}
pub fn set_icon_filetype<S: IntoCow<'a, str>>(&mut self, filetype: S) {
self.item.icon = Some(Icon::FileType(filetype.into_cow()));
}
pub fn unset_icon(&mut self) {
self.item.icon = None;
}
pub fn set_uid<S: IntoCow<'a, str>>(&mut self, uid: S) {
self.item.uid = Some(uid.into_cow());
}
pub fn unset_uid(&mut self) {
self.item.uid = None;
}
pub fn set_arg<S: IntoCow<'a, str>>(&mut self, arg: S) {
self.item.arg = Some(arg.into_cow());
}
pub fn unset_arg(&mut self) {
self.item.arg = None;
}
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_autocomplete<S: IntoCow<'a, str>>(&mut self, autocomplete: S) {
self.item.autocomplete = Some(autocomplete.into_cow());
}
pub fn unset_autocomplete(&mut self) {
self.item.autocomplete = None;
}
pub fn set_text_copy<S: IntoCow<'a, str>>(&mut self, text: S) {
self.item.text_copy = Some(text.into_cow());
}
pub fn unset_text_copy(&mut self) {
self.item.text_copy = None;
}
pub fn set_text_large_type<S: IntoCow<'a, str>>(&mut self, text: S) {
self.item.text_large_type = Some(text.into_cow());
}
pub fn unset_text_large_type(&mut self) {
self.item.text_large_type = None;
}
}
#[derive(Clone,Copy,Debug,Hash,PartialEq,Eq)]
pub enum Modifier {
Command,
Option,
Control,
Shift,
Fn
}
#[derive(PartialEq,Eq,Clone)]
pub enum Icon<'a> {
Path(Cow<'a, str>),
File(Cow<'a, str>),
FileType(Cow<'a, str>)
}
#[derive(PartialEq,Eq,Clone,Copy)]
pub enum ItemType {
Default,
File,
FileSkipCheck
}
pub struct XMLWriter<W: Write> {
w: Option<W>,
last_err: Option<SavedError>
}
enum SavedError {
Os(i32),
Custom(SharedError)
}
#[derive(Clone)]
struct SharedError {
error: sync::Arc<io::Error>
}
impl From<io::Error> for SavedError {
fn from(err: io::Error) -> SavedError {
if let Some(code) = err.raw_os_error() {
SavedError::Os(code)
} else {
SavedError::Custom(SharedError { error: sync::Arc::new(err) })
}
}
}
impl SavedError {
fn make_io_error(&self) -> io::Error {
match *self {
SavedError::Os(code) => io::Error::from_raw_os_error(code),
SavedError::Custom(ref err) => {
let shared_err: SharedError = err.clone();
io::Error::new(err.error.kind(), shared_err)
}
}
}
}
impl error::Error for SharedError {
fn description(&self) -> &str {
self.error.description()
}
fn cause(&self) -> Option<&error::Error> {
Some(&*self.error)
}
}
impl fmt::Debug for SharedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<io::Error as fmt::Debug>::fmt(&self.error, f)
}
}
impl fmt::Display for SharedError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<io::Error as fmt::Display>::fmt(&self.error, f)
}
}
impl<W: Write> XMLWriter<W> {
pub fn new(mut w: W) -> io::Result<XMLWriter<W>> {
match w.write_all(b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<items>\n") {
Ok(()) => {
Ok(XMLWriter {
w: Some(w),
last_err: None
})
}
Err(err) => Err(err)
}
}
pub fn write_item(&mut self, item: &Item) -> io::Result<()> {
if let Some(ref err) = self.last_err {
return Err(err.make_io_error());
}
let result = item.write_xml(self.w.as_mut().unwrap(), 1);
match result {
Err(err) => {
let err: SavedError = err.into();
let io_err = err.make_io_error();
self.last_err = Some(err);
Err(io_err)
}
x@Ok(_) => x
}
}
pub fn close(mut self) -> io::Result<W> {
let last_err = self.last_err.take();
let mut w = self.w.take().unwrap();
unsafe { mem::forget(self); }
if let Some(err) = last_err {
return Err(err.make_io_error());
}
try!(write_footer(&mut w));
Ok(w)
}
}
fn write_footer<'a, W: Write + 'a>(w: &'a mut W) -> io::Result<()> {
w.write_all(b"</items>\n")
}
#[unsafe_destructor]
impl<W: Write> Drop for XMLWriter<W> {
fn drop(&mut self) {
if self.last_err.is_some() {
return;
}
let mut w = self.w.take().unwrap();
if write_footer(&mut w).is_ok() {
let _ = w.flush();
}
}
}
pub fn write_items<W: Write>(w: W, items: &[Item]) -> io::Result<()> {
let mut xmlw = try!(XMLWriter::new(w));
for item in items.iter() {
try!(xmlw.write_item(item));
}
let mut w = try!(xmlw.close());
w.flush()
}
impl<'a> Item<'a> {
pub fn write_xml(&self, w: &mut Write, indent: u32) -> io::Result<()> {
fn write_indent(w: &mut Write, indent: u32) -> io::Result<()> {
for _ in (0..indent) {
try!(w.write_all(b" "));
}
Ok(())
}
let mut w = io::BufWriter::with_capacity(512, w);
try!(write_indent(&mut w, indent));
try!(w.write_all(b"<item"));
if let Some(ref uid) = self.uid {
try!(write!(&mut w, r#" uid="{}""#, encode_entities(&uid)));
}
if let Some(ref arg) = self.arg {
try!(write!(&mut w, r#" arg="{}""#, encode_entities(&arg)));
}
match self.type_ {
ItemType::Default => {}
ItemType::File => {
try!(w.write_all(br#" type="file""#));
}
ItemType::FileSkipCheck => {
try!(w.write_all(br#" type="file:skipcheck""#));
}
}
if !self.valid {
try!(w.write_all(br#" valid="no""#));
}
if let Some(ref auto) = self.autocomplete {
try!(write!(&mut w, r#" autocomplete="{}""#, encode_entities(&auto)));
}
try!(w.write_all(b">\n"));
try!(write_indent(&mut w, indent+1));
try!(write!(&mut w, "<title>{}</title>\n", encode_entities(&self.title)));
for (modifier, subtitle) in self.subtitle.iter() {
try!(write_indent(&mut w, indent+1));
if let Some(modifier) = *modifier {
try!(write!(&mut w, r#"<subtitle mod="{}">"#, match modifier {
Modifier::Command => "cmd",
Modifier::Option => "alt",
Modifier::Control => "ctrl",
Modifier::Shift => "shift",
Modifier::Fn => "fn"
}));
} else {
try!(w.write_all(b"<subtitle>"));
}
try!(write!(&mut w, "{}</subtitle>\n", encode_entities(&subtitle)));
}
if let Some(ref icon) = self.icon {
try!(write_indent(&mut w, indent+1));
match *icon {
Icon::Path(ref s) => {
try!(write!(&mut w, "<icon>{}</icon>\n", encode_entities(&s)));
}
Icon::File(ref s) => {
try!(write!(&mut w, "<icon type=\"fileicon\">{}</icon>\n",
encode_entities(&s)));
}
Icon::FileType(ref s) => {
try!(write!(&mut w, "<icon type=\"filetype\">{}</icon>\n",
encode_entities(&s)));
}
}
}
if let Some(ref text) = self.text_copy {
try!(write_indent(&mut w, indent+1));
try!(write!(&mut w, "<text type=\"copy\">{}</text>\n", encode_entities(&text)));
}
if let Some(ref text) = self.text_large_type {
try!(write_indent(&mut w, indent+1));
try!(write!(&mut w, "<text type=\"largetype\">{}</text>\n", encode_entities(&text)));
}
try!(write_indent(&mut w, indent));
try!(w.write_all(b"</item>\n"));
w.flush()
}
}
fn encode_entities<'a>(s: &'a str) -> Cow<'a, str> {
fn encode_entity(c: char) -> Option<&'static str> {
Some(match c {
'<' => "<",
'>' => ">",
'"' => """,
'&' => "&",
'\0'...'\x08' |
'\x0B'...'\x0C' |
'\x0E'...'\x1F' |
'\u{FFFE}' | '\u{FFFF}' => {
"\u{FFFD}"
}
_ => return None
})
}
if s.chars().any(|c| encode_entity(c).is_some()) {
let mut res = String::with_capacity(s.len());
for c in s.chars() {
match encode_entity(c) {
Some(ent) => res.push_str(ent),
None => res.push(c)
}
}
Cow::Owned(res)
} else {
Cow::Borrowed(s)
}
}