the core of loading slides from lisp is done

Slides themselves are capable of loading from lisp. The goal next will
be to create a way to load songs from lisp.
This commit is contained in:
Chris Cochrun 2024-11-20 12:13:50 -06:00
parent b49aa55ec3
commit 356384821b
4 changed files with 191 additions and 239 deletions

View file

@ -140,24 +140,24 @@ mod test {
use super::*; use super::*;
#[test] // #[test]
fn test_list() { // fn test_list() {
let lisp = // let lisp =
read_to_string("./test_presentation.lisp").expect("oops"); // read_to_string("./test_presentation.lisp").expect("oops");
println!("{lisp}"); // // println!("{lisp}");
let mut parser = // let mut parser =
Parser::from_str_custom(&lisp, Options::elisp()); // Parser::from_str_custom(&lisp, Options::elisp());
for atom in parser.value_iter() { // for atom in parser.value_iter() {
match atom { // match atom {
Ok(atom) => { // Ok(atom) => {
println!("{atom}"); // // println!("{atom}");
let lists = get_lists(&atom); // let lists = get_lists(&atom);
assert_eq!(lists, vec![Value::Null]) // assert_eq!(lists, vec![Value::Null])
} // }
Err(e) => { // Err(e) => {
panic!("{e}"); // panic!("{e}");
} // }
} // }
} // }
} // }
} }

View file

@ -1,4 +1,4 @@
use crisp::types::{Keyword, Value}; use crisp::types::{Keyword, Symbol, Value};
use miette::{miette, IntoDiagnostic, Result}; use miette::{miette, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
@ -7,8 +7,7 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
str::FromStr, str::FromStr,
}; };
use tracing::error;
use crate::core::lisp::Symbol;
#[derive( #[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize,
@ -49,42 +48,32 @@ impl TryFrom<String> for Background {
fn try_from(value: String) -> Result<Self, Self::Error> { fn try_from(value: String) -> Result<Self, Self::Error> {
let value = value.trim_start_matches("file://"); let value = value.trim_start_matches("file://");
let path = PathBuf::from(value); let path = PathBuf::from(value);
if !path.exists() { Background::try_from(path)
return Err(ParseError::DoesNotExist);
}
let extension = value.rsplit_once('.').unwrap_or_default();
match extension.1 {
"jpg" | "png" | "webp" | "html" => Ok(Self {
path,
kind: BackgroundKind::Image,
}),
"mp4" | "mkv" | "webm" => Ok(Self {
path,
kind: BackgroundKind::Video,
}),
_ => Err(ParseError::NonBackgroundFile),
}
} }
} }
impl TryFrom<PathBuf> for Background { impl TryFrom<PathBuf> for Background {
type Error = ParseError; type Error = ParseError;
fn try_from(value: PathBuf) -> Result<Self, Self::Error> { fn try_from(value: PathBuf) -> Result<Self, Self::Error> {
let extension = value if let Ok(value) = value.canonicalize() {
.extension() let extension = value
.unwrap_or_default() .extension()
.to_str() .unwrap_or_default()
.unwrap_or_default(); .to_str()
match extension { .unwrap_or_default();
"jpg" | "png" | "webp" | "html" => Ok(Self { match extension {
path: value, "jpg" | "png" | "webp" | "html" => Ok(Self {
kind: BackgroundKind::Image, path: value,
}), kind: BackgroundKind::Image,
"mp4" | "mkv" | "webm" => Ok(Self { }),
path: value, "mp4" | "mkv" | "webm" => Ok(Self {
kind: BackgroundKind::Video, path: value,
}), kind: BackgroundKind::Video,
_ => Err(ParseError::NonBackgroundFile), }),
_ => Err(ParseError::NonBackgroundFile),
}
} else {
Err(ParseError::CannotCanonicalize)
} }
} }
} }
@ -92,14 +81,27 @@ impl TryFrom<PathBuf> for Background {
impl TryFrom<&str> for Background { impl TryFrom<&str> for Background {
type Error = ParseError; type Error = ParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(Self::try_from(String::from(value))?) if value.starts_with("~") {
if let Some(home) = dirs::home_dir() {
if let Some(home) = home.to_str() {
let value = value.replace("~", home);
Self::try_from(PathBuf::from(value))
} else {
Self::try_from(PathBuf::from(value))
}
} else {
Self::try_from(PathBuf::from(value))
}
} else {
Self::try_from(PathBuf::from(value))
}
} }
} }
impl TryFrom<&Path> for Background { impl TryFrom<&Path> for Background {
type Error = ParseError; type Error = ParseError;
fn try_from(value: &Path) -> Result<Self, Self::Error> { fn try_from(value: &Path) -> Result<Self, Self::Error> {
Ok(Self::try_from(PathBuf::from(value))?) Self::try_from(PathBuf::from(value))
} }
} }
@ -107,6 +109,7 @@ impl TryFrom<&Path> for Background {
pub enum ParseError { pub enum ParseError {
NonBackgroundFile, NonBackgroundFile,
DoesNotExist, DoesNotExist,
CannotCanonicalize,
} }
impl std::error::Error for ParseError {} impl std::error::Error for ParseError {}
@ -121,6 +124,9 @@ impl Display for ParseError {
"The file is not a recognized image or video type" "The file is not a recognized image or video type"
} }
Self::DoesNotExist => "This file doesn't exist", Self::DoesNotExist => "This file doesn't exist",
Self::CannotCanonicalize => {
"Could not canonicalize this file"
}
}; };
write!(f, "Error: {message}") write!(f, "Error: {message}")
} }
@ -184,6 +190,7 @@ impl Slide {
impl From<Value> for Slide { impl From<Value> for Slide {
fn from(value: Value) -> Self { fn from(value: Value) -> Self {
dbg!(&value);
match value { match value {
Value::List(list) => lisp_to_slide(list), Value::List(list) => lisp_to_slide(list),
_ => Slide::default(), _ => Slide::default(),
@ -194,39 +201,120 @@ impl From<Value> for Slide {
fn lisp_to_slide(lisp: Vec<Value>) -> Slide { fn lisp_to_slide(lisp: Vec<Value>) -> Slide {
let mut slide = SlideBuilder::new(); let mut slide = SlideBuilder::new();
let background_position = if let Some(background) = let background_position = if let Some(background) =
lisp.position(|v| Value::Keyword(Keyword::from("background"))) lisp.iter().position(|v| {
{ v == &Value::Keyword(Keyword::from("background"))
}) {
background + 1 background + 1
} else { } else {
0 1
}; };
dbg!(&background_position);
if let Some(background) = lisp.get(background_position) { if let Some(background) = lisp.get(background_position) {
slide.background(lisp_to_background(background)); dbg!(&background);
slide = slide.background(lisp_to_background(background));
} else { } else {
slide.background(Background::default()); slide = slide.background(Background::default());
}; };
let text_position = lisp.iter().position(|v| match v {
Value::List(vec) => {
vec[0] == Value::Symbol(Symbol::from("text"))
}
_ => false,
});
if let Some(text_position) = text_position {
if let Some(text) = lisp.get(text_position) {
slide = slide.text(lisp_to_text(text));
} else {
slide = slide.text("");
}
} else {
slide = slide.text("");
}
if let Some(text_position) = text_position {
if let Some(text) = lisp.get(text_position) {
slide = slide.font_size(lisp_to_font_size(text));
} else {
slide = slide.font_size(0);
}
} else {
slide = slide.font_size(0);
}
slide = slide
.font("Quicksand")
.text_alignment(TextAlignment::MiddleCenter)
.video_loop(false)
.video_start_time(0.0)
.video_end_time(0.0);
dbg!(&slide);
match slide.build() { match slide.build() {
Ok(slide) => slide, Ok(slide) => slide,
Err(e) => { Err(e) => {
miette!("Shoot! Slide didn't build: {e}"); dbg!(&e);
error!("Shoot! Slide didn't build: {e}");
Slide::default() Slide::default()
} }
} }
} }
fn lisp_to_font_size(lisp: &Value) -> i32 {
match lisp {
Value::List(list) => {
if let Some(font_size_position) =
list.iter().position(|v| {
v == &Value::Keyword(Keyword::from("font-size"))
})
{
if let Some(font_size_value) =
list.get(font_size_position + 1)
{
font_size_value.into()
} else {
50
}
} else {
50
}
}
_ => 50,
}
}
fn lisp_to_text(lisp: &Value) -> impl Into<String> {
match lisp {
Value::List(list) => list[1].clone(),
_ => "".into(),
}
}
fn lisp_to_background(lisp: &Value) -> Background { fn lisp_to_background(lisp: &Value) -> Background {
match lisp { match lisp {
Value::List(list) => { Value::List(list) => {
if let Some(source) = list.iter().position(|v| { if let Some(source) = list.iter().position(|v| {
v == &Value::Keyword(Keyword::from("source")) v == &Value::Keyword(Keyword::from("source"))
}) { }) {
let source = list[source + 1]; let source = &list[source + 1];
dbg!(&source);
match source { match source {
Value::String(s) => Value::String(s) => {
match Background::try_from(s.as_str()) {
Ok(background) => {
dbg!(&background);
background
}
Err(e) => {
dbg!("Couldn't load background: ", e);
Background::default()
}
}
}
_ => Background::default(),
} }
Background::try_from(&path)
} else { } else {
Background::default() Background::default()
} }
@ -267,7 +355,7 @@ impl SlideBuilder {
mut self, mut self,
background: Background, background: Background,
) -> Self { ) -> Self {
self.background.insert(background); let _ = self.background.insert(background);
self self
} }
@ -360,6 +448,7 @@ struct Image {
pub fit: String, pub fit: String,
pub children: Vec<String>, pub children: Vec<String>,
} }
impl Image { impl Image {
fn new() -> Self { fn new() -> Self {
Self { Self {
@ -368,167 +457,56 @@ impl Image {
} }
} }
// fn build_image_bg(
// atom: &Value,
// image_map: &mut HashMap<String, String>,
// map_index: usize,
// ) {
// // This needs to be the cons that contains (image . ...)
// // the image is a symbol and the rest are keywords and other maps
// if atom.is_symbol() {
// // We shouldn't get a symbol
// return;
// }
// for atom in
// atom.list_iter().unwrap().map(|a| a.as_cons().unwrap())
// {
// if atom.car() == &Value::Symbol("image".into()) {
// build_image_bg(atom.cdr(), image_map, map_index);
// } else {
// let atom = atom.car();
// match atom {
// Value::Keyword(keyword) => {
// image_map.insert(keyword.to_string(), "".into());
// build_image_bg(atom, image_map, map_index);
// }
// Value::Symbol(symbol) => {
// // let mut key;
// // let image_map = image_map
// // .iter_mut()
// // .enumerate()
// // .filter(|(i, e)| i == &map_index)
// // .map(|(i, (k, v))| v.push_str(symbol))
// // .collect();
// build_image_bg(atom, image_map, map_index);
// }
// Value::String(string) => {}
// _ => {}
// }
// }
// }
// }
// fn build_slide(exp: Value) -> Result<Slide> {
// let mut slide_builder = SlideBuilder::new();
// let mut keyword = "idk";
// for value in exp.as_cons().unwrap().to_vec().0 {
// let mut vecs = vec![vec![]];
// match value {
// Value::Symbol(symbol) => {}
// Value::Keyword(keyword) => {}
// Value::String(string) => {}
// Value::Number(num) => {}
// Value::Cons(cons) => {
// vecs.push(get_lists(&value));
// }
// _ => {}
// }
// }
// todo!()
// }
// fn build_slides(
// cons: &lexpr::Cons,
// mut current_symbol: Symbol,
// mut slide_builder: SlideBuilder,
// ) -> SlideBuilder {
// for value in cons.list_iter() {
// dbg!(&current_symbol);
// match value {
// Value::Cons(v) => {
// slide_builder = build_slides(
// &v,
// current_symbol.clone(),
// slide_builder,
// );
// }
// Value::Nil => {
// dbg!(Value::Nil);
// }
// Value::Bool(boolean) => {
// dbg!(boolean);
// }
// Value::Number(number) => {
// dbg!(number);
// }
// Value::String(string) => {
// dbg!(string);
// }
// Value::Symbol(symbol) => {
// dbg!(symbol);
// current_symbol =
// Symbol::from_str(symbol).unwrap_or_default();
// }
// Value::Keyword(keyword) => {
// dbg!(keyword);
// }
// Value::Null => {
// dbg!("null");
// }
// Value::Char(c) => {
// dbg!(c);
// }
// Value::Bytes(b) => {
// dbg!(b);
// }
// Value::Vector(v) => {
// dbg!(v);
// }
// }
// }
// slide_builder
// }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use lexpr::{parse::Options, Datum, Parser};
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use serde_lexpr::from_str;
use std::fs::read_to_string; use std::fs::read_to_string;
use tracing::debug; use tracing::debug;
use super::*; use super::*;
fn test_slide() -> Slide { fn test_slide() -> Slide {
Slide::default() Slide {
text: "This is frodo".to_string(),
background: Background::try_from("~/pics/frodo.jpg")
.unwrap(),
font: "Quicksand".to_string(),
font_size: 70,
..Default::default()
}
}
fn test_second_slide() -> Slide {
Slide {
text: "".to_string(),
background: Background::try_from(
"~/vids/test/camprules2024.mp4",
)
.unwrap(),
font: "Quicksand".to_string(),
..Default::default()
}
} }
#[test] #[test]
fn test_lexp_serialize() { fn test_lisp_serialize() {
let lisp = let lisp =
read_to_string("./test_presentation.lisp").expect("oops"); read_to_string("./test_presentation.lisp").expect("oops");
println!("{lisp}"); println!("{lisp}");
let mut parser = let lisp_value = crisp::reader::read(&lisp);
Parser::from_str_custom(&lisp, Options::elisp()); match lisp_value {
for atom in parser.value_iter() { Value::List(value) => {
match atom { let slide = Slide::from(value[0].clone());
Ok(atom) => { let test_slide = test_slide();
let symbol = Symbol::None; assert_eq!(slide, test_slide);
let slide_builder = SlideBuilder::new();
atom.as_cons().map(|c| { let second_slide = Slide::from(value[1].clone());
build_slides(c, symbol, slide_builder) dbg!(&second_slide);
}); let second_test_slide = test_second_slide();
} assert_eq!(second_slide, second_test_slide)
Err(e) => {
dbg!(e);
}
} }
_ => panic!("this should be a lisp"),
} }
// parser.map(|atom| match atom {
// Ok(atom) => dbg!(atom),
// Err(e) => dbg!(e),
// });
// let lispy = from_str_elisp(&lisp).expect("oops");
// if lispy.is_list() {
// for atom in lispy.list_iter().unwrap() {
// print_list(atom);
// }
// }
let slide: Slide = from_str(&lisp).expect("oops");
let test_slide = test_slide();
assert_eq!(slide, test_slide)
} }
#[test] #[test]

View file

@ -577,32 +577,6 @@ where
} }
} }
fn test_slide<'a>() -> Element<'a, Message> {
if let Ok(slide) = SlideBuilder::new()
.background(
Background::try_from("/home/chris/pics/frodo.jpg")
.unwrap(),
)
.text("This is a frodo")
.text_alignment(TextAlignment::TopCenter)
.font_size(50)
.font("Quicksand")
.build()
{
let font = Font::with_name("Noto Sans");
let stack = stack!(
image(slide.background().path.clone()),
text(slide.text())
.size(slide.font_size() as u16)
.font(font)
);
stack.into()
} else {
text("Slide is broken").into()
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View file

@ -1,6 +1,6 @@
(slide :background (image :source "~/pics/frodo.jpg" :fit crop) (slide :background (image :source "~/pics/frodo.jpg" :fit fill)
(text "This is frodo" :font-size 50)) (text "This is frodo" :font-size 70))
(slide (video :source "~/vids/test/chosensmol.mp4" :fit fill)) (slide (video :source "~/vids/test/camprules2024.mp4" :fit contain))
(song :author "Jordan Feliz" :ccli 97987 (song :author "Jordan Feliz" :ccli 97987
:font "Quicksand" :font-size 80 :font "Quicksand" :font-size 80
:title "The River" :title "The River"