aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib.rs')
-rw-r--r--src/lib.rs514
1 files changed, 1 insertions, 513 deletions
diff --git a/src/lib.rs b/src/lib.rs
index 346dcef..cf634d2 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,516 +1,4 @@
#[macro_use]
extern crate combine;
-use std::collections::HashMap;
-
-use combine::*;
-use combine::parser::choice::or;
-use combine::parser::char::{
- newline,
- space,
- string,
- string_cmp,
- tab,
-};
-use combine::parser::repeat::take_until;
-
-#[derive(Debug, Hash, Eq, PartialEq)]
-pub enum HeadphoneButton {
- Play,
- Up,
- Down,
-}
-type Trigger = Vec<HeadphoneButton>;
-type Action = String;
-
-#[derive(Debug, PartialEq)]
-pub enum MapKind {
- Map,
- Command,
-}
-
-#[derive(Debug, PartialEq)]
-pub struct MapAction {
- pub action: Action,
- pub kind: MapKind,
-}
-
-#[derive(Debug, PartialEq)]
-struct Map {
- trigger: Trigger,
- action: Action,
- kind: MapKind,
-}
-
-type MapCollection = HashMap<Trigger, MapAction>;
-
-#[derive(Debug, PartialEq)]
-struct Mode {
- trigger: Trigger,
- maps: MapCollection,
-}
-
-#[derive(Debug, PartialEq)]
-pub struct MapGroup {
- maps: MapCollection,
- modes: HashMap<Trigger, MapCollection>,
-}
-
-#[derive(Debug, PartialEq)]
-enum Definition {
- Map(Map),
- Mode(Mode),
-}
-
-
-fn map_kind<I>() -> impl Parser<Input = I, Output = MapKind>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- or(
- string("map").map(|_| MapKind::Map),
- string("cmd").map(|_| MapKind::Command),
- )
-}
-
-fn headphone_button<I>() -> impl Parser<Input = I, Output = HeadphoneButton>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- between(
- token('<'),
- token('>'),
- choice!(
- string_cmp("play", |l, r| l.eq_ignore_ascii_case(&r))
- .map(|_| HeadphoneButton::Play),
- string_cmp("up", |l, r| l.eq_ignore_ascii_case(&r))
- .map(|_| HeadphoneButton::Up),
- string_cmp("down", |l, r| l.eq_ignore_ascii_case(&r))
- .map(|_| HeadphoneButton::Down)
- ),
- )
-}
-
-fn trigger<I>() -> impl Parser<Input = I, Output = Trigger>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- many1(headphone_button())
-}
-
-fn action<I>() -> impl Parser<Input = I, Output = Action>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- take_until(newline())
-}
-
-fn whitespace_separator<I>() -> impl Parser<Input = I>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- skip_many1(space().or(tab()))
-}
-
-fn map<I>() -> impl Parser<Input = I, Output = Map>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- (
- map_kind(),
- whitespace_separator(),
- trigger(),
- whitespace_separator(),
- action()
- ).map(|(kind, _, trigger, _, action)|
- Map {
- trigger: trigger,
- action: action,
- kind: kind,
- }
- )
-}
-
-fn map_collection<I>() -> impl Parser<Input = I, Output = MapCollection>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- (
- blank(),
- many::<Vec<Map>, _>(map().skip(blank())),
- ).map(|(_, collection)| {
- let mut maps = HashMap::new();
-
- for map in collection {
- maps.insert(
- map.trigger,
- MapAction {
- action: map.action,
- kind: map.kind,
- }
- );
- }
-
- maps
- })
-}
-
-fn mode<I>() -> impl Parser<Input = I, Output = Mode>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- (
- string("mode"),
- whitespace_separator(),
- trigger(),
- whitespace_separator(),
- token('{'),
- map_collection(),
- token('}'), // Verify that this is parsed on its own line, not inside a map
- ).map(|(_, _, trigger, _, _, collection, _)|
- Mode {
- trigger: trigger,
- maps: collection,
- }
- )
-}
-
-fn definitions<I>() -> impl Parser<Input = I, Output = Vec<Definition>>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- (
- blank(),
- many(
- choice!(
- try(map()).map(|map| Definition::Map(map)),
- try(mode()).map(|mode| Definition::Mode(mode))
- ).skip(blank())
- )
- ).map(|(_, definitions)| definitions)
-}
-
-fn map_group<I>() -> impl Parser<Input = I, Output = MapGroup>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- definitions()
- .map(|definitions| {
- let mut maps = HashMap::new();
- let mut modes = HashMap::new();
-
- for definition in definitions {
- match definition {
- Definition::Map(map) => {
- maps.insert(
- map.trigger,
- MapAction {
- action: map.action,
- kind: map.kind,
- }
- );
- },
- Definition::Mode(mode) => {
- modes.insert(
- mode.trigger,
- mode.maps,
- );
- },
- }
- }
-
- MapGroup {
- maps: maps,
- modes: modes,
- }
- })
-}
-
-fn comment<I>() -> impl Parser<Input = I>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- (
- token('#'),
- skip_many(satisfy(|c| c != '\n')),
- )
-}
-
-
-fn blank<I>() -> impl Parser<Input = I>
-where
- I: Stream<Item = char>,
- I::Error: ParseError<I::Item, I::Range, I::Position>,
-{
- let comment = comment().map(|_| ());
- let whitespace = whitespace_separator().map(|_| ());
- skip_many(skip_many1(newline()).or(whitespace).or(comment))
-}
-
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn map_kind_parses_kind_map() {
- let text = "map";
- let result = map_kind().parse(text).map(|t| t.0);
-
- assert_eq!(result, Ok(MapKind::Map));
- }
-
- #[test]
- fn map_kind_parses_kind_command() {
- let text = "cmd";
- let result = map_kind().parse(text).map(|t| t.0);
-
- assert_eq!(result, Ok(MapKind::Command));
- }
-
- #[test]
- fn headphone_button_parses_play() {
- let text = "<play>";
- let result = headphone_button().parse(text).map(|t| t.0);
-
- assert_eq!(result, Ok(HeadphoneButton::Play));
- }
-
- #[test]
- fn headphone_button_ignores_case() {
- let text = "<Play>";
- let result = headphone_button().parse(text).map(|t| t.0);
-
- assert_eq!(result, Ok(HeadphoneButton::Play));
- }
-
- #[test]
- fn trigger_parses_headphone_button_sequence() {
- let text = "<up><down><play>";
- let result = trigger().parse(text).map(|t| t.0);
-
- assert_eq!(result, Ok(vec![
- HeadphoneButton::Up,
- HeadphoneButton::Down,
- HeadphoneButton::Play,
- ]));
- }
-
- #[test]
- fn action_parses_string_to_end_of_line() {
- let text = "/usr/bin/say 'hello'
-";
- let expected: Action = "/usr/bin/say 'hello'".to_owned();
- let result = action().parse(text).map(|t| t.0);
-
- assert_eq!(result, Ok(expected));
- }
-
- #[test]
- fn map_parses_map_line() {
- let text = "map <play><down> test
-";
- let expected = Map {
- trigger: vec![HeadphoneButton::Play, HeadphoneButton::Down],
- action: "test".to_owned(),
- kind: MapKind::Map,
- };
- let result = map().parse(text).map(|t| t.0);
-
- assert_eq!(result, Ok(expected));
- }
-
- #[test]
- fn map_collection_parses_maps() {
- let text = "
-# Test comment
- # continued
-
-map <up><down> test
-map <play> salt and pepper
-
-# Another comment
-cmd <down> /usr/bin/say 'hello'
-";
- let result = map_collection().easy_parse(text).map(|t| t.0);
-
- let mut expected = HashMap::new();
- expected.insert(
- vec![HeadphoneButton::Up, HeadphoneButton::Down],
- MapAction {
- action: "test".to_owned(),
- kind: MapKind::Map,
- },
- );
- expected.insert(
- vec![HeadphoneButton::Play],
- MapAction {
- action: "salt and pepper".to_owned(),
- kind: MapKind::Map,
- },
- );
- expected.insert(
- vec![HeadphoneButton::Down],
- MapAction {
- action: "/usr/bin/say 'hello'".to_owned(),
- kind: MapKind::Command,
- },
- );
-
- assert_eq!(result, Ok(expected));
- }
-
- #[test]
- fn mode_parses_a_mode() {
- let text = "mode <down><up> {
- cmd <up><play> echo hello
- map <down> insert {}
- }";
- let result = mode().parse(text).map(|t| t.0);
-
- let mut expected = Mode {
- trigger: vec![HeadphoneButton::Down, HeadphoneButton::Up],
- maps: HashMap::new(),
- };
-
- expected.maps.insert(
- vec![HeadphoneButton::Up, HeadphoneButton::Play],
- MapAction {
- action: "echo hello".to_owned(),
- kind: MapKind::Command,
- },
- );
- expected.maps.insert(
- vec![HeadphoneButton::Down],
- MapAction {
- action: "insert {}".to_owned(),
- kind: MapKind::Map,
- },
- );
-
- assert_eq!(result, Ok(expected));
- }
-
- #[test]
- fn definitions_parses_modes_and_maps() {
- let text = "
-
-mode <up> {
- cmd <down> j
-}
-map <play> m
-mode <down><up> {
- cmd <down> j
-}
-
-map <down> k
-";
- let result = definitions().easy_parse(text).map(|t| t.0);
-
- let mut mode_up_maps = HashMap::new();
- mode_up_maps.insert(
- vec![HeadphoneButton::Down],
- MapAction {
- action: "j".to_owned(),
- kind: MapKind::Command,
- }
- );
-
- let mut mode_down_up_maps = HashMap::new();
- mode_down_up_maps.insert(
- vec![HeadphoneButton::Down],
- MapAction {
- action: "j".to_owned(),
- kind: MapKind::Command,
- }
- );
-
- let expected = vec![
- Definition::Mode(Mode {
- trigger: vec![HeadphoneButton::Up],
- maps: mode_up_maps,
- }),
- Definition::Map(Map {
- trigger: vec![HeadphoneButton::Play],
- action: "m".to_owned(),
- kind: MapKind::Map,
- }),
- Definition::Mode(Mode {
- trigger: vec![HeadphoneButton::Down, HeadphoneButton::Up],
- maps: mode_down_up_maps,
- }),
- Definition::Map(Map {
- trigger: vec![HeadphoneButton::Down],
- action: "k".to_owned(),
- kind: MapKind::Map,
- }),
- ];
-
- assert_eq!(result, Ok(expected));
- }
-
- #[test]
- fn map_group_parses_a_whole_map_file_string() {
- let text = "map <play> some text
-
-# The following does nothing
-cmd <down> /bin/echo nothing
-
-mode <down><up> {
- map <play> p
-}
-
-cmd <play> /usr/bin/say hello
-";
- let result = map_group().easy_parse(text).map(|t| t.0);
-
- let mut maps: MapCollection = HashMap::new();
- let mut modes: HashMap<Trigger, MapCollection> = HashMap::new();
- let mut mode_maps: MapCollection = HashMap::new();
-
- maps.insert(
- vec![HeadphoneButton::Down],
- MapAction {
- action: "/bin/echo nothing".to_owned(),
- kind: MapKind::Command,
- },
- );
- maps.insert(
- vec![HeadphoneButton::Play],
- MapAction {
- action: "/usr/bin/say hello".to_owned(),
- kind: MapKind::Command,
- },
- );
-
- mode_maps.insert(
- vec![HeadphoneButton::Play],
- MapAction {
- action: "p".to_owned(),
- kind: MapKind::Map,
- },
- );
- modes.insert(
- vec![HeadphoneButton::Down, HeadphoneButton::Up],
- mode_maps,
- );
-
- let expected = MapGroup {
- maps: maps,
- modes: modes,
- };
-
- assert_eq!(result, Ok(expected));
- }
-}
+mod parser;