lear/
lib.rs

1use std::{
2    cmp::{max, min},
3    fmt::Display,
4    ops::RangeInclusive,
5};
6
7use rand::{prelude::ThreadRng, Rng};
8use serde::Deserialize;
9use termion::style;
10
11const SCENES: [&str; 26] = [
12    include_str!("res/01.json"),
13    include_str!("res/02.json"),
14    include_str!("res/03.json"),
15    include_str!("res/04.json"),
16    include_str!("res/05.json"),
17    include_str!("res/06.json"),
18    include_str!("res/07.json"),
19    include_str!("res/08.json"),
20    include_str!("res/09.json"),
21    include_str!("res/10.json"),
22    include_str!("res/11.json"),
23    include_str!("res/12.json"),
24    include_str!("res/13.json"),
25    include_str!("res/14.json"),
26    include_str!("res/15.json"),
27    include_str!("res/16.json"),
28    include_str!("res/17.json"),
29    include_str!("res/18.json"),
30    include_str!("res/19.json"),
31    include_str!("res/20.json"),
32    include_str!("res/21.json"),
33    include_str!("res/22.json"),
34    include_str!("res/23.json"),
35    include_str!("res/24.json"),
36    include_str!("res/25.json"),
37    include_str!("res/26.json"),
38];
39
40#[derive(Debug)]
41struct Metadata {
42    act: &'static str,
43    scene: usize,
44    lines: usize,
45}
46
47const METADATA: [Metadata; 26] = [
48    Metadata {
49        act: "1",
50        scene: 1,
51        lines: 332,
52    },
53    Metadata {
54        act: "",
55        scene: 2,
56        lines: 191,
57    },
58    Metadata {
59        act: "",
60        scene: 3,
61        lines: 27,
62    },
63    Metadata {
64        act: "",
65        scene: 4,
66        lines: 352,
67    },
68    Metadata {
69        act: "",
70        scene: 5,
71        lines: 48,
72    },
73    Metadata {
74        act: "2",
75        scene: 1,
76        lines: 141,
77    },
78    Metadata {
79        act: "",
80        scene: 2,
81        lines: 177,
82    },
83    Metadata {
84        act: "",
85        scene: 3,
86        lines: 21,
87    },
88    Metadata {
89        act: "",
90        scene: 4,
91        lines: 339,
92    },
93    Metadata {
94        act: "3",
95        scene: 1,
96        lines: 58,
97    },
98    Metadata {
99        act: "",
100        scene: 2,
101        lines: 100,
102    },
103    Metadata {
104        act: "",
105        scene: 3,
106        lines: 25,
107    },
108    Metadata {
109        act: "",
110        scene: 4,
111        lines: 187,
112    },
113    Metadata {
114        act: "",
115        scene: 5,
116        lines: 25,
117    },
118    Metadata {
119        act: "",
120        scene: 6,
121        lines: 117,
122    },
123    Metadata {
124        act: "",
125        scene: 7,
126        lines: 118,
127    },
128    Metadata {
129        act: "4",
130        scene: 1,
131        lines: 90,
132    },
133    Metadata {
134        act: "",
135        scene: 2,
136        lines: 111,
137    },
138    Metadata {
139        act: "",
140        scene: 3,
141        lines: 62,
142    },
143    Metadata {
144        act: "",
145        scene: 4,
146        lines: 32,
147    },
148    Metadata {
149        act: "",
150        scene: 5,
151        lines: 45,
152    },
153    Metadata {
154        act: "",
155        scene: 6,
156        lines: 314,
157    },
158    Metadata {
159        act: "",
160        scene: 7,
161        lines: 110,
162    },
163    Metadata {
164        act: "5",
165        scene: 1,
166        lines: 78,
167    },
168    Metadata {
169        act: "",
170        scene: 2,
171        lines: 13,
172    },
173    Metadata {
174        act: "",
175        scene: 3,
176        lines: 386,
177    },
178];
179
180pub fn display_contents() {
181    println!(
182        "{}{: ^19}{}",
183        style::Italic,
184        "The Tragedie of",
185        style::Reset
186    );
187    println!("{: ^19}\n", "KING LEAR");
188    println!("{: ^19}\n", "by");
189    println!("{}{: ^19}{}", style::Italic, "William", style::Reset);
190    println!("{}{: ^19}{}\n", style::Italic, "Shakespeare", style::Reset);
191    println!(
192        "{}{: ^5}{: ^7}{: ^7}{}",
193        style::Bold,
194        "Act",
195        "Scene",
196        "Lines",
197        style::Reset
198    );
199    for line in &METADATA {
200        if !line.act.is_empty() {
201            println!("{:-^19}", "");
202        }
203        println!("{: ^5}{: ^7}{: ^7}", line.act, line.scene, line.lines,);
204    }
205}
206
207struct TableEntry {
208    start: usize,
209    scenes: RangeInclusive<usize>,
210}
211
212impl TableEntry {
213    fn index(&self, scene: usize) -> Option<usize> {
214        if self.scenes.contains(&scene) {
215            Some(self.start + scene - 1)
216        } else {
217            None
218        }
219    }
220}
221
222const TABLE_OF_CONTENTS: [TableEntry; 5] = [
223    TableEntry {
224        start: 0,
225        scenes: (1..=5),
226    },
227    TableEntry {
228        start: 5,
229        scenes: (1..=4),
230    },
231    TableEntry {
232        start: 9,
233        scenes: (1..=7),
234    },
235    TableEntry {
236        start: 16,
237        scenes: (1..=7),
238    },
239    TableEntry {
240        start: 23,
241        scenes: (1..=3),
242    },
243];
244
245#[derive(Debug, Deserialize, Clone)]
246pub struct Heading {
247    act: String,
248    scene: String,
249    setting: String,
250    staging: String,
251}
252
253#[derive(Debug)]
254pub enum LearError {
255    IoError(std::io::Error),
256    InvalidAct(usize),
257    InvalidScene {
258        act: usize,
259        scene: usize,
260    },
261    InvalidLines {
262        act: usize,
263        scene: usize,
264        lines: RangeInclusive<usize>,
265    },
266}
267
268impl From<serde_json::Error> for LearError {
269    fn from(e: serde_json::Error) -> Self {
270        LearError::IoError(e.into())
271    }
272}
273
274impl Display for LearError {
275    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276        write!(f, "{:?}", self)
277    }
278}
279
280impl std::error::Error for LearError {}
281
282impl Display for &Heading {
283    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284        write!(
285            f,
286            "{}{}, {}{}\n{}\n\n\t{}{}{}\n\n",
287            style::Bold,
288            self.act,
289            self.scene,
290            style::Reset,
291            self.setting,
292            style::Italic,
293            self.staging,
294            style::Reset
295        )
296    }
297}
298
299#[derive(Debug, Deserialize, Clone)]
300pub enum Line {
301    #[serde(rename(deserialize = "text"))]
302    Text(String),
303    #[serde(rename(deserialize = "direction"))]
304    Direction(String),
305}
306
307#[derive(Debug, Deserialize, Clone)]
308pub struct Dialogue {
309    character: String,
310    lines: Vec<Line>,
311    act: usize,
312    scene: usize,
313    start: usize,
314    end: usize,
315}
316
317impl Dialogue {
318    fn selection(&self, range: &RangeInclusive<usize>) -> Option<Dialogue> {
319        // no overlap
320        if self.start > *range.end() || self.end < *range.start() {
321            return None;
322        }
323        // the line numbers from the selection
324        let start_line = max(range.start(), &self.start);
325        let end_line = min(range.end(), &self.end);
326        // the start and end indexes in self.lines
327        let (start, _) = self
328            .lines
329            .iter()
330            .enumerate()
331            .filter(|(_, line)| matches!(line, Line::Text(_)))
332            .find(|(i, _)| i + self.start >= *start_line)?;
333        // there's some weirdness when stage directions are in the last block of
334        // text, so we count them separately and add them together. There's
335        // probably a better way to handle this...
336        let (lines_of_text, _) = self
337            .lines
338            .iter()
339            .filter(|line| matches!(line, Line::Text(_)))
340            .enumerate()
341            .find(|(i, _)| i + self.start >= *end_line)?;
342        let skipped_directions = self
343            .lines
344            .iter()
345            .take(lines_of_text)
346            .filter(|line| matches!(line, Line::Direction(_)))
347            .count();
348        let end = lines_of_text + skipped_directions;
349        // grab only the lines we want to display
350        let mut lines: Vec<Line> = self.lines[start..=end].to_vec();
351        // omit empty dialogue blocks
352        if lines.is_empty() {
353            return None;
354        }
355        // if we truncated a dialogue block, add leading and/or trailing ellipses
356        if range.start() > &self.start {
357            lines.insert(0, Line::Text("...".into()));
358        }
359        if range.end() < &self.end {
360            lines.push(Line::Text("...".into()));
361        }
362        Some(Dialogue {
363            lines,
364            start: *start_line,
365            end: *end_line,
366            character: self.character.clone(),
367            ..*self
368        })
369    }
370}
371
372impl Display for &Dialogue {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374        f.write_fmt(format_args!(
375            "{}{}{}\n",
376            style::Bold,
377            self.character,
378            style::Reset
379        ))?;
380        let iter = self.lines.iter().peekable();
381        let mut prev = None;
382        // while let Some(line) = iter.next() {
383        for line in iter {
384            if let Some(&Line::Direction(_)) = prev {
385                match line {
386                    Line::Text(text) => f.write_fmt(format_args!("\n\t{}\n", text))?,
387                    Line::Direction(direction) => f.write_fmt(format_args!(
388                        "\t{}{}{}\n",
389                        style::Italic,
390                        direction,
391                        style::Reset
392                    ))?,
393                };
394            } else {
395                match line {
396                    Line::Text(text) => f.write_fmt(format_args!("\t{}\n", text))?,
397                    Line::Direction(direction) => f.write_fmt(format_args!(
398                        "\n\t{}{}{}\n",
399                        style::Italic,
400                        direction,
401                        style::Reset
402                    ))?,
403                };
404            }
405            prev = Some(line);
406        }
407        f.write_fmt(format_args!("\n"))?;
408        Ok(())
409    }
410}
411
412#[derive(Debug, Deserialize, Clone)]
413pub enum Block {
414    Heading(Heading),
415    Dialogue(Dialogue),
416}
417
418impl Block {
419    fn selection(&self, range: &RangeInclusive<usize>) -> Option<Block> {
420        match self {
421            Block::Heading(_) => None,
422            Block::Dialogue(d) => Some(Block::Dialogue(d.selection(range)?)),
423        }
424    }
425}
426
427impl Display for &Block {
428    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
429        match self {
430            Block::Heading(heading) => write!(f, "{}", heading),
431            Block::Dialogue(dialogue) => write!(f, "{}", dialogue),
432        }
433    }
434}
435
436pub fn blocks_to_show(rng: &mut ThreadRng) -> Result<Vec<Block>, std::io::Error> {
437    let scene = rng.gen_range(0..25) as usize;
438    let blocks: Vec<Block> = serde_json::from_str(SCENES[scene])?;
439    let blocks_to_show = rng.gen_range(2..=min(5, blocks.len())) as usize;
440    // the fact that scene 8 is so short complicates things...
441    let range = 0..(blocks.len() - blocks_to_show);
442    let start = if range.is_empty() {
443        0
444    } else {
445        rng.gen_range(range)
446    };
447    let blocks = blocks
448        .into_iter()
449        .skip(start)
450        .take(blocks_to_show)
451        .collect();
452    Ok(blocks)
453}
454
455pub fn text(
456    act: usize,
457    scene: usize,
458    lines: RangeInclusive<usize>,
459) -> Result<Vec<Block>, LearError> {
460    let scene_index = TABLE_OF_CONTENTS
461        .get(act - 1)
462        .ok_or(LearError::InvalidAct(act))?
463        .index(scene)
464        .ok_or(LearError::InvalidScene { act, scene })?;
465    let blocks: Vec<Block> = serde_json::from_str(SCENES[scene_index])?;
466    let blocks: Vec<Block> = blocks
467        // .get(lines.clone())
468        .iter()
469        .filter_map(|b| b.selection(&lines))
470        .collect();
471    if blocks.is_empty() {
472        return Err(LearError::InvalidLines { act, scene, lines });
473    }
474    Ok(blocks)
475}
476
477fn attribution(blocks: &[Block]) -> Option<String> {
478    let dialogue: Vec<_> = blocks
479        .iter()
480        .filter_map(|b| match b {
481            Block::Heading(_) => None,
482            Block::Dialogue(dialogue) => Some(dialogue),
483        })
484        .collect();
485    let first = dialogue.first()?;
486    let act = first.act;
487    let scene = first.scene;
488    let start = first.start;
489    let end = dialogue.last()?.end;
490    Some(format!(
491        "({}Lr.{} {}.{}.{}-{})",
492        style::Italic,
493        style::Reset,
494        act,
495        scene,
496        start,
497        end
498    ))
499}
500
501pub fn display(blocks: &[Block]) {
502    let attribution = attribution(blocks);
503    for block in blocks {
504        print!("{}", block);
505    }
506    if let Some(attr) = attribution {
507        println!("{: >80}", attr);
508    }
509}