use std::collections::BTreeMap;
use std::io;
use std::path::{Component, Path, PathBuf};
pub use crate::checker::observe_inputs::{LiveRead, LiveSource};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stream {
Out,
Err,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
Red,
Yellow,
Cyan,
}
pub trait Host {
fn write(&mut self, stream: Stream, text: &str, color: Option<Color>);
fn read_stdin_line(&self) -> io::Result<Option<String>> {
Err(io::Error::new(io::ErrorKind::Unsupported, "stdin is unavailable on this host"))
}
fn cwd(&self) -> PathBuf;
fn read_to_string(&self, path: &Path) -> io::Result<String>;
fn write_file(&mut self, path: &Path, contents: &str) -> io::Result<()>;
fn is_file(&self, path: &Path) -> bool;
fn is_dir(&self, path: &Path) -> bool;
fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>>;
fn create_dir_all(&mut self, path: &Path) -> io::Result<()>;
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()>;
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
Err(io::Error::new(io::ErrorKind::Unsupported, format!("cannot remove '{}' on this host", path.display())))
}
fn rename(&mut self, from: &Path, to: &Path) -> io::Result<()>;
fn now(&self) -> f64;
fn max_memory_gb(&self) -> f64;
fn random_seed(&mut self) -> u32;
fn unix_time(&self) -> f64;
fn threads(&self) -> Option<Threads> {
None
}
fn open_live(&self, inputs: &[LiveInput]) -> Box<dyn LiveSource + '_> {
Box::new(SequentialLive { host: self, inputs: inputs.to_vec(), current: 0, lines: None })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LiveInput {
Stdin,
Path(PathBuf),
}
struct SequentialLive<'a, H: Host + ?Sized> {
host: &'a H,
inputs: Vec<LiveInput>,
current: usize,
lines: Option<std::vec::IntoIter<String>>,
}
impl<H: Host + ?Sized> LiveSource for SequentialLive<'_, H> {
fn next(&mut self) -> LiveRead {
let input = self.current;
let line = match self.inputs.get(input) {
None => return LiveRead::End { input },
Some(LiveInput::Stdin) => self.host.read_stdin_line(),
Some(LiveInput::Path(p)) => {
if self.lines.is_none() {
match self.host.read_to_string(p) {
Ok(text) => {
let lines: Vec<String> = text.lines().map(String::from).collect();
self.lines = Some(lines.into_iter());
}
Err(e) => return LiveRead::Error { input, error: e.to_string() },
}
}
Ok(self.lines.as_mut().and_then(|l| l.next()))
}
};
match line {
Ok(Some(line)) => LiveRead::Line { input, line },
Ok(None) => {
self.current += 1;
self.lines = None;
LiveRead::End { input }
}
Err(e) => LiveRead::Error { input, error: e.to_string() },
}
}
}
pub type Job<'a> = Box<dyn FnOnce() + Send + 'a>;
#[derive(Debug, Clone, Copy)]
pub struct Threads {
pub available: usize,
pub run: for<'a> fn(jobs: Vec<Job<'a>>, main: &mut (dyn FnMut() + 'a)),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Exit(pub i32);
pub(crate) fn report_and_exit(host: &mut dyn Host, msg: &str) -> Exit {
host.write(Stream::Err, "Error: ", Some(Color::Red));
host.write(Stream::Err, msg, Some(Color::Yellow));
host.write(Stream::Err, "\n", None);
Exit(1)
}
pub(crate) fn write_output(host: &mut dyn Host, path: &str, contents: &str) -> Result<(), Exit> {
let full = full_path(&host.cwd(), Path::new(path));
host.write_file(&full, contents)
.map_err(|e| report_and_exit(host, &format!("Could not write file '{}': {}", path, e)))
}
pub trait HostExt: Host {
fn out_line(&mut self, s: &str) {
self.write(Stream::Out, s, None);
self.write(Stream::Out, "\n", None);
}
fn out_colored_line(&mut self, s: &str, c: Color) {
self.write(Stream::Out, s, Some(c));
self.write(Stream::Out, "\n", None);
}
}
impl<T: Host + ?Sized> HostExt for T {}
#[cfg(feature = "cli")]
pub(crate) struct Buffered<'a> {
pub inner: &'a mut dyn Host,
pub out: Vec<(Stream, String, Option<Color>)>,
}
#[cfg(feature = "cli")]
impl<'a> Buffered<'a> {
pub fn new(inner: &'a mut dyn Host) -> Self {
Buffered { inner, out: Vec::new() }
}
pub fn errors(&self) -> String {
self.out.iter().filter(|(s, ..)| *s == Stream::Err).map(|(_, t, _)| t.as_str()).collect()
}
}
#[cfg(feature = "cli")]
impl Host for Buffered<'_> {
fn write(&mut self, stream: Stream, text: &str, color: Option<Color>) {
self.out.push((stream, text.to_string(), color));
}
fn cwd(&self) -> PathBuf {
self.inner.cwd()
}
fn read_to_string(&self, path: &Path) -> io::Result<String> {
self.inner.read_to_string(path)
}
fn write_file(&mut self, path: &Path, contents: &str) -> io::Result<()> {
self.inner.write_file(path, contents)
}
fn is_file(&self, path: &Path) -> bool {
self.inner.is_file(path)
}
fn is_dir(&self, path: &Path) -> bool {
self.inner.is_dir(path)
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
self.inner.read_dir(path)
}
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
self.inner.create_dir_all(path)
}
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
self.inner.remove_dir_all(path)
}
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
self.inner.remove_file(path)
}
fn rename(&mut self, from: &Path, to: &Path) -> io::Result<()> {
self.inner.rename(from, to)
}
fn now(&self) -> f64 {
self.inner.now()
}
fn max_memory_gb(&self) -> f64 {
self.inner.max_memory_gb()
}
fn random_seed(&mut self) -> u32 {
self.inner.random_seed()
}
fn unix_time(&self) -> f64 {
self.inner.unix_time()
}
fn threads(&self) -> Option<Threads> {
self.inner.threads()
}
}
#[cfg(feature = "cli")]
pub(crate) struct Quiet<'a> {
pub inner: &'a mut dyn Host,
pub quiet: bool,
shown: bool,
}
#[cfg(feature = "cli")]
impl<'a> Quiet<'a> {
pub fn new(inner: &'a mut dyn Host, quiet: bool) -> Self {
Quiet { inner, quiet, shown: false }
}
}
#[cfg(feature = "cli")]
impl Host for Quiet<'_> {
fn write(&mut self, stream: Stream, text: &str, color: Option<Color>) {
if stream == Stream::Err || !self.quiet {
return self.inner.write(stream, text, color);
}
if color.is_some() || (self.shown && text == "\n") {
self.shown = true;
self.inner.write(stream, text, color);
} else {
self.shown = false;
}
}
fn read_stdin_line(&self) -> io::Result<Option<String>> {
self.inner.read_stdin_line()
}
fn cwd(&self) -> PathBuf {
self.inner.cwd()
}
fn read_to_string(&self, path: &Path) -> io::Result<String> {
self.inner.read_to_string(path)
}
fn write_file(&mut self, path: &Path, contents: &str) -> io::Result<()> {
self.inner.write_file(path, contents)
}
fn is_file(&self, path: &Path) -> bool {
self.inner.is_file(path)
}
fn is_dir(&self, path: &Path) -> bool {
self.inner.is_dir(path)
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
self.inner.read_dir(path)
}
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
self.inner.create_dir_all(path)
}
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
self.inner.remove_dir_all(path)
}
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
self.inner.remove_file(path)
}
fn rename(&mut self, from: &Path, to: &Path) -> io::Result<()> {
self.inner.rename(from, to)
}
fn now(&self) -> f64 {
self.inner.now()
}
fn max_memory_gb(&self) -> f64 {
self.inner.max_memory_gb()
}
fn random_seed(&mut self) -> u32 {
self.inner.random_seed()
}
fn unix_time(&self) -> f64 {
self.inner.unix_time()
}
fn threads(&self) -> Option<Threads> {
self.inner.threads()
}
fn open_live(&self, inputs: &[LiveInput]) -> Box<dyn LiveSource + '_> {
self.inner.open_live(inputs)
}
}
#[cfg(feature = "cli")]
pub(crate) struct HostFiles<'a>(pub &'a dyn Host);
#[cfg(feature = "cli")]
impl crate::checker::observe_inputs::LogFiles for HostFiles<'_> {
fn is_file(&self, path: &Path) -> bool {
self.0.is_file(path)
}
fn is_dir(&self, path: &Path) -> bool {
self.0.is_dir(path)
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
self.0.read_dir(path)
}
fn read_to_string(&self, path: &Path) -> io::Result<String> {
self.0.read_to_string(path)
}
}
pub fn full_path(cwd: &Path, p: &Path) -> PathBuf {
let joined = if p.is_absolute() { p.to_path_buf() } else { cwd.join(p) };
normalize(&joined)
}
pub fn normalize(p: &Path) -> PathBuf {
let mut out = PathBuf::new();
for c in p.components() {
match c {
Component::CurDir => {}
Component::ParentDir => {
out.pop();
}
other => out.push(other.as_os_str()),
}
}
out
}
pub fn relative_path(from: &Path, to: &Path) -> String {
let f: Vec<_> = from.components().collect();
let t: Vec<_> = to.components().collect();
let common = f.iter().zip(t.iter()).take_while(|(a, b)| a == b).count();
if common == 0 {
return to.to_string_lossy().to_string();
}
let mut parts: Vec<String> = Vec::new();
for _ in common..f.len() {
parts.push("..".into());
}
for c in &t[common..] {
parts.push(c.as_os_str().to_string_lossy().to_string());
}
if parts.is_empty() {
".".into()
} else {
parts.join("/")
}
}
pub fn is_hidden(p: &Path) -> bool {
p.file_name().and_then(|n| n.to_str()).map(|n| n.starts_with('.')).unwrap_or(false)
}
#[derive(Debug, Clone)]
enum Node {
Dir,
File(String),
}
#[derive(Debug, Clone)]
pub struct MemHost {
nodes: BTreeMap<PathBuf, Node>,
pub cwd: PathBuf,
pub stdout: String,
pub stderr: String,
pub clock: fn() -> f64,
pub seed: u32,
pub threads: Option<Threads>,
}
impl Default for MemHost {
fn default() -> Self {
MemHost::new("/work")
}
}
impl MemHost {
pub fn new(cwd: &str) -> MemHost {
let mut h = MemHost {
nodes: BTreeMap::new(),
cwd: PathBuf::from(cwd),
stdout: String::new(),
stderr: String::new(),
clock: || 0.0,
seed: 0,
threads: None,
};
h.create_dir_all(Path::new(cwd)).expect("an empty file system has no files in the way");
h
}
fn abs(&self, p: &Path) -> PathBuf {
full_path(&self.cwd, p)
}
pub fn add_file(&mut self, path: &str, contents: &str) {
if let Err(e) = self.write_file(Path::new(path), contents) {
panic!("cannot add {}: {}", path, e);
}
}
pub fn files_under(&self, dir: &str) -> Vec<PathBuf> {
let d = self.abs(Path::new(dir));
self.nodes
.iter()
.filter(|(p, n)| matches!(n, Node::File(_)) && p.starts_with(&d))
.map(|(p, _)| p.clone())
.collect()
}
}
fn not_found(p: &Path) -> io::Error {
io::Error::new(io::ErrorKind::NotFound, format!("Could not find a part of the path '{}'.", p.display()))
}
fn is_a_directory(p: &Path) -> io::Error {
io::Error::other(format!("Is a directory: '{}'", p.display()))
}
fn not_a_directory(p: &Path) -> io::Error {
io::Error::other(format!("Not a directory: '{}'", p.display()))
}
impl Host for MemHost {
fn write(&mut self, stream: Stream, text: &str, _color: Option<Color>) {
match stream {
Stream::Out => self.stdout.push_str(text),
Stream::Err => self.stderr.push_str(text),
}
}
fn cwd(&self) -> PathBuf {
self.cwd.clone()
}
fn read_to_string(&self, path: &Path) -> io::Result<String> {
match self.nodes.get(&self.abs(path)) {
Some(Node::File(s)) => Ok(s.clone()),
Some(Node::Dir) => Err(is_a_directory(path)),
None => Err(not_found(path)),
}
}
fn write_file(&mut self, path: &Path, contents: &str) -> io::Result<()> {
let p = self.abs(path);
if let Some(Node::Dir) = self.nodes.get(&p) {
return Err(is_a_directory(path));
}
if let Some(parent) = p.parent() {
self.create_dir_all(parent)?;
}
self.nodes.insert(p, Node::File(contents.to_string()));
Ok(())
}
fn is_file(&self, path: &Path) -> bool {
matches!(self.nodes.get(&self.abs(path)), Some(Node::File(_)))
}
fn is_dir(&self, path: &Path) -> bool {
matches!(self.nodes.get(&self.abs(path)), Some(Node::Dir))
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<PathBuf>> {
let d = self.abs(path);
if !self.is_dir(&d) {
return Err(not_found(path));
}
Ok(self.nodes.keys().filter(|p| p.parent() == Some(d.as_path())).cloned().collect())
}
fn create_dir_all(&mut self, path: &Path) -> io::Result<()> {
let p = self.abs(path);
let mut cur = PathBuf::new();
for c in p.components() {
cur.push(c.as_os_str());
if let Some(Node::File(_)) = self.nodes.get(&cur) {
return Err(io::Error::new(io::ErrorKind::AlreadyExists, format!("File exists: '{}'", cur.display())));
}
self.nodes.entry(cur.clone()).or_insert(Node::Dir);
}
Ok(())
}
fn remove_dir_all(&mut self, path: &Path) -> io::Result<()> {
let p = self.abs(path);
match self.nodes.get(&p) {
Some(Node::Dir) => {
self.nodes.retain(|k, _| !k.starts_with(&p));
Ok(())
}
Some(Node::File(_)) => Err(not_a_directory(path)),
None => Err(not_found(path)),
}
}
fn remove_file(&mut self, path: &Path) -> io::Result<()> {
let p = self.abs(path);
match self.nodes.get(&p) {
Some(Node::File(_)) => {
self.nodes.remove(&p);
Ok(())
}
Some(Node::Dir) => Err(is_a_directory(path)),
None => Err(not_found(path)),
}
}
fn rename(&mut self, from: &Path, to: &Path) -> io::Result<()> {
let f = self.abs(from);
let t = self.abs(to);
if !self.nodes.contains_key(&f) {
return Err(not_found(from));
}
let moved: Vec<(PathBuf, Node)> =
self.nodes.iter().filter(|(k, _)| k.starts_with(&f)).map(|(k, v)| (k.clone(), v.clone())).collect();
self.nodes.retain(|k, _| !k.starts_with(&f));
for (k, v) in moved {
let rel = k.strip_prefix(&f).unwrap().to_path_buf();
let dest = if rel.as_os_str().is_empty() { t.clone() } else { t.join(rel) };
self.nodes.insert(dest, v);
}
Ok(())
}
fn now(&self) -> f64 {
(self.clock)()
}
fn max_memory_gb(&self) -> f64 {
0.0
}
fn random_seed(&mut self) -> u32 {
self.seed
}
fn unix_time(&self) -> f64 {
0.0
}
fn threads(&self) -> Option<Threads> {
self.threads
}
}