#[cfg(feature = "tui")]
mod tui;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use songbook::{Song, Note, Key, STRINGS};
use songbook::song_library;
use songbook::{Fingering, StringState};
#[derive(Parser, Debug)]
#[command(name = "songbook")]
#[command(version, about, long_about = None)]
struct Args {
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Subcommand, Debug, Clone)]
enum Command {
Init,
Fret {
#[arg(short, long, default_value = "E, B, G, D, A, E", value_name = "TUNING")]
tuning: String
},
#[clap(alias = "cof")]
CircleOfFifth {
#[arg(short, long)]
key: Option<String>
},
Chord { chord: String },
Fingering {
#[arg(long, short)]
chord: String,
#[arg(long, short, num_args = 1..)]
fingering: Vec<String>,
},
Show {
path: PathBuf,
#[arg(short, long)]
key: Option<String>,
#[arg(short, long)]
chords: bool,
#[arg(short, long)]
rhythm: bool,
#[arg(short, long)]
fingerings: bool,
#[arg(short, long)]
notes: bool,
#[arg(long)]
colored: bool,
},
Edit { path: PathBuf },
#[command(subcommand)]
Add(AddSubcommand),
Sort,
Rm { paths: Vec<PathBuf> },
Mv {
#[arg(num_args = 1.., required = true)]
input_paths: Vec<PathBuf>,
output_path: PathBuf
},
Ls { path: Option<PathBuf> },
Tree { path: Option<PathBuf> },
Mkdir { path: PathBuf },
}
#[derive(Subcommand, Debug, Clone)]
enum AddSubcommand {
FromTxt {
path: PathBuf,
#[arg(long, short)]
artist: String,
#[arg(long, short)]
title: String,
},
FromChordpro { path: PathBuf },
FromSbp { path: PathBuf },
Empty {
#[arg(long, short)]
artist: String,
#[arg(long, short)]
title: String,
}
}
fn main() {
let args = Args::parse();
if let Some(command) = args.command {
match command {
Command::Init => song_library::init().expect("Error during initialisation!"),
Command::Fret { tuning } => {
let mut notes = [Note::A; STRINGS];
let mut counter = 0;
for n in tuning.split(", ") {
if let Some(note) = Note::new(&n) {
notes[counter] = note;
} else {
println!("Unknown note: {n}!");
return
}
counter += 1;
if counter == 6 { break }
}
if counter != STRINGS {
println!("Notes must be {STRINGS}!");
return
}
songbook::print_fretboard(¬es);
},
Command::CircleOfFifth{key} => {
let key = if let Some(k) = key.as_deref() { Key::new(k) }
else { None };
songbook::print_circle_of_fifth(key);
},
Command::Chord { chord } => {
if let Some(chord) = songbook::Chord::new(&chord) {
let fings = chord.get_fingerings(&songbook::STANDART_TUNING);
if let Some(text) = songbook::sum_text_in_fingerings(&fings, None) {
println!("{text}");
}
} else {
println!("Unknown chord!");
}
},
Command::Fingering { chord, fingering } => {
if fingering.len() != STRINGS {
println!("Len --fingering must be {}!", STRINGS);
return
}
let allowed = ["x", "0",
"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12",
"13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"
];
if !fingering.iter().all(|f|
allowed.iter().any(|a| a == f)
) {
println!("There's not allowed char in your fingering!");
return
}
let mut strings = [StringState::Muted; STRINGS];
for (i, f) in fingering.iter().enumerate() {
match f {
c if c == "x" => {},
c if c == "0" => strings[i] = StringState::Open,
c => {
let fret_num = c.parse::<u8>().unwrap();
strings[i] = StringState::FrettedOn(fret_num);
}
}
}
let fing = Fingering::new(strings, Some(chord)).unwrap();
song_library::add_fingering(&fing).expect("Error during saving a fingering!");
},
Command::Show { path, key, chords, rhythm, fingerings, notes, colored } => {
let key = if let Some(k) = key.as_deref() { Key::new(k) }
else { None };
song_library::show(&path, key, chords, rhythm, fingerings, notes, colored)
.expect("Error during geting song!");
},
Command::Edit { path } => {
song_library::edit(&path)
.expect("Error during editing song!");
},
Command::Add(subcommand) => match subcommand {
AddSubcommand::FromTxt { path, title, artist } => {
let song = Song::from_txt( &path, &title, &artist)
.expect("Error during adding a song!");
song_library::add(&song)
.expect("Error during adding a song!");
},
AddSubcommand::FromChordpro { path } => {
let song = Song::from_chordpro(&path)
.expect("Error during adding a song!");
song_library::add(&song)
.expect("Error during adding a song!");
},
AddSubcommand::FromSbp { path } => {
let songs = Song::from_sbp(&path)
.expect("Error during adding a song!");
for song in &songs {
if let Err(msg) = song_library::add(song) {
println!("Cannot add song: {} - {}! {}",
song.metadata.artist,
song.metadata.title,
msg
);
}
}
},
AddSubcommand::Empty { title, artist } => {
let song = Song::new(&title, &artist);
song_library::add(&song)
.expect("Error during adding a song!");
}
},
Command::Sort => song_library::sort()
.expect("Error during sorting!"),
Command::Rm { paths } => {
for path in &paths {
song_library::rm(&path)
.expect("Error during removing!");
}
},
Command::Mv {input_paths, output_path } => {
for input_path in &input_paths {
song_library::mv(&input_path, &output_path)
.expect("Error during moving!");
}
},
Command::Ls { path } => {
song_library::ls(path.as_deref())
.expect("Error during reading a dir!");
},
Command::Tree { path } => {
song_library::tree(path.as_deref())
.expect("Error during reading a dir!");
},
Command::Mkdir { path } => {
song_library::mkdir(&path)
.expect("Error during creating a dir!");
},
}
} else {
#[cfg(not(feature = "tui"))]
println!("There's a command required! Try 'songbook help' for more information");
#[cfg(feature = "tui")]
tui::main()
.expect("Error in TUI!");
}
}