summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorSoniEx2 <endermoneymod@gmail.com>2021-11-14 14:38:57 -0300
committerSoniEx2 <endermoneymod@gmail.com>2021-11-14 14:38:57 -0300
commitbe4c770a7c65bb1eb3d8b96128abfc5fa89d63d1 (patch)
tree89de310d5fb28072cbdc507b2c6ede0749d09cf9
parent26ae2d9a528e3f97a7f46e4b7aa4b1ec58fea127 (diff)
Add Suggestion
-rw-r--r--src/lib.rs14
-rw-r--r--src/suggestion.rs81
-rw-r--r--tests/suggestion.rs89
3 files changed, 184 insertions, 0 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 2900669..d8c7582 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,18 @@
+// Copyright (c) 2021 Soni L.
+
+//! Iosonism is a command parsing library. It parses commands from strings, in
+//! contrast with an argument parsing library, which parses arrays of strings.
+//!
+//! Iosonism is based on [Brigadier](https://github.com/Mojang/brigadier).
+
+// quick overview of brigadier vs iosonism:
+//
+// - brigadier.StringReader -> iosonism::strcursor::StringReader + Cursor<&str>
+// - brigadier.context.StringRange -> Range<usize>
+// - brigadier.suggestion.Suggestion -> iosonism::suggestion::Suggestion;
+
 pub mod strcursor;
+pub mod suggestion;
 
 #[cfg(test)]
 mod tests {
diff --git a/src/suggestion.rs b/src/suggestion.rs
new file mode 100644
index 0000000..bad9793
--- /dev/null
+++ b/src/suggestion.rs
@@ -0,0 +1,81 @@
+// Copyright (c) 2021 Soni L.
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+//! A Suggestion.
+
+use ::std::ops::Range;
+
+/// A suggested editing operation.
+///
+/// # Examples
+///
+/// ```rust
+/// use iosonism::suggestion::Suggestion;
+///
+/// let s = Suggestion::new(3..3, "home".into());
+/// assert_eq!(s.apply("Go ".into()), "Go home");
+/// ```
+#[derive(Debug, PartialEq, Eq, Hash)]
+pub struct Suggestion {
+    range: Range<usize>,
+    text: String,
+}
+
+impl Suggestion {
+    /// Creates a new `Suggestion` for `text` for the given `range`.
+    pub fn new(range: Range<usize>, text: String) -> Self {
+        Self {
+            range: range,
+            text: text,
+        }
+    }
+
+    /// Returns the range associated with this suggestion.
+    pub fn get_range(&self) -> Range<usize> {
+        self.range.clone()
+    }
+
+    /// Returns the replacement text associated with this suggestion.
+    pub fn get_text(&self) -> &str {
+        &self.text
+    }
+
+    /// Applies this suggestion on the `input`.
+    ///
+    /// # Panics
+    ///
+    /// Panics if the range is outside the `input`'s bounds, or not on an UTF-8
+    /// boundary.
+    pub fn apply(&self, mut input: String) -> String {
+        // the use of String here actually has a performance reason:
+        // either you already have a String, in which case this is fast,
+        // or you need a String anyway, in which case it's your responsibility
+        // to make your &str into one.
+        input.replace_range(self.range.clone(), &self.text);
+        input
+    }
+
+    /// Creates a new `Suggestion` by applying this `Suggestion` on the
+    /// `range` part of the given `input`.
+    ///
+    /// # Panics
+    ///
+    /// May panic if this suggestion's range is outside the bounds given by
+    /// `range`. Panics if the given `range` is outside `input`'s bounds, or any
+    /// range is not on an UTF-8 boundary.
+    // FIXME: This function could really use a better description.
+    pub fn expand(&self, mut input: String, range: Range<usize>) -> Self {
+        // It is our understanding that both ranges must be within input.
+        // It's also our understanding that self.range must be within the passed
+        // range.
+        // So we just do our best to enforce those.
+        input.truncate(range.end);
+        input.drain(..range.start);
+        input.replace_range(
+            (self.range.start-range.start)..(self.range.end-range.start),
+            &self.text,
+        );
+        Self::new(range, input)
+    }
+}
diff --git a/tests/suggestion.rs b/tests/suggestion.rs
new file mode 100644
index 0000000..0e1b9fe
--- /dev/null
+++ b/tests/suggestion.rs
@@ -0,0 +1,89 @@
+// Copyright (c) 2021 Soni L.
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+// because we wanna use double underscore (__) for test names
+#![allow(non_snake_case)]
+
+use ::iosonism::suggestion::Suggestion;
+
+#[test]
+fn test_apply__insertion_start() {
+    let s = Suggestion::new(0..0, "And so I said: ".into());
+    assert_eq!(s.apply("Hello world!".into()), "And so I said: Hello world!");
+}
+
+#[test]
+fn test_apply__insertion_middle() {
+    let s = Suggestion::new(6..6, "small ".into());
+    assert_eq!(s.apply("Hello world!".into()), "Hello small world!");
+}
+
+#[test]
+fn test_apply__insertion_end() {
+    let s = Suggestion::new(5..5, " world!".into());
+    assert_eq!(s.apply("Hello".into()), "Hello world!");
+}
+
+#[test]
+fn test_apply__replacement_start() {
+    let s = Suggestion::new(0..5, "Goodbye".into());
+    assert_eq!(s.apply("Hello world!".into()), "Goodbye world!");
+}
+
+#[test]
+fn test_apply__replacement_middle() {
+    let s = Suggestion::new(6..11, "Alex".into());
+    assert_eq!(s.apply("Hello world!".into()), "Hello Alex!");
+}
+
+#[test]
+fn test_apply__replacement_end() {
+    let s = Suggestion::new(6..12, "Creeper!".into());
+    assert_eq!(s.apply("Hello world!".into()), "Hello Creeper!");
+}
+
+#[test]
+fn test_apply__replacement_everything() {
+    let s = Suggestion::new(0..12, "Oh dear.".into());
+    assert_eq!(s.apply("Hello world!".into()), "Oh dear.");
+}
+
+#[test]
+fn test_expand__unchanged() {
+    let s = Suggestion::new(1..1, "oo".into());
+    assert_eq!(s.expand("f".into(), 1..1), s);
+}
+
+#[test]
+fn test_expand__left() {
+    let s = Suggestion::new(1..1, "oo".into());
+    assert_eq!(s.expand("f".into(), 0..1), Suggestion::new(0..1, "foo".into()));
+}
+
+#[test]
+fn test_expand__right() {
+    let s = Suggestion::new(0..0, "minecraft:".into());
+    assert_eq!(
+        s.expand("fish".into(), 0..4),
+        Suggestion::new(0..4, "minecraft:fish".into()),
+    );
+}
+
+#[test]
+fn test_expand__both() {
+    let s = Suggestion::new(11..11, "minecraft:".into());
+    assert_eq!(
+        s.expand("give Steve fish_block".into(), 5..21),
+        Suggestion::new(5..21, "Steve minecraft:fish_block".into()),
+    );
+}
+
+#[test]
+fn test_expand__replacement() {
+    let s = Suggestion::new(6..11, "strangers".into());
+    assert_eq!(
+        s.expand("Hello world!".into(), 0..12),
+        Suggestion::new(0..12, "Hello strangers!".into()),
+    );
+}