1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
use std::collections::HashMap;
use std::error::Error;
use std::fs::File;
use std::path::Path;
use std::io::prelude::*;
use std::process;
use std::thread::sleep;
use std::time::Duration;

use combine::primitives::{ from_iter, Parser, ParseResult, State, Stream };
use combine::combinator::{ many, parser, satisfy, skip_many, skip_many1, token, ParserExt };
use combine::char::{ char, digit, space, spaces, newline, alpha_num };

#[derive(PartialEq, Debug)]
pub struct Lyric {
    pub duration: u64,
    pub text: String,
}

#[derive(PartialEq, Debug)]
pub struct Karaoke {
    pub header: HashMap<String, String>,
    pub lyrics: Vec<Lyric>
}

fn header<I>(input: State<I>) -> ParseResult<(String, String), I>
where I: Stream<Item=char> {
    let lex_char = |c| char(c).skip(spaces());
    (lex_char('#')
     , many::<String, _>(alpha_num())
     , lex_char(':')
     , many::<String, _>(satisfy(|c| c != '\n'))
     , newline())
        .map(|(_, key, _, value, _)| (key, value.trim_right().to_string()))
        .expected("header")
        .parse_state(input)
}

fn headers<I>(input: State<I>) -> ParseResult<HashMap<String, String>, I>
where I: Stream<Item=char> {
    many(parser(header))
        .parse_state(input)
}

fn lyric<I>(input: State<I>) -> ParseResult<Lyric, I>
where I: Stream<Item=char> {
    (char(':')
     , spaces()
     , many::<String, _>(digit())
     , spaces()
     , many::<String, _>(digit())
     , spaces()
     , many::<String, _>(digit())
     , spaces()
     , many::<String, _>(satisfy(|c| c != '\n'))
     , newline())
        .map(|(_, _, _, _, duration, _, _, _, text, _)| {
            Lyric {
                duration: duration.parse::<u64>().unwrap() * 100,
                text: text.trim_right().to_string()
            }
        })
    .expected("lyric")
    .parse_state(input)
}

fn split<I>(input: State<I>) -> ParseResult<(), I>
where I: Stream<Item=char> {
    let split = (token('-'), skip_many(satisfy(|c| c != '\n'))).map(|_| ());
    skip_many(skip_many1(space()).or(split))
        .parse_state(input)
}

pub fn karaoke<I>(input: State<I>) -> ParseResult<Karaoke, I>
where I: Stream<Item=char> {
    (parser(headers), many(parser(lyric).skip(parser(split))), char('E'))
        .map(|(h, l, _)| {
            Karaoke {
                header: h,
                lyrics: l
            }
    }).parse_state(input)
}

pub fn read_karaoke_file(input: &str) -> String {
    let path = Path::new(&input);
    let display = path.display();
    let mut file = match File::open(&path) {
        Err(err) => {
            println!("Couldn't open {}: {}", display, Error::description(&err));
            process::exit(1);
        },
        Ok(file) => file,
    };

    let mut buffer = String::new();
    match file.read_to_string(&mut buffer) {
        Err(err) => {
            println!("Couldn't read {}: {}", display, Error::description(&err));
            process::exit(1);
        },
        Ok(_) => println!("Starting karaoke with {}", display),
    }
    buffer
}

pub fn parse_karaoke_file(input: &str) {
    let buffer = read_karaoke_file(&input);
    let text = from_iter(buffer.chars());

    match parser(karaoke).parse(text.clone()) {
        Ok((k, _)) => {
            for (k, v) in k.header.iter() {
                println!("{} ~> {}", k, v);
            }
            for lyric in k.lyrics.iter() {
                println!("{}", lyric.text);
                sleep(Duration::from_millis(lyric.duration));
            }
        },
        Err(err) => {
            println!("Couldn't read file: {}", Error::description(&err));
            process::exit(1);
        }
    };
}