#![forbid(unsafe_code)]
use std::io::{BufRead, IsTerminal, Write};
use std::path::Path;
use std::process::{Command, ExitCode};
use tzselect_rs::{run, Host, Options};
struct RealHost {
stdin: std::io::Lines<std::io::StdinLock<'static>>,
}
impl Host for RealHost {
fn read_line(&mut self) -> Option<String> {
match self.stdin.next() {
Some(Ok(s)) => Some(s),
_ => None,
}
}
fn err(&mut self, s: &str) {
let mut e = std::io::stderr();
let _ = e.write_all(s.as_bytes());
let _ = e.flush();
}
fn out(&mut self, s: &str) {
let mut o = std::io::stdout();
let _ = o.write_all(s.as_bytes());
let _ = o.flush();
}
fn read_table(&self, path: &str) -> Option<String> {
std::fs::read_to_string(path).ok()
}
fn run_date(&self, tz: &str) -> Option<String> {
let out = Command::new("date")
.env("LANG", "C")
.env("TZ", tz)
.output()
.ok()?;
Some(
String::from_utf8_lossy(&out.stdout)
.trim_end_matches('\n')
.to_string(),
)
}
fn run_date_fmt(&self, tz: &str, fmt: &str) -> Option<String> {
let out = Command::new("date")
.env("TZ", tz)
.arg(format!("+{fmt}"))
.output()
.ok()?;
Some(
String::from_utf8_lossy(&out.stdout)
.trim_end_matches('\n')
.to_string(),
)
}
fn zone_readable(&self, path: &str) -> bool {
Path::new(path).is_file() && std::fs::File::open(path).is_ok()
}
fn stdout_is_tty(&self) -> bool {
std::io::stdout().is_terminal()
}
}
fn main() -> ExitCode {
let mut o = Options {
tzdir: std::env::var("TZDIR").unwrap_or_else(|_| {
std::env::current_dir()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|_| ".".to_string())
}),
tzversion: option_env!("TZVERSION")
.unwrap_or(env!("CARGO_PKG_VERSION"))
.to_string(),
..Options::default()
};
let args: Vec<String> = std::env::args().skip(1).collect();
let mut i = 0;
let mut positional: Vec<String> = Vec::new();
let mut opts_done = false;
while i < args.len() {
let a = &args[i];
if opts_done || !a.starts_with('-') || a == "-" {
positional.push(a.clone());
i += 1;
continue;
}
if a == "--" {
opts_done = true;
i += 1;
continue;
}
if let Some(long) = a.strip_prefix("--") {
match long {
"help" => {
println!("{}", tzselect_rs::usage(&o));
return ExitCode::SUCCESS;
}
"version" => {
println!("tzselect {}{}", o.pkgversion, o.tzversion);
return ExitCode::SUCCESS;
}
_ => {
eprintln!(
"{}: --{long}: unknown option; try '{} --help'",
o.argv0, o.argv0
);
return ExitCode::FAILURE;
}
}
}
let body = &a[1..];
let (flag, rest) = body.split_at(1);
let take_val = |rest: &str, i: &mut usize| -> Option<String> {
if !rest.is_empty() {
Some(rest.to_string())
} else {
*i += 1;
args.get(*i).cloned()
}
};
match flag {
"c" => match take_val(rest, &mut i) {
Some(v) => o.coord = Some(v),
None => {
eprintln!("{}: option requires an argument -- 'c'", o.argv0);
return ExitCode::FAILURE;
}
},
"n" => match take_val(rest, &mut i) {
Some(v) => o.location_limit = v.parse().unwrap_or(o.location_limit),
None => {
eprintln!("{}: option requires an argument -- 'n'", o.argv0);
return ExitCode::FAILURE;
}
},
"t" => match take_val(rest, &mut i) {
Some(v) => o.zonetabtype = v,
None => {
eprintln!("{}: option requires an argument -- 't'", o.argv0);
return ExitCode::FAILURE;
}
},
other => {
eprintln!("{}: illegal option -- {other}", o.argv0);
eprintln!("{}: try '{} --help'", o.argv0, o.argv0);
return ExitCode::FAILURE;
}
}
i += 1;
}
if let Some(first) = positional.first() {
eprintln!("{}: {first}: unknown argument", o.argv0);
return ExitCode::FAILURE;
}
let mut host = RealHost {
stdin: std::io::stdin().lock().lines(),
};
match run(&o, &mut host) {
0 => ExitCode::SUCCESS,
_ => ExitCode::FAILURE,
}
}