use std::collections::{BTreeMap, HashMap};
use std::io;
use std::path::PathBuf;
use std::time::Duration;
use serde::ser::SerializeStruct;
use serde::{Serialize, Serializer};
pub use serde_json::Value;
pub use serde_json::json as value;
#[cfg(feature = "env")]
pub use powerpack_env as env;
#[cfg(feature = "cache")]
pub use powerpack_cache as cache;
#[cfg(feature = "detach")]
pub use powerpack_detach as detach;
#[cfg(feature = "logger")]
pub use powerpack_logger as logger;
fn is_default<T: Default + PartialEq>(t: &T) -> bool {
t == &T::default()
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(untagged)]
enum Arg {
One(String),
Many(Vec<String>),
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize)]
pub enum Key {
#[serde(rename = "cmd")]
Command,
#[serde(rename = "alt")]
Option,
#[serde(rename = "ctrl")]
Control,
#[serde(rename = "shift")]
Shift,
#[serde(rename = "fn")]
Function,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(untagged)]
enum Keys {
One(Key),
#[serde(serialize_with = "serialize_many_keys")]
Many(Vec<Key>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum IconInner {
Image(PathBuf),
FileIcon(PathBuf),
FileType(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Icon(IconInner);
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize)]
pub enum Kind {
#[serde(rename = "default")]
Default,
#[serde(rename = "file")]
File,
#[serde(rename = "file:skipcheck")]
FileSkipCheck,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize)]
struct Text {
#[serde(skip_serializing_if = "Option::is_none")]
copy: Option<String>,
#[serde(rename = "largetype", skip_serializing_if = "Option::is_none")]
large_type: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize)]
struct Data {
#[serde(skip_serializing_if = "Option::is_none")]
subtitle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
arg: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<Icon>,
#[serde(skip_serializing_if = "Option::is_none")]
valid: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
variables: Option<BTreeMap<String, String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
pub struct Modifier {
key: Keys,
data: Data,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct Cache {
#[serde(serialize_with = "duration_as_secs")]
seconds: Duration,
#[serde(rename = "loosereload", skip_serializing_if = "Option::is_none")]
loose_reload: Option<bool>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct Item {
#[serde(skip_serializing_if = "Option::is_none")]
uid: Option<String>,
title: String,
#[serde(skip_serializing_if = "Option::is_none")]
subtitle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
arg: Option<Arg>,
#[serde(skip_serializing_if = "Option::is_none")]
icon: Option<Icon>,
#[serde(skip_serializing_if = "Option::is_none")]
valid: Option<bool>,
#[serde(rename = "match", skip_serializing_if = "Option::is_none")]
matches: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
autocomplete: Option<String>,
#[serde(rename = "type", skip_serializing_if = "is_default")]
kind: Kind,
#[serde(rename = "mods", skip_serializing_if = "HashMap::is_empty")]
modifiers: HashMap<Keys, Data>,
#[serde(skip_serializing_if = "Value::is_null")]
action: Value,
#[serde(skip_serializing_if = "Option::is_none")]
text: Option<Text>,
#[serde(rename = "quicklookurl", skip_serializing_if = "Option::is_none")]
quicklook_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
variables: Option<BTreeMap<String, String>>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
pub struct Output {
#[serde(
skip_serializing_if = "Option::is_none",
serialize_with = "option_duration_as_secs"
)]
rerun: Option<Duration>,
#[serde(rename = "skipknowledge", skip_serializing_if = "Option::is_none")]
skip_knowledge: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
variables: Option<BTreeMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
cache: Option<Cache>,
items: Vec<Item>,
}
fn serialize_many_keys<S>(duration: &[Key], s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut out = String::with_capacity(5 * duration.len());
for (i, key) in duration.iter().enumerate() {
if i != 0 {
out.push('+');
}
out.push_str(match key {
Key::Command => "cmd",
Key::Option => "alt",
Key::Control => "ctrl",
Key::Shift => "shift",
Key::Function => "fn",
});
}
s.serialize_str(&out)
}
impl Serialize for Icon {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match &self.0 {
IconInner::Image(path) => {
let mut s = serializer.serialize_struct("Icon", 1)?;
s.serialize_field("path", &path)?;
s.end()
}
IconInner::FileIcon(path) => {
let mut s = serializer.serialize_struct("Icon", 2)?;
s.serialize_field("type", "fileicon")?;
s.serialize_field("path", &path)?;
s.end()
}
IconInner::FileType(string) => {
let mut s = serializer.serialize_struct("Icon", 2)?;
s.serialize_field("type", "filetype")?;
s.serialize_field("path", &string)?;
s.end()
}
}
}
}
impl Icon {
pub fn with_image(path: impl Into<PathBuf>) -> Self {
Self(IconInner::Image(path.into()))
}
pub fn with_file_icon(path: impl Into<PathBuf>) -> Self {
Self(IconInner::FileIcon(path.into()))
}
pub fn with_type(uti: impl Into<String>) -> Self {
Self(IconInner::FileType(uti.into()))
}
}
impl Default for Kind {
fn default() -> Self {
Self::Default
}
}
impl Modifier {
#[must_use]
pub fn new(key: Key) -> Self {
Self {
key: Keys::One(key),
data: Data::default(),
}
}
#[must_use]
pub fn new_multi(keys: impl IntoIterator<Item = Key>) -> Self {
Self {
key: Keys::Many(keys.into_iter().collect()),
data: Data::default(),
}
}
#[must_use]
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
self.data.subtitle = Some(subtitle.into());
self
}
#[must_use]
pub fn arg(mut self, arg: impl Into<String>) -> Self {
self.data.arg = Some(arg.into());
self
}
#[must_use]
pub fn icon(mut self, arg: Icon) -> Self {
self.data.icon = Some(arg);
self
}
#[must_use]
pub fn valid(mut self, valid: bool) -> Self {
self.data.valid = Some(valid);
self
}
#[must_use]
pub fn variables<K, V>(mut self, variables: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: Into<String>,
{
self.data.variables = Some(
variables
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
);
self
}
}
impl Cache {
#[must_use]
pub fn new(duration: Duration) -> Self {
Self {
seconds: duration,
..Self::default()
}
}
#[must_use]
pub fn loose_reload(mut self, loose_reload: bool) -> Self {
self.loose_reload = Some(loose_reload);
self
}
}
impl Item {
#[must_use]
pub fn new(title: impl Into<String>) -> Self {
Self {
title: title.into(),
..Self::default()
}
}
#[must_use]
pub fn uid(mut self, uid: impl Into<String>) -> Self {
self.uid = Some(uid.into());
self
}
#[must_use]
pub fn subtitle(mut self, subtitle: impl Into<String>) -> Self {
self.subtitle = Some(subtitle.into());
self
}
#[must_use]
pub fn arg(mut self, arg: impl Into<String>) -> Self {
self.arg = Some(Arg::One(arg.into()));
self
}
#[must_use]
pub fn args<I, J>(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self
where
I: IntoIterator<Item = J>,
J: Into<String>,
{
self.arg = Some(Arg::Many(args.into_iter().map(Into::into).collect()));
self
}
#[must_use]
pub fn icon(mut self, icon: Icon) -> Self {
self.icon = Some(icon);
self
}
#[must_use]
pub fn valid(mut self, valid: bool) -> Self {
self.valid = Some(valid);
self
}
#[must_use]
pub fn matches(mut self, matches: impl Into<String>) -> Self {
self.matches = Some(matches.into());
self
}
#[must_use]
pub fn autocomplete(mut self, autocomplete: impl Into<String>) -> Self {
self.autocomplete = Some(autocomplete.into());
self
}
#[must_use]
pub fn kind(mut self, kind: Kind) -> Self {
self.kind = kind;
self
}
#[must_use]
pub fn modifier(mut self, modifier: Modifier) -> Self {
let Modifier { key, data } = modifier;
self.modifiers.insert(key, data);
self
}
pub fn action(mut self, action: impl Into<Value>) -> Self {
self.action = action.into();
self
}
#[must_use]
pub fn copy_text(mut self, copy: impl Into<String>) -> Self {
self.text.get_or_insert_with(Text::default).copy = Some(copy.into());
self
}
#[must_use]
pub fn large_type_text(mut self, large_type: impl Into<String>) -> Self {
self.text.get_or_insert_with(Text::default).large_type = Some(large_type.into());
self
}
#[must_use]
pub fn quicklook_url(mut self, quicklook_url: impl Into<String>) -> Self {
self.quicklook_url = Some(quicklook_url.into());
self
}
#[must_use]
pub fn variables<K, V>(mut self, variables: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: Into<String>,
{
self.variables = Some(
variables
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
);
self
}
}
#[inline]
fn option_duration_as_secs<S>(duration: &Option<Duration>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match duration {
Some(d) => duration_as_secs(d, s),
None => unreachable!(),
}
}
#[inline]
fn duration_as_secs<S>(duration: &Duration, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
s.serialize_f32(duration.as_secs_f32())
}
impl Output {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn rerun(&mut self, duration: Duration) -> &mut Self {
self.rerun = Some(duration);
self
}
pub fn skip_knowledge(&mut self, skip_knowledge: bool) -> &mut Self {
self.skip_knowledge = Some(skip_knowledge);
self
}
#[must_use]
pub fn variables<K, V>(mut self, variables: impl IntoIterator<Item = (K, V)>) -> Self
where
K: Into<String>,
V: Into<String>,
{
self.variables = Some(
variables
.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.collect(),
);
self
}
pub fn cache(&mut self, cache: Cache) -> &mut Self {
self.cache = Some(cache);
self
}
pub fn items<I>(&mut self, iter: I) -> &mut Self
where
I: IntoIterator<Item = Item>,
{
self.items.extend(iter);
self
}
pub fn write<W: io::Write>(&self, w: W) -> serde_json::Result<()> {
serde_json::to_writer(w, self)
}
}
pub fn output<I>(items: I) -> serde_json::Result<()>
where
I: IntoIterator<Item = Item>,
{
Output::new().items(items).write(io::stdout())
}