aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTeddy Wing2018-08-22 01:42:39 +0200
committerTeddy Wing2018-08-22 01:42:39 +0200
commit5f670cf87ac217930babb0b8ef0f953fd2ee3447 (patch)
tree29895af69ae4058cad972acc197777bceb93aed3 /src
parent62f649a368bee6c7dbff88d7d599637bb9c5d184 (diff)
downloaddome-key-map-5f670cf87ac217930babb0b8ef0f953fd2ee3447.tar.bz2
Move all code in `lib.rs` to `parser.rs`
Put our parsing code in its own module. We'll be creating a new `cocoa` module and doing this allows us to keep `lib` free as a module container.
Diffstat (limited to 'src')
-rw-r--r--src/lib.rs514
-rw-r--r--src/parser.rs513
2 files changed, 514 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;
diff --git a/src/parser.rs b/src/parser.rs
new file mode 100644
index 0000000..e30c5f4
--- /dev/null
+++ b/src/parser.rs
@@ -0,0 +1,513 @@
+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));
+ }
+}