mdplayscript/
interface.rs

1use std::collections::VecDeque;
2use pulldown_cmark::{Event, Tag};
3use crate::parser::{FuseOnParagraphEnd, Speeches};
4use crate::speech::{parse_speech, parse_body};
5use crate::renderer::HtmlRenderer;
6
7#[derive(Debug)]
8enum Mode {
9    Nop,
10    PlayScript,
11    Monologue,
12}
13
14impl Mode {
15    fn is_off(&self) -> bool {
16        match self {
17            Mode::Nop => true,
18            _ => false,
19        }
20    }
21
22    fn is_monologue(&self) -> bool {
23        match self {
24            Mode::Monologue => true,
25            _ => false,
26        }
27    }
28}
29
30#[derive(Debug,Clone)]
31pub struct Options {
32    replace_softbreaks_with: Option<String>,
33    disabled_in_default: bool,
34}
35
36impl Default for Options {
37    fn default() -> Self {
38        Self {
39            replace_softbreaks_with: Some(" ".to_owned()),
40            disabled_in_default: false,
41        }
42    }
43}
44
45impl Options {
46    pub fn default_ja() -> Self {
47        Self {
48            replace_softbreaks_with: Some("".to_owned()),
49            disabled_in_default: false,
50        }
51    }
52}
53
54#[derive(Debug,Default,Clone)]
55pub struct Params {
56    pub title: Option<String>,
57    pub subtitle: Option<String>,
58    pub authors: Vec<String>,
59}
60
61pub struct MdPlayScriptBuilder {
62    options: Option<Options>,
63    params: Option<Params>,
64    make_title: Option<Box<dyn FnMut(&Params) -> String>>,
65}
66
67impl MdPlayScriptBuilder {
68    pub fn new() -> Self {
69        Self {
70            options: None,
71            params: None,
72            make_title: None,
73        }
74    }
75
76    pub fn options(self, opt: Options) -> Self {
77        Self {
78            options: Some(opt),
79            ..self
80        }
81    }
82
83    pub fn params(self, p: Params) -> Self {
84        Self {
85            params: Some(p),
86            ..self
87        }
88    }
89
90    pub fn make_title(self, val: Box<dyn FnMut(&Params) -> String>) -> Self {
91        Self {
92            make_title: Some(val),
93            ..self
94        }
95    }
96
97    pub fn build<'a, I>(self, iter: I) -> MdPlayScript<'a, I>
98        where
99            I: Iterator<Item=Event<'a>>,
100    {
101        let options = self.options.unwrap();
102        let renderer = HtmlRenderer {
103            replace_softbreak: options.replace_softbreaks_with,
104            ..Default::default()
105        };
106        let mode = if options.disabled_in_default {
107            Mode::Nop
108        } else {
109            Mode::PlayScript
110        };
111
112        MdPlayScript {
113            iter: Some(iter),
114            queue: VecDeque::new(),
115            mode: mode,
116            params: self.params.unwrap_or(Params::default()),
117            renderer: renderer,
118            make_title: self.make_title,
119        }
120    }
121}
122
123pub struct MdPlayScript<'a, I> {
124    iter: Option<I>,
125    queue: VecDeque<Event<'a>>,
126    mode: Mode,
127    params: Params,
128    renderer: HtmlRenderer,
129    make_title: Option<Box<dyn FnMut(&Params) -> String>>,
130}
131
132impl<'a, I> MdPlayScript<'a, I>
133where
134    I: Iterator<Item=Event<'a>>,
135{
136    pub fn new(iter: I) -> Self {
137        Self {
138            iter: Some(iter),
139            queue: VecDeque::new(),
140            mode: Mode::PlayScript,
141            params: Default::default(),
142            renderer: Default::default(),
143            make_title: None,
144        }
145    }
146
147    pub fn renderer_mut(&mut self) -> &mut HtmlRenderer {
148        &mut self.renderer
149    }
150
151    pub fn into_inner(self) -> I {
152        self.iter.unwrap()
153    }
154
155    fn dispatch_directive(&mut self, s: &str) {
156        match parse_directive(&s) {
157            Some(Directive::MonologueBegin) => {
158                self.mode = Mode::Monologue;
159            },
160            Some(Directive::MonologueEnd) => {
161                self.mode = Mode::PlayScript;
162            },
163            Some(Directive::PlayScriptOn) => {
164                self.mode = Mode::PlayScript;
165            },
166            Some(Directive::PlayScriptOff) => {
167                self.mode = Mode::Nop;
168            },
169            Some(Directive::Title) => {
170                emit_title(&self.params, &mut self.queue);
171            },
172            Some(Directive::SubTitle) => {
173                emit_subtitle(&self.params, &mut self.queue);
174            },
175            Some(Directive::Authors) => {
176                emit_authors(&self.params, &mut self.queue);
177            },
178            Some(Directive::MakeTitle) => {
179                if let Some(make_title) = self.make_title.as_mut() {
180                    let cover = (make_title)(&self.params);
181                    self.queue.push_back(Event::Html(cover.into()));
182                }
183            },
184            None => {},
185        }
186    }
187
188    fn append_events(&mut self, events: Vec<Event<'a>>) {
189        self.queue.extend(events);
190    }
191
192    fn dispatch_speech(&mut self, speech: Vec<Event<'a>>) {
193        match parse_speech(speech) {
194            Ok(speech) => {
195                let mut html = Vec::new();
196                self.renderer.render_speech(speech, &mut html);
197                html.push(Event::SoftBreak);
198                self.append_events(html);
199            },
200            Err(para) if self.mode.is_monologue() => {
201                let monologue = parse_body(para);
202                let mut html = Vec::new();
203                self.renderer.render_monologue(monologue, &mut html);
204                self.append_events(html);
205            },
206            Err(para) => {
207                let mut output = Vec::new();
208                self.renderer.render_events(para, &mut output);
209                self.append_events(wrap_by_paragraph_tag(output));
210            },
211        };
212    }
213}
214
215impl<'a, I: 'a> Iterator for MdPlayScript<'a, I>
216where
217    I: Iterator<Item=Event<'a>>,
218{
219    type Item = Event<'a>;
220
221    fn next(&mut self) -> Option<Self::Item> {
222        if let Some(event) = self.queue.pop_front() {
223            return Some(event);
224        }
225
226        let mut iter = self.iter.take().unwrap();
227
228        match iter.next() {
229            Some(Event::Html(s)) => {
230                self.dispatch_directive(&s);
231                self.queue.push_back(Event::Html(s));
232            },
233            Some(Event::Start(Tag::Paragraph)) if !self.mode.is_off() => {
234                let mut speeches = Speeches::new(FuseOnParagraphEnd::new(iter));
235
236                while let Some(speech) = speeches.next() {
237                    self.dispatch_speech(speech);
238                }
239
240                iter = speeches.into_inner().into_inner();
241            },
242            Some(event) => {
243                self.queue.push_back(event);
244            },
245            None => {},
246        }
247
248        self.iter.replace(iter);
249
250        self.queue.pop_front()
251    }
252}
253
254fn wrap_events_by<'a, 'b: 'a>(mut events: Vec<Event<'a>>, start: Event<'b>, end: Event<'b>) -> Vec<Event<'a>> {
255    let mut output = Vec::with_capacity(events.len() + 2);
256
257    output.push(start);
258    output.append(&mut events);
259    output.push(end);
260
261    output
262}
263
264fn wrap_by_paragraph_tag<'a>(events: Vec<Event<'a>>) -> Vec<Event<'a>> {
265    wrap_events_by(
266        events,
267        Event::Start(Tag::Paragraph),
268        Event::End(Tag::Paragraph),
269    )
270}
271
272#[derive(Debug,Clone,PartialEq)]
273enum Directive {
274    MonologueBegin,
275    MonologueEnd,
276    PlayScriptOn,
277    PlayScriptOff,
278    Title,
279    SubTitle,
280    Authors,
281    MakeTitle,
282}
283
284fn parse_directive(s: &str) -> Option<Directive> {
285    let s = s.trim()
286        .strip_prefix("<!--")?
287        .strip_suffix("-->")?
288        .trim();
289
290    match s {
291        "playscript-monologue-begin" => Some(Directive::MonologueBegin),
292        "playscript-monologue-end" => Some(Directive::MonologueEnd),
293        "playscript-on" => Some(Directive::PlayScriptOn),
294        "playscript-off" => Some(Directive::PlayScriptOff),
295        "playscript-title" => Some(Directive::Title),
296        "playscript-subtitle" => Some(Directive::SubTitle),
297        "playscript-authors" => Some(Directive::Authors),
298        "playscript-make-title" => Some(Directive::MakeTitle),
299        _ => None,
300    }
301}
302
303fn emit_title<'a>(params: &Params, queue: &mut VecDeque<Event<'a>>) {
304    let p_start = "<h1 class=\"cover-title\">";
305    let p_end = "</h1>";
306
307    if let Some(content) = params.title.as_ref().cloned() {
308        queue.push_back(Event::Html(p_start.into()));
309        queue.push_back(Event::Text(content.into()));
310        queue.push_back(Event::Html(p_end.into()));
311    }
312}
313
314fn emit_subtitle<'a>(params: &Params, queue: &mut VecDeque<Event<'a>>) {
315    let p_start = "<h2 class=\"cover-title\">";
316    let p_end = "</h2>";
317
318    if let Some(content) = params.subtitle.as_ref().cloned() {
319        queue.push_back(Event::Html(p_start.into()));
320        queue.push_back(Event::Text(content.into()));
321        queue.push_back(Event::Html(p_end.into()));
322    }
323}
324
325fn emit_authors<'a>(params: &Params, queue: &mut VecDeque<Event<'a>>) {
326    let div_start = "<div class=\"authors\">";
327    let div_end = "</div>";
328    let p_start = "<p class=\"cover-author\">";
329    let p_end = "</p>";
330
331    if params.authors.is_empty() {
332        return;
333    }
334
335    queue.push_back(Event::Html(div_start.into()));
336
337    for author in params.authors.iter().cloned() {
338        queue.push_back(Event::Html(p_start.into()));
339        queue.push_back(Event::Text(author.into()));
340        queue.push_back(Event::Html(p_end.into()));
341    }
342
343    queue.push_back(Event::Html(div_end.into()));
344}
345
346#[cfg(test)]
347mod test {
348    use super::*;
349    use pulldown_cmark::Parser;
350    use pulldown_cmark::html::push_html;
351
352    #[test]
353    fn consume() {
354        let s = r#"A> xxx
355\ruby
356B> Hello
357
358This is a normal line.
359
360<!-- playscript-monologue-begin -->
361Monologue
362<!-- playscript-monologue-end -->
363
364A> What?
365????
366B> !!!!
367A> ...."#;
368        let parser = MdPlayScript::new(Parser::new(s));
369
370        for e in parser {
371            eprintln!("{:?}", e);
372        }
373
374        let mut buf = String::new();
375        let parser = MdPlayScript::new(Parser::new(s));
376
377        push_html(&mut buf, parser);
378
379        eprintln!("{}", buf);
380    }
381
382    #[test]
383    fn parse_correct_directives() {
384        assert_eq!(
385            parse_directive("<!-- playscript-monologue-begin -->"),
386            Some(Directive::MonologueBegin));
387        assert_eq!(
388            parse_directive("<!-- playscript-monologue-end -->"),
389            Some(Directive::MonologueEnd));
390        assert_eq!(
391            parse_directive("<!-- playscript-on -->"),
392            Some(Directive::PlayScriptOn));
393        assert_eq!(
394            parse_directive("<!-- playscript-off -->"),
395            Some(Directive::PlayScriptOff));
396        assert_eq!(
397            parse_directive("<!-- playscript-title -->"),
398            Some(Directive::Title));
399        assert_eq!(
400            parse_directive("<!-- playscript-subtitle -->"),
401            Some(Directive::SubTitle));
402        assert_eq!(
403            parse_directive("<!-- playscript-authors -->"),
404            Some(Directive::Authors));
405    }
406}