ksl 0.1.30

KSL core library and interpreter
Documentation
//! # ksl::builtin::read
//!
//! Built-in function `Read`.

use crate::{Environment, eval::apply::eval_apply, expand_tilde, is_number_eq, value::Value};

pub(crate) fn builtin(args: &[Value], env: Environment) -> Result<Value, std::sync::Arc<str>> {
    if let [path] = args {
        match eval_apply(path, env)? {
            Value::String(file_path) => expand_tilde(file_path).and_then(|real_path| {
                match std::fs::File::options()
                    .read(true)
                    .open(&real_path)
                    .and_then(|f| read_file(f, 0))
                {
                    Ok(value) => Ok(value),
                    Err(e) => Err(std::sync::Arc::from(format!(
                        concat!(
                            "Error[ksl::builtin::Read]: ",
                            "Unable to read contents of file at `{}` with `{}`."
                        ),
                        real_path.display(),
                        e
                    ))),
                }
            }),
            e => Err(std::sync::Arc::from(format!(
                concat!("Error[ksl::builtin::Read]: ", "Unexpected value: `{}`."),
                e
            ))),
        }
    } else if let [path, bytes] = args {
        match (eval_apply(path, env.clone())?, eval_apply(bytes, env)?) {
            (Value::String(file_path), Value::Number(limit)) if is_number_eq(limit.trunc(), limit) => expand_tilde(file_path)
                .and_then(|real_path| {
                    match std::fs::File::options()
                        .read(true)
                        .open(&real_path)
                        .and_then(|f| read_file(f, limit as u64))
                    {
                        Ok(value) => Ok(value),
                        Err(e) => Err(std::sync::Arc::from(format!(
                            concat!(
                                "Error[ksl::builtin::Read]: ",
                                "Unable to read contents of file at `{}` with `{}`."
                            ),
                            real_path.display(),
                            e
                        ))),
                    }
                }),
            (Value::String(_), e) => Err(std::sync::Arc::from(format!(
                concat!("Error[ksl::builtin::Read]: ", "Unexpected value: `{}`."),
                e
            ))),
            (e, _) => Err(std::sync::Arc::from(format!(
                concat!("Error[ksl::builtin::Read]: ", "Unexpected value: `{}`."),
                e
            ))),
        }
    } else {
        Err(std::sync::Arc::from(format!(
            concat!(
                "Error[ksl::builtin::Read]: ",
                "Expected 1 or 2 parameters, but {} were passed."
            ),
            args.len()
        )))
    }
}

fn read_file(file: std::fs::File, limit: u64) -> std::io::Result<Value> {
    let is_regular = if let Ok(meta) = file.metadata() {
        meta.file_type().is_file()
    } else {
        false
    };
    if is_regular {
        let mut buffer = Vec::new();
        std::io::Read::read_to_end(&mut std::io::BufReader::new(file), &mut buffer).map(|_| {
            match String::from_utf8(buffer.clone()) {
                Ok(file_content) => Value::String(std::sync::Arc::from(if limit == 0 {
                    file_content
                } else {
                    file_content
                        .chars()
                        .take(limit as usize)
                        .collect::<String>()
                })),
                Err(_) => Value::List(buffer_to_u32_numbers(buffer, limit)),
            }
        })
    } else if limit != 0 {
        let mut buffer = Vec::new();
        std::io::Read::read_to_end(
            &mut std::io::Read::take(std::io::BufReader::new(file), limit * 4),
            &mut buffer,
        )
        .map(|_| Value::List(buffer_to_u32_numbers(buffer, limit)))
    } else {
        Err(std::io::Error::from(std::io::ErrorKind::Interrupted))
    }
}

fn buffer_to_u32_numbers(buffer: Vec<u8>, limit: u64) -> std::sync::Arc<[Value]> {
    let buffer = buffer.chunks_exact(4).map(|n| {
        Value::Number({
            let mut combined = [0u8; 4];
            combined[..n.len()].copy_from_slice(n);
            // by default, use little endian
            u32::from_le_bytes(combined) as f64
        })
    });
    if limit == 0 {
        buffer.collect::<std::sync::Arc<[Value]>>()
    } else {
        buffer
            .take(limit as usize)
            .collect::<std::sync::Arc<[Value]>>()
    }
}