diff --git a/.gitignore b/.gitignore index ea8c4bf..2a0038a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 67f0520..e367afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,55 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "arc-swap" version = "1.7.1" @@ -171,6 +220,46 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "clap" +version = "4.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "color-eyre" version = "0.6.3" @@ -198,6 +287,12 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "compact_str" version = "0.8.0" @@ -306,6 +401,12 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -501,6 +602,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "indexmap" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "instability" version = "0.3.2" @@ -511,6 +622,12 @@ dependencies = [ "syn", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.11.0" @@ -915,10 +1032,13 @@ name = "scyllash" version = "0.1.0" dependencies = [ "chrono", + "clap", "color-eyre", "ratatui", "scylla", + "serde", "tokio", + "toml", "uuid", ] @@ -942,6 +1062,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1143,6 +1272,40 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -1235,6 +1398,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.10.0" @@ -1422,6 +1591,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.7.35" diff --git a/Cargo.toml b/Cargo.toml index 86600ba..f6d8104 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,11 @@ edition = "2021" [dependencies] chrono = "0.4.38" +clap = { version = "4.5.16", features = ["derive"] } color-eyre = "0.6.3" ratatui = { version = "0.28.0", features = ["all-widgets"] } scylla = "0.13.2" +serde = { version = "1.0.208", features = ["derive"] } tokio = { version = "1.39.3", features = ["full"] } +toml = "0.8.19" uuid = { version = "1.10.0", features = ["v1", "v4"] } diff --git a/README.md b/README.md index 2f48008..a0d1be8 100644 --- a/README.md +++ b/README.md @@ -4,27 +4,38 @@ > A Better CQLsh REPL Built in Rust -**ScyllaSH** is a powerful and user-friendly REPL (Read-Eval-Print Loop) for ScyllaDB and Cassandra, designed to be a superior alternative to the default `cqlsh`. Built with Rust and leveraging the `ratatui` library for an enhanced terminal interface, ScyllaSH offers a range of features that make working with CQL (Cassandra Query Language) more efficient and enjoyable. +**ScyllaSH** is a powerful and user-friendly REPL (Read-Eval-Print Loop) for ScyllaDB and Cassandra, designed to be a +superior alternative to the default `cqlsh`. Built with Rust and leveraging the `ratatui` library for an enhanced +terminal interface, ScyllaSH offers a range of features that make working with CQL (Cassandra Query Language) more +efficient and enjoyable. ## Key Features -- **Enhanced REPL Experience**: ScyllaSH provides a more intuitive and responsive command-line interface, making it easier to execute CQL commands, navigate history, and manage sessions. +- **Enhanced REPL Experience**: ScyllaSH provides a more intuitive and responsive command-line interface, making it + easier to execute CQL commands, navigate history, and manage sessions. -- **Real-Time Metrics**: Monitor key metrics directly within the REPL. ScyllaSH displays performance statistics such as query latency, node health, and resource utilization, enabling you to make informed decisions as you work. +- **Real-Time Metrics**: Monitor key metrics directly within the REPL. ScyllaSH displays performance statistics such as + query latency, node health, and resource utilization, enabling you to make informed decisions as you work. -- **Auto-Completion**: ScyllaSH includes advanced auto-completion for CQL keywords, table names, and columns, reducing the need for constant reference checking and speeding up your workflow. +- **Auto-Completion**: ScyllaSH includes advanced auto-completion for CQL keywords, table names, and columns, reducing + the need for constant reference checking and speeding up your workflow. -- **Syntax Highlighting**: With built-in syntax highlighting, ScyllaSH helps you spot errors and understand complex queries at a glance. +- **Syntax Highlighting**: With built-in syntax highlighting, ScyllaSH helps you spot errors and understand complex + queries at a glance. -- **Improved History Management**: ScyllaSH retains your command history across sessions, making it easy to recall and re-execute previous commands. +- **Improved History Management**: ScyllaSH retains your command history across sessions, making it easy to recall and + re-execute previous commands. -- **Customizable Settings**: Tailor the REPL environment to your preferences with configurable settings for colors, prompts, and more. +- **Customizable Settings**: Tailor the REPL environment to your preferences with configurable settings for colors, + prompts, and more. -- **Extensible with Plugins**: ScyllaSH supports plugins, allowing you to extend its functionality with custom commands, integrations, and tools. +- **Extensible with Plugins**: ScyllaSH supports plugins, allowing you to extend its functionality with custom commands, + integrations, and tools. ## Installation -ScyllaSH is distributed as a standalone binary. You can install it via Cargo (Rust's package manager) or download the pre-built binaries from the [releases page](https://github.com/your-repo/ScyllaSH/releases). +ScyllaSH is distributed as a standalone binary. You can install it via Cargo (Rust's package manager) or download the +pre-built binaries from the [releases page](https://github.com/your-repo/ScyllaSH/releases). ### Via Cargo @@ -34,7 +45,8 @@ cargo install scyllash ### Pre-built Binary -1. Download the appropriate binary for your operating system from the [releases page](https://github.com/your-repo/ScyllaSH/releases). +1. Download the appropriate binary for your operating system from + the [releases page](https://github.com/your-repo/ScyllaSH/releases). 2. Extract the binary to a directory in your `$PATH`. 3. Run `scyllash` from your terminal. @@ -46,17 +58,20 @@ To start ScyllaSH, simply run: scyllash ``` -By default, ScyllaSH will attempt to connect to a ScyllaDB or Cassandra instance running on `localhost` at the default CQL port (`9042`). You can specify a different host and port using command-line arguments: +By default, ScyllaSH will attempt to connect to a ScyllaDB or Cassandra instance running on `localhost` at the default +CQL port (`9042`). You can specify a different host and port using command-line arguments: ```bash scyllash --host --port ``` -Once connected, you'll be greeted with the ScyllaSH prompt where you can start entering CQL commands. Use `Ctrl+D` to exit the REPL. +Once connected, you'll be greeted with the ScyllaSH prompt where you can start entering CQL commands. Use `Ctrl+D` to +exit the REPL. ## Configuration -ScyllaSH can be configured using a configuration file located at `~/.scyllash/config.toml`. Here, you can customize various aspects of the REPL, such as: +ScyllaSH can be configured using a configuration file located at `~/.config/scyllash/config.toml`. Here, you can +customize various aspects of the REPL, such as: - Prompt style - Color scheme @@ -66,23 +81,29 @@ ScyllaSH can be configured using a configuration file located at `~/.scyllash/co Example configuration file: ```toml -[prompt] -style = "ScyllaSH> " - -[colors] -keyword = "cyan" -string = "green" -number = "yellow" -error = "red" - -[metrics] -enabled = true -refresh_interval = 5 +[connection] +hostname = "127.0.0.1" +port = 9042 +username = "scylla" +password = "" +timeout = 10 + +# [scyllash] +# prompt = "scyllash> " +# color_scheme = "solarized_dark" + +# [cql] +# version = "3.3.1" + +# [metrics] +# enabled = true +# refresh_interval = 5 ``` ## Contributing -We welcome contributions from the community! Whether it's bug reports, feature requests, or pull requests, your help is appreciated. Please check out our [contributing guide](CONTRIBUTING.md) for more details. +We welcome contributions from the community! Whether it's bug reports, feature requests, or pull requests, your help is +appreciated. Please check out our [contributing guide](CONTRIBUTING.md) for more details. ## License @@ -90,7 +111,8 @@ ScyllaSH is licensed under the MIT License. See the [LICENSE](LICENSE) file for ## Acknowledgments -ScyllaSH is inspired by the need for a more powerful and flexible CQLsh experience. Special thanks to the creators of Rust, Ratatui, and ScyllaDB for providing the tools and inspiration to make this project possible. +ScyllaSH is inspired by the need for a more powerful and flexible CQLsh experience. Special thanks to the creators of +Rust, Ratatui, and ScyllaDB for providing the tools and inspiration to make this project possible. --- diff --git a/default_config.toml b/default_config.toml new file mode 100644 index 0000000..9873671 --- /dev/null +++ b/default_config.toml @@ -0,0 +1,6 @@ +[connection] +hostname = "127.0.0.1" +port = 9042 +username = "scylla" +password = "" +timeout = 10 diff --git a/src/args.rs b/src/args.rs new file mode 100644 index 0000000..7cf45e6 --- /dev/null +++ b/src/args.rs @@ -0,0 +1,105 @@ +use clap::{Args, Parser, Subcommand}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +static DEFAULT_SETTINGS_STR: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/default_config.toml")); + +static SETTINGS_PATH: &str = concat!(env!("HOME"), "/.config/scyllash"); + +#[derive(Parser)] +#[command(author, version, about, long_about = None)] +pub struct Arguments { + #[command(subcommand)] + pub command: Option, +} + +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Subcommand)] +pub enum Command { + /// Starts ScyllaSH with a provided connection + Run { + #[command(flatten)] + connection: ConnectionConfig, + }, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Args, Deserialize, Serialize)] +pub struct ConnectionConfig { + /// Scylla Host + #[arg(long, default_value_t = get_default_string_value("hostname"))] + pub hostname: String, + /// Scylla User + #[arg(short, long, default_value_t = get_default_string_value("username"))] + pub username: String, + + /// Scylla Password + #[arg(short, long, default_value_t = get_default_string_value("password"))] + pub password: String, + + /// Scylla Timeout + #[arg(short, long, default_value_t = 5)] + pub timeout: u64, +} + +fn get_default_string_value(key: &str) -> String { + let config = ConfigToml::default(); + let connection = config.connection.clone(); + match key { + "hostname" => { + if !config.connection.hostname.is_empty() { + connection.hostname + } else { + String::from("localhost:9042") + } + } + "username" => { + if !config.connection.username.is_empty() { + connection.username + } else { + String::from("scylla") + } + } + "password" => { + if !config.connection.password.is_empty() { + connection.password + } else { + String::from("") + } + } + _ => String::from(""), + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ConfigToml { + pub connection: ConnectionConfig, +} + +impl Default for ConfigToml { + fn default() -> Self { + let settings_path = Path::new(SETTINGS_PATH); + let file_path = settings_path.join("config.toml"); + + if !settings_path.exists() { + println!("No settings directory identified"); + fs::create_dir_all(settings_path).expect("Failed to create settings directory"); + } + + if !file_path.exists() { + println!("No config file identified"); + let mut file = File::create(&file_path).expect("Failed to create settings file"); + file + .write_all(DEFAULT_SETTINGS_STR.as_bytes()) + .expect("Failed to write default settings"); + } + + let toml_str = fs::read_to_string(&file_path).expect("Failed to read settings file"); + let cargo_toml: ConfigToml = + toml::from_str(&toml_str).expect("Failed to deserialize Cargo.toml"); + + cargo_toml + } +} diff --git a/src/lib.rs b/src/lib.rs index 7a96857..ed4a824 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod app; pub mod tui; +pub mod args; diff --git a/src/main.rs b/src/main.rs index eb27182..de4b155 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,29 @@ -use scyllash::{app::App, tui}; +use clap::Parser; +use scyllash::args::Command; +use scyllash::{app::App, args::Arguments, tui}; fn main() -> color_eyre::Result<()> { - color_eyre::install()?; - let mut terminal = tui::init()?; - let app_result = App::default().run(&mut terminal); - if let Err(err) = tui::restore() { - eprintln!( - "failed to restore terminal. Run `reset` or restart your terminal to recover: {}", - err - ); - } - app_result + let args = Arguments::parse(); + + match &args.command { + Some(Command::Run { .. }) => { + run_interface() + } + None => { + run_interface() + } + } +} + +fn run_interface() -> color_eyre::Result<()> { + color_eyre::install()?; + let mut terminal = tui::init()?; + let app_result = App::default().run(&mut terminal); + if let Err(err) = tui::restore() { + eprintln!( + "failed to restore terminal. Run `reset` or restart your terminal to recover: {}", + err + ); + } + app_result }