From 7c2f3d4ed2d98d6680372ebeb21ea4d7d0e21791 Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Thu, 5 Jan 2023 17:08:28 +0100
Subject: [PATCH] AMADEUS: Add things to the amadeus-next workspace

- Add more things in the common crate
- Fix cargo files + can now convert from a &str to a LektorUri
- Begin to parse arguments
---
 src/rust/amadeus-next/amadeus/Cargo.toml     |   4 +-
 src/rust/amadeus-next/amalib/Cargo.toml      |   1 -
 src/rust/amadeus-next/amalib/src/lib.rs      |   3 +-
 src/rust/amadeus-next/amalib/src/uri.rs      |  24 ++++
 src/rust/amadeus-next/commons/src/asserts.rs |  36 ++++++
 src/rust/amadeus-next/commons/src/lib.rs     |   3 +
 src/rust/amadeus-next/commons/src/log.rs     |   1 +
 src/rust/amadeus-next/lkt-rs/Cargo.toml      |  13 ++-
 src/rust/amadeus-next/lkt-rs/src/args.rs     | 113 +++++++++++++++++++
 src/rust/amadeus-next/lkt-rs/src/main.rs     |   9 +-
 src/rust/amadeus-next/lkt-rs/src/parsers.rs  |  50 ++++++++
 src/rust/amadeus-next/lkt-rs/src/types.rs    |  48 ++++++++
 12 files changed, 299 insertions(+), 6 deletions(-)
 create mode 100644 src/rust/amadeus-next/commons/src/asserts.rs
 create mode 100644 src/rust/amadeus-next/commons/src/log.rs
 create mode 100644 src/rust/amadeus-next/lkt-rs/src/args.rs
 create mode 100644 src/rust/amadeus-next/lkt-rs/src/parsers.rs
 create mode 100644 src/rust/amadeus-next/lkt-rs/src/types.rs

diff --git a/src/rust/amadeus-next/amadeus/Cargo.toml b/src/rust/amadeus-next/amadeus/Cargo.toml
index 804d6c43..0383a5d9 100644
--- a/src/rust/amadeus-next/amadeus/Cargo.toml
+++ b/src/rust/amadeus-next/amadeus/Cargo.toml
@@ -6,6 +6,8 @@ authors.workspace = true
 license.workspace = true
 
 [dependencies]
-log.workspace = true
 serde.workspace = true
 tokio.workspace = true
+
+commons = { path = "../commons" }
+amalib = { path = "../amalib" }
diff --git a/src/rust/amadeus-next/amalib/Cargo.toml b/src/rust/amadeus-next/amalib/Cargo.toml
index 001f251e..6def1de2 100644
--- a/src/rust/amadeus-next/amalib/Cargo.toml
+++ b/src/rust/amadeus-next/amalib/Cargo.toml
@@ -6,7 +6,6 @@ authors.workspace = true
 license.workspace = true
 
 [dependencies]
-log.workspace = true
 serde.workspace = true
 tokio.workspace = true
 
diff --git a/src/rust/amadeus-next/amalib/src/lib.rs b/src/rust/amadeus-next/amalib/src/lib.rs
index aef81465..33f96712 100644
--- a/src/rust/amadeus-next/amalib/src/lib.rs
+++ b/src/rust/amadeus-next/amalib/src/lib.rs
@@ -13,8 +13,7 @@ pub use query::*;
 pub use response::*;
 pub use uri::*;
 
-pub(crate) use commons::*;
-pub(crate) use log::*;
+pub(crate) use commons::{log::*, *};
 pub(crate) use std::str::FromStr;
 
 /// The playback state of the lektord server.
diff --git a/src/rust/amadeus-next/amalib/src/uri.rs b/src/rust/amadeus-next/amalib/src/uri.rs
index 4d4bf040..c8fbc724 100644
--- a/src/rust/amadeus-next/amalib/src/uri.rs
+++ b/src/rust/amadeus-next/amalib/src/uri.rs
@@ -1,6 +1,7 @@
 //! Build lektord queries.
 
 use crate::*;
+use std::str::FromStr;
 
 #[derive(Debug, Clone)]
 pub enum LektorUri {
@@ -34,3 +35,26 @@ impl std::fmt::Display for LektorUri {
         f.write_str(&ret_str)
     }
 }
+
+impl TryFrom<&str> for LektorUri {
+    type Error = String;
+
+    fn try_from(value: &str) -> Result<Self, Self::Error> {
+        match value.trim().split_once("://") {
+            Some(("id", id)) => {
+                Ok(Self::Id(i32::from_str(id).map_err(|e| {
+                    format!("the string `{id}` is not an i32: {e}")
+                })?))
+            }
+            Some(("author", author)) => Ok(Self::Author(author.to_string())),
+            Some(("playlist", plt)) => Ok(Self::Playlist(plt.to_string())),
+            Some(("query", query)) => Ok(Self::Query(
+                query.split(' ').map(String::from).collect::<Vec<_>>(),
+            )),
+            Some((identifier, _)) => Err(format!(
+                "the identifier `{identifier}` is not a valid uri identifier"
+            )),
+            None => Err(format!("the string `{value}` is not a valid uri")),
+        }
+    }
+}
diff --git a/src/rust/amadeus-next/commons/src/asserts.rs b/src/rust/amadeus-next/commons/src/asserts.rs
new file mode 100644
index 00000000..5a71a7f5
--- /dev/null
+++ b/src/rust/amadeus-next/commons/src/asserts.rs
@@ -0,0 +1,36 @@
+#[macro_export]
+macro_rules! fatal {
+    (target: $target: expr, $($arg:tt)+) => {{
+        $crate::log::log!(target: $target, $crate::log::Level::Error, $($arg)+);
+        std::process::exit(1);
+    }};
+
+    ($($arg:tt)+) => {{
+        $crate::log::log!($crate::log::Level::Error, $($arg)+);
+        std::process::exit(1);
+    }};
+}
+
+/// Test that something was [`Err`] and return the content of the
+/// [`Result::Err`] variant. Panic on [`Ok`].
+#[macro_export]
+macro_rules! assert_err {
+    ($x: expr) => {
+        match $x {
+            Ok(e) => panic!("'{}' was successfull and got '{e:?}'", stringify!($x)),
+            Err(e) => e,
+        }
+    };
+}
+
+/// Test that something was [`Ok`] and return the content of the [`Result::Ok`]
+/// variant. Panic on [`Err`].
+#[macro_export]
+macro_rules! assert_ok {
+    ($x: expr) => {
+        match $x {
+            Ok(x) => x,
+            Err(e) => panic!("failed '{}' with '{e:?}'", stringify!($x)),
+        }
+    };
+}
diff --git a/src/rust/amadeus-next/commons/src/lib.rs b/src/rust/amadeus-next/commons/src/lib.rs
index 3c4eeed7..5322d4f8 100644
--- a/src/rust/amadeus-next/commons/src/lib.rs
+++ b/src/rust/amadeus-next/commons/src/lib.rs
@@ -1,4 +1,7 @@
+mod asserts;
 mod error;
 mod macros;
 
+pub mod log;
+
 pub use error::*;
diff --git a/src/rust/amadeus-next/commons/src/log.rs b/src/rust/amadeus-next/commons/src/log.rs
new file mode 100644
index 00000000..178e55f5
--- /dev/null
+++ b/src/rust/amadeus-next/commons/src/log.rs
@@ -0,0 +1 @@
+pub use log::{debug, error, info, log, trace, warn, Level};
diff --git a/src/rust/amadeus-next/lkt-rs/Cargo.toml b/src/rust/amadeus-next/lkt-rs/Cargo.toml
index e2902802..438b5e29 100644
--- a/src/rust/amadeus-next/lkt-rs/Cargo.toml
+++ b/src/rust/amadeus-next/lkt-rs/Cargo.toml
@@ -6,5 +6,16 @@ authors.workspace = true
 license.workspace = true
 
 [dependencies]
-log.workspace = true
 serde.workspace = true
+
+commons = { path = "../commons" }
+amalib = { path = "../amalib" }
+
+clap = { version = "^4", default-features = false, features = [
+    "usage",
+    "help",
+    "std",
+    "suggestions",
+    "error-context",
+    "derive",
+] }
diff --git a/src/rust/amadeus-next/lkt-rs/src/args.rs b/src/rust/amadeus-next/lkt-rs/src/args.rs
new file mode 100644
index 00000000..4c8bcbcd
--- /dev/null
+++ b/src/rust/amadeus-next/lkt-rs/src/args.rs
@@ -0,0 +1,113 @@
+use crate::types::*;
+use clap::{Parser, Subcommand};
+
+#[derive(Parser, Debug)]
+#[command(author, version, about, long_about = None)]
+pub struct Args {
+    #[command(subcommand)]
+    action: SubCommand,
+}
+
+#[derive(Subcommand, Debug)]
+pub enum SubCommand {
+    // Status commands
+    #[command(
+        about = "Prints informations about the currently playing kara. Can be used to display the current kara in a status bar like xmobar or in the i3 panel."
+    )]
+    Current,
+
+    #[command(
+        about = "Prints information about the state of lektor and the currently playing kara."
+    )]
+    Status,
+
+    // Playback commands
+    #[command(
+        about = "Toggle play/pause state. If the playback is stopped, start at a possibly specified index."
+    )]
+    Play { index: Option<usize> },
+
+    #[command(about = "For lektord to be paused, don't do anything is lektord is already paused.")]
+    Pause,
+
+    #[command(
+        about = "Unpause lektord, starts at the begening of the queue if lektord was stopped."
+    )]
+    UnPause,
+
+    #[command(about = "Stop the playback and reading the queue, the state is now stopped.")]
+    Stop,
+
+    #[command(about = "Play next kara in the queue.")]
+    Next,
+
+    #[command(about = "Play the previous kara in the queue.")]
+    Previous,
+
+    #[command(
+        about = "Shuffle the queue. If lektord was paused it will unpause but if it was stopped it won't start. If it exists, the current kara will be placed in the first place in the queue. You can also pass a level to shuffle up to."
+    )]
+    Shuffle {
+        #[arg(
+            default_value_t = PriorityLevel::Add,
+        )]
+        level: PriorityLevel,
+    },
+
+    // Playlist commands
+    #[command(alias = "plt", short_flag = 'P')]
+    Playlist {
+        #[arg(
+            value_parser = clap::builder::NonEmptyStringValueParser::new(),
+            exclusive    = true,
+            short        = 'c',
+            help         = "Create a new playlist with a specific name.",
+        )]
+        create: Option<String>,
+
+        #[arg(
+            value_parser = clap::builder::NonEmptyStringValueParser::new(),
+            exclusive    = true,
+            short        = 'd',
+            help         = "Delete a playlist with all its content, do nothing if the playlist didn't exists.",
+        )]
+        destroy: Option<String>,
+
+        #[arg(
+            short = 'l',
+            exclusive = true,
+            help = "List the content of the playlist named if a name is passed. If missing returns a list of all the available playlists."
+        )]
+        list: Option<Option<String>>,
+
+        #[arg(
+            action    = clap::ArgAction::Append,
+            exclusive = true,
+            num_args  = 2..,
+            short     = 'r',
+            help      = "Deletes karas from a playlist with a valid query. The first element is the name of the playlist."
+        )]
+        remove: Option<Vec<String>>,
+
+        #[arg(
+            action    = clap::ArgAction::Append,
+            exclusive = true,
+            num_args  = 2..,
+            short     = 'a',
+            help      = "Add karas to a playlist with a valid query. The first element is the name of the playlist."
+        )]
+        add: Option<Vec<String>>,
+    },
+
+    // Queue commands
+    #[command(short_flag = 'Q')]
+    Queue {},
+
+    // Search commands
+    #[command(short_flag = 'S')]
+    Search {},
+
+    // Admin commands
+    #[command(alias = "adm", short_flag = 'A')]
+    Admin {},
+}
diff --git a/src/rust/amadeus-next/lkt-rs/src/main.rs b/src/rust/amadeus-next/lkt-rs/src/main.rs
index f328e4d9..943affe7 100644
--- a/src/rust/amadeus-next/lkt-rs/src/main.rs
+++ b/src/rust/amadeus-next/lkt-rs/src/main.rs
@@ -1 +1,8 @@
-fn main() {}
+mod args;
+mod parsers;
+mod types;
+
+fn main() {
+    let args = <args::Args as clap::Parser>::parse();
+    println!("{args:#?}");
+}
diff --git a/src/rust/amadeus-next/lkt-rs/src/parsers.rs b/src/rust/amadeus-next/lkt-rs/src/parsers.rs
new file mode 100644
index 00000000..2a936f28
--- /dev/null
+++ b/src/rust/amadeus-next/lkt-rs/src/parsers.rs
@@ -0,0 +1,50 @@
+use clap::{builder::TypedValueParser, error::ErrorKind, Arg, Command, Error};
+
+/// A parser for the [crate::types::FilterPlaylist] structure
+#[derive(Copy, Clone, Debug)]
+#[non_exhaustive]
+pub struct FilterPlaylistParser {}
+
+impl FilterPlaylistParser {
+    /// Parse non-empty string values
+    pub fn new() -> Self {
+        Self {}
+    }
+}
+
+impl TypedValueParser for FilterPlaylistParser {
+    type Value = (String, String);
+
+    fn parse_ref(
+        &self,
+        cmd: &Command,
+        arg: Option<&Arg>,
+        value: &std::ffi::OsStr,
+    ) -> Result<Self::Value, Error> {
+        let arg = arg
+            .map(ToString::to_string)
+            .unwrap_or_else(|| "...".to_owned());
+        let value = value.to_str().ok_or_else(|| {
+            Error::raw(
+                ErrorKind::InvalidUtf8,
+                format!("not a valid utf8 string: {}", value.to_string_lossy()),
+            )
+        })?;
+        match value.split_once(' ') {
+            Some((head, tail)) => Ok((head.to_string(), tail.to_string())),
+            _ => Err(Error::raw(
+                ErrorKind::InvalidValue,
+                format!(
+                    "in `{} {arg}`, the value should be of the form: [PLAYLIST] [QUERY]",
+                    cmd.get_name()
+                ),
+            )),
+        }
+    }
+}
+
+impl Default for FilterPlaylistParser {
+    fn default() -> Self {
+        Self::new()
+    }
+}
diff --git a/src/rust/amadeus-next/lkt-rs/src/types.rs b/src/rust/amadeus-next/lkt-rs/src/types.rs
new file mode 100644
index 00000000..1b2e616e
--- /dev/null
+++ b/src/rust/amadeus-next/lkt-rs/src/types.rs
@@ -0,0 +1,48 @@
+use commons::fatal;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum PriorityLevel {
+    Add = 1,
+    Suggest = 2,
+    Insert = 3,
+    Pick = 4,
+    Enforce = 5,
+}
+
+impl From<&str> for PriorityLevel {
+    fn from(value: &str) -> Self {
+        use PriorityLevel::*;
+        match value {
+            "add" | "1" => Add,
+            "suggest" | "2" => Suggest,
+            "insert" | "3" => Insert,
+            "pick" | "4" => Pick,
+            "enforce" | "5" => Enforce,
+            _ => fatal!("unknown priority level: {value}"),
+        }
+    }
+}
+
+impl Default for PriorityLevel {
+    fn default() -> Self {
+        PriorityLevel::Add
+    }
+}
+
+impl std::fmt::Display for PriorityLevel {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_str(match self {
+            PriorityLevel::Add => "add",
+            PriorityLevel::Suggest => "suggest",
+            PriorityLevel::Insert => "insert",
+            PriorityLevel::Pick => "pick",
+            PriorityLevel::Enforce => "enforce",
+        })
+    }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct FilterPlaylist {
+    playlist: String,
+    query: String,
+}
-- 
GitLab