use std::{
collections::{HashMap, VecDeque},
fs::{remove_file, OpenOptions},
io::Read,
io::{stdin, stdout, Write},
path::PathBuf,
process::exit,
str::Chars,
};
use kconfig_parser::MacroLexer;
use kconfig_represent::{ConfigRegistry, LoadError, Menu, MenuItemDescriptor};
use crate::util;
use super::{CargoKconfigConfig, ConfigGetOpt, ConfigSetOpt};
pub struct ConfigSetController {
opt: ConfigSetOpt,
}
impl ConfigSetController {
pub fn run(&self) {
let mut config = ConfigGetController::load();
let default_config = CargoKconfigConfig::default();
match &self.opt {
ConfigSetOpt::Kconfig(v) => match v.is_empty() {
true => config.set_kconfig(&default_config.kconfig()),
false => config.set_kconfig(&PathBuf::from(v)),
},
ConfigSetOpt::Dotconfig(v) => match v.is_empty() {
true => config.set_dotconfig(&default_config.dotconfig()),
false => config.set_dotconfig(&PathBuf::from(v)),
},
}
Self::write(&config);
}
fn write(config: &CargoKconfigConfig) {
let default_config = CargoKconfigConfig::default();
let should_delete = match OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(".cargo-kconfig")
{
Ok(mut f) => {
if default_config.kconfig() != config.kconfig() {
writeln!(f, "input = {}", Self::pathbuf_tostring(&config.kconfig())).unwrap();
}
if default_config.dotconfig() != config.dotconfig() {
writeln!(
f,
"output = {}",
Self::pathbuf_tostring(&config.dotconfig())
)
.unwrap();
}
f.flush().unwrap();
f.metadata().unwrap().len() == 0
}
Err(e) => panic!("Could not write to cargo-kconfig configuration file: {e}"),
};
if should_delete {
remove_file(".cargo-kconfig").unwrap();
}
}
fn pathbuf_tostring(b: &PathBuf) -> String {
b.to_string_lossy()
.replace("\\", "\\\\")
.replace("\n", "\\\n")
.replace("\r", "\\\r")
}
}
impl From<ConfigSetOpt> for ConfigSetController {
fn from(opt: ConfigSetOpt) -> Self {
Self { opt }
}
}
pub struct ConfigGetController {
opt: ConfigGetOpt,
}
impl From<ConfigGetOpt> for ConfigGetController {
fn from(opt: ConfigGetOpt) -> Self {
Self { opt }
}
}
impl ConfigGetController {
pub fn run(&self) {
let config = Self::load();
match &self.opt {
ConfigGetOpt::List => {
println!("input = {}", config.kconfig().display());
println!("output = {}", config.dotconfig().display());
}
ConfigGetOpt::Kconfig => println!("{}", config.kconfig().display()),
ConfigGetOpt::Dotconfig => println!("{}", config.dotconfig().display()),
}
}
pub fn load() -> CargoKconfigConfig {
match OpenOptions::new().read(true).open(".cargo-kconfig") {
Ok(mut f) => Self::load_from_stream(&mut f),
Err(_) => CargoKconfigConfig::default(),
}
}
pub fn load_from_stream<R>(r: &mut R) -> CargoKconfigConfig
where
R: Read,
{
let mut buf = String::new();
if let Err(e) = r.read_to_string(&mut buf) {
eprintln!("Can not read found cargo-kconfig configuration: {e}");
return CargoKconfigConfig::default();
}
let mut chars = buf.chars();
let mut rawentries = HashMap::new();
loop {
let rawentry = Self::read_next_entry(&mut chars);
match rawentry {
Some((optname, optvalue)) => {
rawentries.insert(optname, optvalue);
}
None => break,
}
}
CargoKconfigConfig::from(&rawentries)
}
pub fn read_next_entry(chars: &mut Chars) -> Option<(String, String)> {
let mut is_lhs = true;
let mut is_escape = false;
let mut lhs = String::new();
let mut rhs = String::new();
loop {
let maybe_c = chars.next();
match maybe_c {
Some(c) => {
if is_lhs {
if c == '\n' || c == '\r' {
if lhs.len() != 0 {
eprintln!(
"Configuration key contains illegal value: {}, skipped",
c as i32
);
}
} else if c == '=' && !is_escape {
is_lhs = false;
} else if is_escape {
is_escape = false;
lhs.push(c);
} else if c == '\\' {
is_escape = true;
} else {
lhs.push(c);
}
} else {
if is_escape {
is_escape = false;
rhs.push(c);
} else if c == '\\' {
is_escape = true;
} else if c == '\r' || c == '\n' {
break;
} else {
rhs.push(c);
}
}
}
None => {
if is_lhs {
if lhs.len() != 0 {
eprintln!("Configuration file ended premature")
}
return None;
}
break;
}
}
}
Some((lhs.trim().to_string(), rhs.trim().to_string()))
}
}
pub struct ListController {
vec_deque: VecDeque<String>,
maxwidth: u16,
}
impl From<&Vec<String>> for ListController {
fn from(vec: &Vec<String>) -> Self {
Self {
vec_deque: vec.clone().into_iter().collect::<VecDeque<String>>(),
maxwidth: 80,
}
}
}
impl ListController {
pub fn run(&self) {
let registry = load_config_registry();
let maybe_menu = registry.lookup_menu(®istry.main_menu_name());
match maybe_menu {
Some(menu) => {
if !self.print_maybe_menu(®istry, &menu, &self.vec_deque, 0) {
eprintln!("⛔ Menu not found")
}
}
None => eprintln!("⛔ Menu not found"),
}
}
fn print_maybe_menu(
&self,
registry: &ConfigRegistry,
menu: &Box<Menu>,
vec_deque: &VecDeque<String>,
offset: u8,
) -> bool {
let mut found = false;
if vec_deque.len() == 0 {
for descriptor in menu.sub_descriptors() {
self.print_value(offset, &Self::format_descriptor(registry, &descriptor));
}
found = true;
} else {
let mut new_deque = vec_deque.clone();
let item = new_deque.pop_front().unwrap();
for descriptor in menu.sub_descriptors() {
if descriptor.to_string() == item {
let maybe_menu = registry.lookup_menu(&item);
match maybe_menu {
Some(menu) => {
self.print_value(
offset,
&Self::format_descriptor(registry, &descriptor),
);
if self.print_maybe_menu(registry, menu, &new_deque, offset + 1) {
found = true;
}
}
None => (),
}
}
}
}
found
}
fn print_value(&self, offset: u8, item: &str) {
let offset = match offset < (self.maxwidth / 4) as u8 {
true => offset,
false => (self.maxwidth / 4) as u8,
};
let mut item = item.chars();
let mut next = item.next();
let mut new_iteration = false;
loop {
if new_iteration {
println!("");
}
if let Some(_) = next {
for i in 0..offset {
match new_iteration {
true => {
print!("| ")
}
false => {
if i == (offset - 1) {
print!("+-")
} else {
print!("| ")
}
}
}
}
}
if new_iteration {
print!(" ");
}
for _ in (offset as u16)..self.maxwidth {
match next {
Some(c) => print!("{}", c),
None => {
println!("");
return;
}
}
next = item.next();
}
if let None = next {
break;
} else {
new_iteration = true;
}
}
}
fn format_descriptor(registry: &ConfigRegistry, descriptor: &MenuItemDescriptor) -> String {
let maybe_menu = registry.lookup_menu(&descriptor.to_string());
match maybe_menu {
Some(menu) => format!("{menu} --->"),
None => {
let maybe_config_item = registry.lookup_config(&descriptor.to_string());
match maybe_config_item {
Some(item) => {
let prefix = match item.value_is_default() {
true => "[ ]",
false => "[x]",
};
format!(
"{} {} [{}]: {}",
prefix,
descriptor,
item.prompt(),
item.to_string_value()
)
}
None => format!(""),
}
}
}
}
}
pub struct InfoController {
name: String,
}
impl From<&str> for InfoController {
fn from(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
}
impl InfoController {
pub fn run(&self) {
let registry = load_config_registry();
let maybe_config = registry.lookup_config(&self.name);
match maybe_config {
Some(config) => {
let mut first = true;
for item in config.info() {
if first {
first = false;
} else {
println!("");
}
println!("{}", item);
}
if first {
println!("⛔ No information available for entry {}", &self.name);
}
}
None => eprintln!("⛔ Configuration item {} not available", self.name),
}
}
}
pub struct UpdateController {
name: String,
}
impl From<&str> for UpdateController {
fn from(name: &str) -> Self {
Self {
name: name.to_string(),
}
}
}
impl UpdateController {
pub fn run(&self) {
let mut registry = load_config_registry();
let mutation_mode = registry.config_mutation_mode(&self.name);
let maybe_config = registry.lookup_config_mut(&self.name);
match maybe_config {
Some(config) => match mutation_mode {
kconfig_represent::ConfigMutationMode::ThroughString(mode) => match mode {
kconfig_represent::InputMode::String => {
println!(
"{} [Current value: {}] ",
config.prompt(),
config.to_string_value()
);
config.from_string_value(&Self::read_lines());
}
kconfig_represent::InputMode::Hex => {
print!(
"{} [Current value: {}]: ",
config.prompt(),
config.to_string_value()
);
config.from_string_value(&Self::read_line());
}
kconfig_represent::InputMode::Int => {
print!(
"{} [Current value: {}]: ",
config.prompt(),
config.to_string_value()
);
config.from_string_value(&Self::read_line());
}
},
kconfig_represent::ConfigMutationMode::ThroughStep => {
if let Err(e) = config.next_step() {
panic!("⛔ Could not mutate value: {e}");
}
}
kconfig_represent::ConfigMutationMode::None => panic!(
"⛔ Configuration item {} should be a mutable item",
self.name
),
},
None => eprintln!("⛔ Configuration item {} not available", self.name),
}
let config = ConfigGetController::load();
if let Err(e) = registry.write_dotconfig_file(&config.dotconfig().to_string_lossy()) {
panic!(
"⛔ Could not write to {} caused by: {}",
config.dotconfig().to_string_lossy(),
e
)
}
}
fn read_line() -> String {
if let Err(e) = stdout().flush() {
panic!("⛔ Could not flush output to terminal: {e}");
}
let mut buf = String::new();
if let Err(e) = stdin().read_line(&mut buf) {
panic!("⛔ Could not read data from terminal: {e}");
}
buf
}
fn read_lines() -> String {
println!("--- Enter two empty newlines to complete the value ----");
if let Err(e) = stdout().flush() {
panic!("⛔ Could not flush output to terminal: {e}");
}
let mut result = String::new();
let mut first_empty_newline = false;
let mut second_empty_newline = false;
while !(first_empty_newline && second_empty_newline) {
let mut buf = String::new();
let has_previous = result.len() > 0;
if let Err(e) = stdin().read_line(&mut buf) {
panic!("⛔ Could not read data from terminal: {e}");
}
let mut processed_len = 0;
let len = buf.len();
let mut new_buf = String::new();
for c in buf.chars() {
if processed_len < len - 1 {
new_buf.push(c);
}
processed_len += 1;
}
stdout().flush().unwrap();
if new_buf.is_empty() {
second_empty_newline = first_empty_newline;
first_empty_newline = true;
} else {
if first_empty_newline {
result.push_str("\n");
}
first_empty_newline = false;
second_empty_newline = false;
if has_previous {
result.push_str("\n");
}
result.push_str(&new_buf);
}
}
result
}
}
fn load_config_registry() -> ConfigRegistry {
let config = ConfigGetController::load();
let mut lexer = MacroLexer::new(util::lex_from_file(&config.kconfig().to_string_lossy()));
let ast = crate::util::parse(&mut lexer);
let mut registry = match ConfigRegistry::new(&ast, &lexer.symbol_table()) {
Ok(registry) => registry,
Err(e) => {
eprintln!("⛔ {}", e);
exit(1);
}
};
let maybe_load_error: Option<LoadError> =
util::load_dot_config_into_registry(&config.dotconfig().to_string_lossy(), &mut registry);
if let Some(load_error) = maybe_load_error {
eprintln!("{}", load_error.to_string())
}
registry
}