/* * Copyright (c) 2022–2023 Emma Tebibyte * SPDX-License-Identifier: AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see https://www.gnu.org/licenses/. */ use std::{ env::args, fs::File, io::Read, iter::Peekable, os::fd::{ FromRawFd }, path::Path, str::FromStr, }; use toml::Value; use yacexits::*; fn parse_toml( mut root: Value, mut tabkey: Peekable>, index: Option, ) -> Result { let mut out = String::new(); while let Some(item) = tabkey.next() { let mut value = match root.get(item) { Some(val) => val, None => { return Err((format!("{}: No such table or key.", item), EX_DATAERR)); }, }; match value { Value::Array(array) => { let i = match index { Some(i) => i, None => { for v in array.iter() { out.push_str(&format!("{}\n", v.to_string())); } continue; }, }; match array.get(i) { Some(val) => value = val, None => { return Err((format!("No value at index {}.", i), EX_DATAERR)); }, }; }, Value::Table(table) => { if tabkey.peek().is_some() { root = toml::Value::Table(table.to_owned()); continue; }; }, _ => { if tabkey.peek().is_some() { return Err((format!("{}: Not a table.", item), EX_DATAERR)); } }, }; match value { Value::Array(_) => {}, Value::Boolean(boolean) => out.push_str(&format!("{:?}\n", boolean)), Value::Datetime(datetime) => out.push_str(&format!("{:?}\n", datetime)), Value::Float(float) => out.push_str(&format!("{:?}\n", float)), Value::Integer(int) => out.push_str(&format!("{:?}\n", int)), Value::String(string) => { let contents = string.to_owned(); let mut lines: Vec<&str> = contents.lines().collect(); if lines.last().unwrap().is_empty() { _ = lines.pop(); } for line in lines.iter() { out.push_str(&format!("{}\n", line)); } }, _ => return Err((format!("{:?}: No such key.", item), EX_DATAERR)), }; } Ok(out) } fn main() { let argv: Vec = args().collect(); let usage_info = format!("Usage: {} [table.]key[[index]] [file...]", argv[0]); if argv.len() <= 1 { eprintln!("{}", usage_info); exit(EX_USAGE); } let input = argv.get(2).unwrap_or(&"".to_owned()).to_owned(); let mut content = Vec::new(); match input.as_str() { "-" | "" => unsafe { File::from_raw_fd(0) }, _ => { File::open(Path::new(&input)).unwrap_or_else(|_| { eprintln!( "{}: {}: No such file or directory.", argv[0], &input ); exit(EX_UNAVAILABLE); }) }, }.read_to_end(&mut content).unwrap_or_else(|_| { eprintln!("{}: Could not read input.", argv[0]); exit(EX_OSERR); }); let mut tabkey: Vec<&str> = argv[1].split(".").collect(); let mut indexvec = Vec::new(); let mut index: Option = None; if tabkey.iter().skip(1).peekable().peek().is_some() { indexvec = tabkey[1].split(&['[', ']'][..]).collect(); tabkey[1] = indexvec.remove(0); }; if ! indexvec.is_empty() { let istr = indexvec.remove(0); match usize::from_str(istr) { Ok(i) => index = Some(i), Err(_) => { eprintln!("{}: {}: Cannot index by given value.", argv[0], istr); exit(EX_USAGE); }, }; } let root = String::from_utf8(content) .unwrap_or_else(|_| { eprintln!("{}: Input is not valid UTF-8.", argv[0]); exit(EX_DATAERR); }) .parse::() .unwrap_or_else(|_| { eprintln!("{}: Unable to parse TOML.", argv[0]); exit(EX_DATAERR); }); let valiter = tabkey.iter().peekable(); print!( "{}", match parse_toml(root, valiter, index) { Ok(val) => val, Err((err, code)) => { eprintln!("{}: {}", argv[0], err); exit(code); }, }, ); }