Lines
0 %
Functions
Branches
100 %
use exitfailure::ExitFailure;
use log::LevelFilter;
use ratatui::Terminal;
use server::config::set_config;
use server::start;
use sqlx::types::Uuid;
use std::env;
use std::str::FromStr;
use structopt::StructOpt;
use tokio::runtime::Handle;
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::{
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::backend::CrosstermBackend;
use std::sync::mpsc as std_mpsc;
use std::{io, io::Write};
use tokio::sync::mpsc;
mod run;
mod ui;
use run::{
CliAccountBalance, CliAccountCreate, CliAccountList, CliCommand, CliCommodityCreate,
CliCommodityList, CliGetConfig, CliSelectColumn, CliSetConfig, CliTransactionCreate,
CliTransactionList, CliVersion, CommandNode,
use ui::{App, run_app};
#[derive(Debug)]
struct FieldContentPair {
field: String,
content: String,
}
impl FromStr for FieldContentPair {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.splitn(2, '=').collect();
if parts.len() == 2 {
Ok(FieldContentPair {
field: parts[0].to_string(),
content: parts[1].to_string(),
})
} else {
Err("Expected format `field=content`".to_string())
// First, create a bridge between sync and async channels
struct LogWriter {
sender: std_mpsc::Sender<String>,
impl Write for LogWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
if let Ok(s) = String::from_utf8(buf.to_vec())
&& let Err(e) = self.sender.send(s)
{
eprintln!("Failed to send to std channel: {e}");
Ok(buf.len())
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
#[derive(StructOpt)]
struct Cli {
#[structopt(short, long, parse(try_from_str))]
userid: Uuid,
#[structopt(short, long)]
database: Option<String>,
#[structopt(long, parse(try_from_str))]
setopt: Option<FieldContentPair>,
/// Account type
#[structopt(long, parse(try_from_str), default_value = "Debug")]
loglevel: LevelFilter,
#[tokio::main]
async fn main() -> Result<(), ExitFailure> {
let args = Cli::from_args();
// Create both sync and async channels
let (std_tx, std_rx) = std_mpsc::channel::<String>();
let (tx, rx) = mpsc::channel::<String>(1024);
let _bridge_handle = tokio::spawn({
let tx = tx.clone();
async move {
loop {
match std_rx.recv() {
Ok(msg) => match tx.send(msg.clone()).await {
Ok(()) => {}
Err(e) => {
eprintln!("Bridge send error: {e}");
break;
},
eprintln!("Bridge receive error: {e}");
eprintln!("Bridge task ended");
});
log::info!("Started");
env_logger::Builder::new()
.filter_level(args.loglevel)
.target(env_logger::Target::Pipe(Box::new(LogWriter {
sender: std_tx,
})))
.init();
if let Some(db) = args.database {
unsafe {
env::set_var("DATABASE_URL", db);
Handle::current()
.spawn(start().await)
.await
.unwrap()
.unwrap();
if let Some(option) = args.setopt {
set_config(&option.field, option.content.into()).await?;
// Terminal setup
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// Create demo command tree
let commands = vec![
CliVersion::node(),
CommandNode {
name: "transaction".to_string(),
command: None,
comment: "Access to transactions".to_string(),
subcommands: vec![CliTransactionList::node(), CliTransactionCreate::node()],
arguments: vec![],
name: "account".to_string(),
comment: "Access to accounts".to_string(),
subcommands: vec![
CliAccountList::node(),
CliAccountBalance::node(),
CliAccountCreate::node(),
],
name: "commodity".to_string(),
comment: "Access to commodities".to_string(),
subcommands: vec![CliCommodityList::node(), CliCommodityCreate::node()],
name: "config".to_string(),
comment: "Access to configuration".to_string(),
subcommands: vec![CliGetConfig::node(), CliSetConfig::node()],
name: "sql".to_string(),
comment: "Access to SQL database".to_string(),
subcommands: vec![CliSelectColumn::node()],
];
let mut app = App::new(rx, args.userid);
let res = run_app(&mut terminal, &mut app, &commands).await;
// Cleanup
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
println!("Error: {err:?}");