pub mod config;
use crate::config::*;
use clap::Parser;
use clap::Subcommand;
use cli_clipboard::ClipboardContext;
use cli_clipboard::ClipboardProvider;
use colored::Colorize;
use pathsearch::find_executable_in_path;
use regex::RegexBuilder;
use std::error::Error;
use std::path::Path;
use std::process::Command;
use std::process::Stdio;
#[derive(Parser)]
#[command(author, version, about, long_about)]
pub struct Cli {
#[arg(short, long, global = true)]
pub verbose: bool,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand)]
pub enum Commands {
#[clap(alias("x"))]
Xournal {
#[command(subcommand)]
action: XournalAction,
},
Microci {
#[command(subcommand)]
action: MicroCIAction,
},
}
#[derive(Subcommand)]
pub enum XournalAction {
#[clap(alias("o"))]
Open {
#[arg(required = true, num_args = 1)]
hash: String,
},
#[clap(alias("s"))]
Search {
#[arg(required = true, num_args = 1)]
text: String,
},
#[clap(alias("b"))]
Bookmark {
#[arg(required = true, num_args = 1)]
hash: String,
},
}
#[derive(Subcommand)]
pub enum MicroCIAction {
#[clap(alias("m"))]
Install,
}
fn show_command(cmd: String) {
println!("CMD: {}", cmd.green().bold());
}
pub fn copy_text_to_clipboard(text: String) -> Result<(), Box<dyn Error>> {
let mut ctx = cli_clipboard::ClipboardContext::new()?;
ctx.set_contents(text.to_owned())?;
Ok(())
}
pub fn copy_text_from_clipboard() -> Result<String, Box<dyn Error>> {
let mut ctx = ClipboardContext::new()?;
let contents = ctx.get_contents()?;
Ok(contents)
}
pub fn bin_xournalpp() -> &'static str {
match std::env::consts::OS {
"linux" => "/usr/bin/xournalpp",
"macos" => "/Applications/Xournal++.app/Contents/MacOS/xournalpp",
&_ => todo!(),
}
}
fn install_via_apt(package: &str) {
match sudo::escalate_if_needed() {
Ok(_) => {
show_command(format!("sudo apt install {}", package));
let _status = Command::new("apt-get")
.arg("update")
.spawn()
.expect("apt-get update failure")
.wait();
let _status = Command::new("apt-get")
.arg("install")
.arg(package)
.spawn()
.expect("apt-get install failure")
.wait();
}
Err(e) => {
eprintln!("Failed to elevate: {}", e);
std::process::exit(1);
}
}
}
fn install_xournalpp() {
match std::env::consts::OS {
"linux" => {
install_via_apt("xournalpp");
}
"macos" => {
eprintln!("Install from https://github.com/xournalpp/xournalpp/releases/tag/nightly");
eprintln!("xattr -c /Applications/Xournal++.app");
eprintln!("codesign --force --deep --sign - /Applications/Xournal++.app");
std::process::exit(1);
}
_ => {
eprintln!(
"Error: Failure installing xournallpp in {}",
std::env::consts::OS
);
std::process::exit(1);
}
}
}
fn check_executable_exists(executable_name: &str) {
match find_executable_in_path(executable_name) {
Some(_path) => {
}
None => {
match executable_name {
"xournalpp" => {
install_xournalpp();
}
_ => todo!(),
}
std::process::exit(1);
}
}
}
pub fn locate_related_file(hash: &str) -> Option<String> {
let index_txt = &MUTABLE_CONFIG.get()?.lock().unwrap().index_txt;
let contents = std::fs::read_to_string(index_txt);
for line in contents.expect("Failure reading index.txt").lines() {
if line.starts_with(hash) {
let filename = line.split_whitespace().nth(1).unwrap();
let file_path = Path::new(filename);
if file_path.exists() {
println!("Found {}", filename);
return Some(filename.to_string());
} else {
println!("Not found {}", filename);
}
}
}
None
}
fn bring_app_to_front(app_name: &str) {
match std::env::consts::OS {
"macos" => {
let script = format!("tell application \"{}\" to activate", app_name);
Command::new("osascript")
.arg("-e")
.arg(&script)
.output()
.expect("Failed to execute AppleScript");
}
&_ => todo!(),
}
}
pub fn search_text(pattern: &str) -> Option<Vec<String>> {
let re = RegexBuilder::new(pattern)
.case_insensitive(true)
.build()
.expect("Invalid regex pattern");
let index_txt = &MUTABLE_CONFIG.get()?.lock().unwrap().index_txt;
let contents = std::fs::read_to_string(index_txt);
let mut list: Vec<String> = Vec::new();
for line in contents.expect("Failure reading index.txt").lines() {
if re.is_match(line) {
list.push(String::from(line));
}
}
if list.is_empty() { None } else { Some(list) }
}
pub fn show_bookmark(hash: &str) -> Option<Vec<String>> {
let re = RegexBuilder::new(hash)
.case_insensitive(true)
.build()
.expect("Invalid regex pattern");
let bookmarks_txt = &MUTABLE_CONFIG.get()?.lock().unwrap().bookmarks_txt;
let contents = std::fs::read_to_string(bookmarks_txt);
let mut list: Vec<String> = Vec::new();
for line in contents.expect("Failure reading bookmarks.txt").lines() {
if re.is_match(line) {
list.push(String::from(line));
}
}
if list.is_empty() { None } else { Some(list) }
}
pub fn cmd_xournal(action: XournalAction, _verbose: bool) -> Result<(), &'static str> {
match action {
XournalAction::Open { hash } => {
check_executable_exists(bin_xournalpp());
match locate_related_file(&hash) {
Some(filename) => {
let hash_and_filename = format!("{}\n{}", hash, filename);
let _ = copy_text_to_clipboard(hash_and_filename);
let _ = Command::new(bin_xournalpp())
.arg(filename)
.stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()
.expect("Failure to execute xournallpp")
.wait();
bring_app_to_front("Xournal++");
println!("Please check Xournal++ window");
Ok(())
}
None => Err("Hash not found at index.txt"),
}
}
XournalAction::Search { text } => match search_text(&text) {
Some(lines) => {
for line in lines {
println!("{}", &line);
}
Ok(())
}
None => Err("Not found"),
},
XournalAction::Bookmark { hash } => {
show_bookmark(&hash);
Ok(())
}
}
}
pub fn cmd_microci(action: MicroCIAction) -> Result<(), &'static str> {
match action {
MicroCIAction::Install => {
match std::env::consts::OS {
"linux" => match sudo::escalate_if_needed() {
Ok(_) => {
let url = "https://github.com/geraldolsribeiro/microci/releases/latest/download/microCI";
let _status = Command::new("curl")
.arg("-fsSL")
.arg(url)
.arg("-o")
.arg("/usr/bin/microCI")
.spawn()
.expect("curl microci")
.wait();
let _status = Command::new("chmod")
.arg("755")
.arg("/usr/bin/microCI")
.spawn()
.expect("chmod microci")
.wait();
let _status = Command::new("microCI")
.arg("--version")
.spawn()
.expect("microci --version")
.wait();
Ok(())
}
Err(e) => {
eprintln!("Failed to elevate: {}", e);
std::process::exit(1);
}
},
"macos" => {
let _status = Command::new("brew")
.arg("install")
.arg("geraldolsribeiro/tap/microci")
.spawn()
.expect("brew install microci")
.wait();
let _status = Command::new("microCI")
.arg("--version")
.spawn()
.expect("microci --version")
.wait();
Ok(())
}
&_ => todo!(),
}
}
}
}