mod cli;
mod errors {
use error_chain::error_chain;
error_chain! {
foreign_links {
I3EstablishError(::i3ipc::EstablishError);
I3MessageError(::i3ipc::MessageError);
IoError(::std::io::Error);
}
links {
Lib(::i3nator::errors::Error, ::i3nator::errors::ErrorKind);
}
errors {
EditorNotFound {
description("cannot find an editor")
display("cannot find an editor. Please specify $VISUAL or $EDITOR")
}
NoConfigExist {
description("no configfiles exist")
display("no configfiles exist. Feel free to create one")
}
}
}
}
use crate::errors::*;
use clap::ArgMatches;
use error_chain::quick_main;
use getch::Getch;
use i3ipc::I3Connection;
use i3nator::{configfiles::ConfigFile, layouts::Layout, projects::Project};
use lazy_static::lazy_static;
use std::{
convert::Into,
env,
ffi::{OsStr, OsString},
fs::File,
io::{stdin, BufReader, Read},
process::{Command, ExitStatus},
};
static PROJECT_TEMPLATE: &[u8] = include_bytes!("../resources/project_template.toml");
lazy_static! {
static ref GETCH: Getch = Getch::new();
}
fn command_copy<C: ConfigFile>(matches: &ArgMatches<'static>) -> Result<()> {
let existing_configfile_name = matches.value_of_os("EXISTING").unwrap();
let new_configfile_name = matches.value_of_os("NEW").unwrap();
let existing_configfile = C::open(existing_configfile_name)?;
let new_configfile = existing_configfile.copy(new_configfile_name)?;
println!(
"Copied existing configfile '{}' to new configfile '{}'",
existing_configfile.name(),
new_configfile.name()
);
if !matches.is_present("no-edit") {
open_editor(&new_configfile)?;
if !matches.is_present("no-verify") {
verify_configfile(&new_configfile)?;
}
}
Ok(())
}
fn command_delete<C: ConfigFile>(matches: &ArgMatches<'static>) -> Result<()> {
let configfiles = matches.values_of_os("NAME").unwrap();
for configfile_name in configfiles {
C::open(configfile_name)?.delete()?;
println!("Deleted configfile '{}'", configfile_name.to_string_lossy());
}
Ok(())
}
fn command_edit<C: ConfigFile>(matches: &ArgMatches<'static>) -> Result<()> {
let configfile_name = matches.value_of_os("NAME").unwrap();
let configfile = C::open(configfile_name)?;
open_editor(&configfile)?;
if !matches.is_present("no-verify") {
verify_configfile(&configfile)?;
}
Ok(())
}
fn command_info<C: ConfigFile>(matches: &ArgMatches<'static>) -> Result<()> {
let configfile_name = matches.value_of_os("NAME").unwrap();
let configfile = C::open(configfile_name)?;
println!("Name: {}", configfile.name());
println!(
"Configuration path: {}",
configfile.path().to_string_lossy()
);
println!(
"Configuration valid: {}",
if configfile.verify().is_ok() {
"yes"
} else {
"NO"
}
);
Ok(())
}
fn command_layout(matches: &ArgMatches<'static>) -> Result<()> {
match matches.subcommand() {
("copy", Some(sub_matches)) => command_copy::<Layout>(sub_matches),
("delete", Some(sub_matches)) => command_delete::<Layout>(sub_matches),
("edit", Some(sub_matches)) => command_edit::<Layout>(sub_matches),
("info", Some(sub_matches)) => command_info::<Layout>(sub_matches),
("list", Some(sub_matches)) => command_list::<Layout>(sub_matches),
("new", Some(sub_matches)) => layout_new(sub_matches),
("rename", Some(sub_matches)) => command_rename::<Layout>(sub_matches),
("", None) =>
{
unreachable!()
}
_ =>
{
unreachable!()
}
}
}
fn command_list<C: ConfigFile>(matches: &ArgMatches<'static>) -> Result<()> {
let configfiles = C::list();
let quiet = matches.is_present("quiet");
if configfiles.is_empty() {
Err(ErrorKind::NoConfigExist.into())
} else {
if !quiet {
println!("i3nator {}:", C::prefix().to_string_lossy());
}
for configfile in configfiles {
if quiet {
println!("{}", configfile.to_string_lossy());
} else {
println!(" {}", configfile.to_string_lossy());
}
}
Ok(())
}
}
fn command_rename<C: ConfigFile>(matches: &ArgMatches<'static>) -> Result<()> {
let current_configfile_name = matches.value_of_os("CURRENT").unwrap();
let new_configfile_name = matches.value_of_os("NEW").unwrap();
let current_configfile = C::open(current_configfile_name)?;
println!(
"Renaming configfile from '{}' to '{}'",
current_configfile_name.to_string_lossy(),
new_configfile_name.to_string_lossy()
);
let new_configfile = current_configfile.rename(new_configfile_name)?;
if matches.is_present("edit") {
open_editor(&new_configfile)?;
if !matches.is_present("no-verify") {
verify_configfile(&new_configfile)?;
}
}
Ok(())
}
fn project_local(matches: &ArgMatches<'static>) -> Result<()> {
let project_path = matches.value_of_os("file").unwrap();
let mut project = Project::from_path(project_path)?;
let mut i3 = I3Connection::connect()?;
println!("Starting project '{}'", project.name);
project.start(
&mut i3,
matches.value_of_os("working-directory"),
matches.value_of("workspace"),
)?;
Ok(())
}
fn project_new(matches: &ArgMatches<'static>) -> Result<()> {
let project_name = matches.value_of_os("NAME").unwrap();
let project = Project::create_from_template(project_name, PROJECT_TEMPLATE)?;
println!("Created project '{}'", project.name);
if !matches.is_present("no-edit") {
open_editor(&project)?;
if !matches.is_present("no-verify") {
verify_configfile(&project)?;
}
}
Ok(())
}
fn project_start(matches: &ArgMatches<'static>) -> Result<()> {
let project_name = matches.value_of_os("NAME").unwrap();
let mut project = Project::open(project_name)?;
let mut i3 = I3Connection::connect()?;
println!("Starting project '{}'", project.name);
project.start(
&mut i3,
matches.value_of_os("working-directory"),
matches.value_of("workspace"),
)?;
Ok(())
}
fn project_verify(matches: &ArgMatches<'static>) -> Result<()> {
let configfiles: Vec<OsString> = matches
.values_of_os("NAME")
.map(|v| v.map(OsStr::to_os_string).collect::<Vec<_>>())
.unwrap_or_else(Project::list);
for configfile_name in configfiles {
if let Err(e) = Project::open(&configfile_name)?.verify() {
println!(
"Configuration INVALID: '{}'",
configfile_name.to_string_lossy()
);
println!("Error:");
println!(" {}", e);
println!();
} else {
println!(
"Configuration VALID: '{}'",
configfile_name.to_string_lossy()
);
}
}
Ok(())
}
fn layout_new(matches: &ArgMatches<'static>) -> Result<()> {
let layout_name = matches.value_of_os("NAME").unwrap();
let layout = if !matches.is_present("template") {
Layout::create(layout_name)?
} else {
let template = matches.value_of_os("template").unwrap();
let stdin_;
let reader: Box<dyn Read> = if template == "-" {
stdin_ = stdin();
Box::new(stdin_.lock())
} else {
Box::new(File::open(template)?)
};
let mut reader = BufReader::new(reader);
let mut bytes: Vec<u8> = Vec::new();
reader.read_to_end(&mut bytes)?;
Layout::create_from_template(layout_name, &bytes)?
};
println!("Created layout '{}'", layout.name);
if !matches.is_present("no-edit") {
open_editor(&layout)?;
}
Ok(())
}
fn get_editor() -> Result<OsString> {
env::var_os("VISUAL")
.or_else(|| env::var_os("EDITOR"))
.and_then(|s| if !s.is_empty() { Some(s) } else { None })
.ok_or_else(|| ErrorKind::EditorNotFound.into())
}
fn open_editor<C: ConfigFile>(configfile: &C) -> Result<ExitStatus> {
println!("Opening your editor to edit '{}'", configfile.name());
Command::new(get_editor()?)
.arg(configfile.path().as_os_str())
.status()
.map_err(|e| e.into())
}
fn verify_configfile<C: ConfigFile>(configfile: &C) -> Result<()> {
while let Err(e) = configfile.verify() {
println!();
println!("VERIFICATION FAILED!");
println!("Error:");
println!(" {}", e);
println!();
let mut ch: Option<char>;
while {
println!("What do you want to do?");
println!("(R)eopen editor, (A)ccept anyway");
ch = GETCH
.getch()
.ok()
.map(|byte| byte.to_ascii_lowercase())
.map(|byte| byte as char);
if ch.is_none() {
true
} else {
match ch {
Some('a') | Some('r') => false,
_ => true,
}
}
} {
}
match ch {
Some('a') => break,
Some('r') => open_editor(configfile)?,
_ => continue,
};
}
Ok(())
}
fn run() -> Result<()> {
let matches = cli::cli().get_matches();
match matches.subcommand() {
("copy", Some(sub_matches)) => command_copy::<Project>(sub_matches),
("delete", Some(sub_matches)) => command_delete::<Project>(sub_matches),
("edit", Some(sub_matches)) => command_edit::<Project>(sub_matches),
("info", Some(sub_matches)) => command_info::<Project>(sub_matches),
("layout", Some(sub_matches)) => command_layout(sub_matches),
("list", Some(sub_matches)) => command_list::<Project>(sub_matches),
("local", Some(sub_matches)) => project_local(sub_matches),
("new", Some(sub_matches)) => project_new(sub_matches),
("rename", Some(sub_matches)) => command_rename::<Project>(sub_matches),
("start", Some(sub_matches)) => project_start(sub_matches),
("verify", Some(sub_matches)) => project_verify(sub_matches),
("", None) =>
{
unreachable!()
}
_ =>
{
unreachable!()
}
}
}
#[cfg(unix)]
quick_main!(run);