diff --git a/Cargo.lock b/Cargo.lock
index 6d2cfec..13074cc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -332,6 +332,7 @@ dependencies = [
"enum-as-inner",
"lazy_static",
"log",
+ "rand 0.8.3",
"regex",
"thiserror",
]
@@ -368,6 +369,17 @@ dependencies = [
"wasi 0.9.0+wasi-snapshot-preview1",
]
+[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi 0.10.0+wasi-snapshot-preview1",
+]
+
[[package]]
name = "glob"
version = "0.3.0"
@@ -556,6 +568,12 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
+[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
[[package]]
name = "proc-macro2"
version = "1.0.24"
@@ -593,6 +611,28 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core 0.6.2",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.2",
+]
+
[[package]]
name = "rand_core"
version = "0.3.1"
@@ -608,6 +648,24 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+[[package]]
+name = "rand_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+dependencies = [
+ "getrandom 0.2.2",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core 0.6.2",
+]
+
[[package]]
name = "rdrand"
version = "0.4.0"
@@ -629,7 +687,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"
dependencies = [
- "getrandom",
+ "getrandom 0.1.16",
"redox_syscall",
"rust-argon2",
]
@@ -791,7 +849,7 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
dependencies = [
- "rand",
+ "rand 0.4.6",
"remove_dir_all",
]
diff --git a/espanso-render/Cargo.toml b/espanso-render/Cargo.toml
index 825eec2..3940d0d 100644
--- a/espanso-render/Cargo.toml
+++ b/espanso-render/Cargo.toml
@@ -11,4 +11,5 @@ thiserror = "1.0.23"
regex = "1.4.3"
lazy_static = "1.4.0"
chrono = "0.4.19"
-enum-as-inner = "0.3.3"
\ No newline at end of file
+enum-as-inner = "0.3.3"
+rand = "0.8.3"
\ No newline at end of file
diff --git a/espanso-render/src/extension/mod.rs b/espanso-render/src/extension/mod.rs
index 9711f62..836fe76 100644
--- a/espanso-render/src/extension/mod.rs
+++ b/espanso-render/src/extension/mod.rs
@@ -22,4 +22,5 @@ pub mod echo;
pub mod clipboard;
pub mod shell;
pub mod script;
+pub mod random;
mod util;
\ No newline at end of file
diff --git a/espanso-render/src/extension/random.rs b/espanso-render/src/extension/random.rs
new file mode 100644
index 0000000..91fd49a
--- /dev/null
+++ b/espanso-render/src/extension/random.rs
@@ -0,0 +1,110 @@
+/*
+ * This file is part of espanso.
+ *
+ * Copyright (C) 2019-2021 Federico Terzi
+ *
+ * espanso is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * espanso is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with espanso. If not, see .
+ */
+
+use crate::{Extension, ExtensionOutput, ExtensionResult, Params, Value};
+use rand::seq::SliceRandom;
+use thiserror::Error;
+
+pub struct RandomExtension {}
+
+#[allow(clippy::new_without_default)]
+impl RandomExtension {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl Extension for RandomExtension {
+ fn name(&self) -> &str {
+ "random"
+ }
+
+ fn calculate(
+ &self,
+ _: &crate::Context,
+ _: &crate::Scope,
+ params: &Params,
+ ) -> crate::ExtensionResult {
+ if let Some(Value::Array(choices)) = params.get("choices") {
+ let choices: Vec = choices
+ .iter()
+ .filter_map(|arg| arg.as_string())
+ .cloned()
+ .collect();
+
+ if let Some(choice) = choices.choose(&mut rand::thread_rng()) {
+ ExtensionResult::Success(ExtensionOutput::Single(choice.clone()))
+ } else {
+ ExtensionResult::Error(RandomExtensionError::SelectionError.into())
+ }
+ } else {
+ ExtensionResult::Error(RandomExtensionError::MissingChoicesParameter.into())
+ }
+ }
+}
+
+#[derive(Error, Debug)]
+pub enum RandomExtensionError {
+ #[error("missing 'choices' parameter")]
+ MissingChoicesParameter,
+
+ #[error("could not select a choice randomly")]
+ SelectionError,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::iter::FromIterator;
+
+ #[test]
+ fn random_works_correctly() {
+ let extension = RandomExtension::new();
+
+ let param = Params::from_iter(
+ vec![(
+ "choices".to_string(),
+ Value::Array(vec![
+ Value::String("first".to_string()),
+ Value::String("second".to_string()),
+ Value::String("third".to_string()),
+ ]),
+ )]
+ .into_iter(),
+ );
+ assert!(matches!(
+ extension
+ .calculate(&Default::default(), &Default::default(), ¶m)
+ .into_success()
+ .unwrap(),
+ ExtensionOutput::Single(result) if ["first", "second", "third"].contains(&result.as_str())
+ ));
+ }
+
+ #[test]
+ fn missing_echo_parameter() {
+ let extension = RandomExtension::new();
+
+ let param = Params::new();
+ assert!(matches!(
+ extension.calculate(&Default::default(), &Default::default(), ¶m),
+ ExtensionResult::Error(_)
+ ));
+ }
+}