drk: introduced interactive shell infra

This commit is contained in:
skoupidi
2025-06-12 16:45:03 +03:00
parent d82e958672
commit 603ebe74f8
8 changed files with 205 additions and 0 deletions

11
Cargo.lock generated
View File

@@ -2468,6 +2468,7 @@ dependencies = [
"darkfi_money_contract",
"easy-parallel",
"lazy_static",
"linenoise-rs",
"log",
"num-bigint",
"prettytable-rs",
@@ -4098,6 +4099,16 @@ dependencies = [
"url",
]
[[package]]
name = "linenoise-rs"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e207483a55cd856bc85af576f663b2ebd25616e7b624c4ba42cff9ec2f0631"
dependencies = [
"lazy_static",
"libc",
]
[[package]]
name = "linux-raw-sys"
version = "0.9.4"

View File

@@ -21,6 +21,7 @@ darkfi-serial = "0.5.0"
blake3 = "1.8.2"
bs58 = "0.5.1"
lazy_static = "1.5.0"
linenoise-rs = "0.1.1"
log = "0.4.27"
num-bigint = "0.4.6"
prettytable-rs = "0.10.0"

View File

@@ -26,6 +26,9 @@ wallet_pass = "changeme"
# darkfid JSON-RPC endpoint
endpoint = "tcp://127.0.0.1:8240"
# Path to interactive shell history file
history_path = "~/.local/share/darkfi/drk/localnet/history.txt"
# Testnet blockchain network configuration
[network_config."testnet"]
# Path to blockchain cache database
@@ -40,6 +43,9 @@ wallet_pass = "changeme"
# darkfid JSON-RPC endpoint
endpoint = "tcp://127.0.0.1:8340"
# Path to interactive shell history file
history_path = "~/.local/share/darkfi/drk/testnet/history.txt"
# Mainnet blockchain network configuration
[network_config."mainnet"]
# Path to blockchain cache database
@@ -53,3 +59,6 @@ wallet_pass = "changeme"
# darkfid JSON-RPC endpoint
endpoint = "tcp://127.0.0.1:8440"
# Path to interactive shell history file
history_path = "~/.local/share/darkfi/drk/mainnet/history.txt"

View File

@@ -113,6 +113,9 @@ pub async fn kaching() {
pub fn generate_completions(shell: &str) -> Result<()> {
// Sub-commands
// Interactive
let interactive = SubCommand::with_name("interactive").about("Enter Drk interactive shell");
// Kaching
let kaching = SubCommand::with_name("kaching").about("Fun");
@@ -504,6 +507,7 @@ pub fn generate_completions(shell: &str) -> Result<()> {
.help("Blockchain network to use");
let command = vec![
interactive,
kaching,
ping,
completions,

152
bin/drk/src/interactive.rs Normal file
View File

@@ -0,0 +1,152 @@
/* This file is part of DarkFi (https://dark.fi)
*
* Copyright (C) 2020-2025 Dyne.org foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use linenoise_rs::{
linenoise, linenoise_history_add, linenoise_history_load, linenoise_history_save,
linenoise_set_completion_callback, linenoise_set_hints_callback,
};
use darkfi::{system::StoppableTask, util::path::expand_path};
use crate::{
cli_util::{generate_completions, kaching},
Drk,
};
// TODO:
// 1. add rest commands handling, along with their completions and hints.
// 2. add input definitions, so you input from files not just stdin.
// 3. add output definitions, so you can output to files not just stdout.
// 4. create a transactions cache in the wallet db, so you can use it to handle them.
/// Auxiliary function to define the interactive shell completions.
fn completion(buf: &str, lc: &mut Vec<String>) {
if buf.starts_with("h") {
lc.push("help".to_string());
return
}
if buf.starts_with("k") {
lc.push("kaching".to_string());
return
}
if buf.starts_with("p") {
lc.push("ping".to_string());
return
}
if buf.starts_with("c") {
lc.push("completions".to_string());
}
}
/// Auxiliary function to define the interactive shell hints.
fn hints(buf: &str) -> Option<(String, i32, bool)> {
match buf {
"completions " => Some(("{shell}".to_string(), 35, false)), // 35 = magenta
_ => None,
}
}
/// Auxiliary function to start provided Drk as an interactive shell.
pub async fn interactive(drk: &Drk, history_path: &str) {
// Expand the history file path
let history_path = match expand_path(history_path) {
Ok(p) => p,
Err(e) => {
println!("Error while expanding history file path: {e}");
return
}
};
let history_path = history_path.into_os_string();
let history_file = history_path.to_str().unwrap();
// Set the completion callback. This will be called every time the
// user uses the <tab> key.
linenoise_set_completion_callback(completion);
// Set the shell hints
linenoise_set_hints_callback(hints);
// Load history from file.The history file is just a plain text file
// where entries are separated by newlines.
let _ = linenoise_history_load(history_file);
// Create a detached task to use for block subscription
let subscription_active = false;
let subscription_task = StoppableTask::new();
// Start the interactive shell
loop {
// Grab input or end if Ctrl-D or Ctrl-C was pressed
let Some(line) = linenoise("drk> ") else {
// Stop the subscription task if its active
if subscription_active {
subscription_task.stop().await;
}
// Write history file
let _ = linenoise_history_save(history_file);
return
};
// Check if line is empty
if line.is_empty() {
continue
}
// Add line to history
linenoise_history_add(&line);
// Parse command parts
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.is_empty() {
continue
}
// Handle command
match parts[0] {
"help" => println!("hhhheeeelp"),
"kaching" => kaching().await,
"ping" => handle_ping(drk).await,
"completions" => handle_completions(&parts),
_ => println!("Unreconized command: {}", parts[0]),
}
}
}
/// Auxiliary function to define the ping command handling.
async fn handle_ping(drk: &Drk) {
if let Err(e) = drk.ping().await {
println!("Error while executing ping command: {e}")
}
}
/// Auxiliary function to define the completions command handling.
fn handle_completions(parts: &[&str]) {
if parts.len() != 2 {
println!("Malformed `completions` command");
println!("Usage: completions {{shell}}");
}
if let Err(e) = generate_completions(parts[1]) {
println!("Error while executing completions command: {e}")
}
}

View File

@@ -41,6 +41,9 @@ pub mod token;
/// CLI utility functions
pub mod cli_util;
/// Drk interactive shell
pub mod interactive;
/// Wallet functionality related to Money
pub mod money;

View File

@@ -57,6 +57,7 @@ use drk::{
generate_completions, kaching, parse_token_pair, parse_tx_from_stdin, parse_value_pair,
},
dao::{DaoParams, ProposalRecord},
interactive::interactive,
money::BALANCE_BASE10_DECIMALS,
swap::PartialSwapData,
Drk,
@@ -100,6 +101,9 @@ struct Args {
// don't forget to update cli_util::generate_completions()
#[derive(Clone, Debug, Deserialize, StructOpt)]
enum Subcmd {
/// Enter Drk interactive shell
Interactive,
/// Fun
Kaching,
@@ -565,6 +569,10 @@ struct BlockchainNetwork {
#[structopt(short, long, default_value = "tcp://127.0.0.1:8240")]
/// darkfid JSON-RPC endpoint
endpoint: Url,
#[structopt(long, default_value = "~/.local/share/darkfi/drk/localnet/history.txt")]
/// Path to interactive shell history file
history_path: String,
}
/// Auxiliary function to parse darkfid configuration file and extract requested
@@ -648,6 +656,20 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
};
match args.command {
Subcmd::Interactive => {
let drk = new_wallet(
blockchain_config.cache_path,
blockchain_config.wallet_path,
blockchain_config.wallet_pass,
Some(blockchain_config.endpoint),
ex,
args.fun,
)
.await;
interactive(&drk, &blockchain_config.history_path).await;
drk.stop_rpc_client().await
}
Subcmd::Kaching => {
if !args.fun {
println!("Apparently you don't like fun...");

View File

@@ -22,3 +22,6 @@ wallet_pass = "testing"
# darkfid JSON-RPC endpoint
endpoint = "tcp://127.0.0.1:48240"
# Path to interactive shell history file
history_path = "drk/history.txt"