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
127
128
129
130
131
132
133
134
135
136
137
use std::{process::Command, str::FromStr};

#[derive(Debug, Clone)]
pub struct DnoteBook {
    pub name: String,
}

impl FromStr for DnoteBook {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let name = s.trim().to_string();
        Ok(DnoteBook { name })
    }
}

#[derive(Debug, Clone)]
pub struct DnotePage {
    pub id: u32,
}

impl FromStr for DnotePage {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let id = s
            .trim()
            .trim_start_matches('(')
            .split(')')
            .next()
            .unwrap()
            .parse()
            .unwrap();
        Ok(DnotePage { id })
    }
}

#[derive(Debug, Clone)]
pub struct DnotePageInfo {
    pub content: String,
}

impl FromStr for DnotePageInfo {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let content = s.trim().to_string();
        Ok(DnotePageInfo { content })
    }
}

#[derive(Debug)]
pub struct DnoteClient {}

#[derive(Debug)]
pub enum DnoteClientError {
    DnoteCommand,
    UTF8ParseError,
    ParseError,
    UnknownError,
}

impl DnoteClient {
    pub fn get_books(&self) -> Result<Vec<DnoteBook>, DnoteClientError> {
        // println!("Viewing all books...");
        let output = Command::new("dnote")
            .arg("view")
            .arg("--name-only")
            .output()
            .map_err(|_| DnoteClientError::DnoteCommand)?;
        let stdout: String =
            String::from_utf8(output.stdout).map_err(|_| DnoteClientError::UTF8ParseError)?;
        let result: Result<Vec<DnoteBook>, _> = stdout.lines().map(|l| l.parse()).collect();
        result.map_err(|_| DnoteClientError::ParseError)
    }
    pub fn get_pages(&self, book_name: &str) -> Result<Vec<DnotePage>, DnoteClientError> {
        // println!("Viewing pages for book: {}", book_name);
        let output = Command::new("dnote")
            .arg("view")
            .arg(book_name)
            .output()
            .map_err(|_| DnoteClientError::DnoteCommand)?;
        let stdout =
            String::from_utf8(output.stdout).map_err(|_| DnoteClientError::UTF8ParseError)?;
        let result: Result<Vec<DnotePage>, _> = stdout
            .lines()
            // skip first line e.g '  • on book ccu'
            .skip(1)
            .map(|l| l.parse())
            .collect();
        result.map_err(|_| DnoteClientError::ParseError)
    }
    pub fn get_page_content(&self, page_id: u32) -> Result<DnotePageInfo, DnoteClientError> {
        // println!("Viewing content for page with id {}", page_id);
        let output = Command::new("dnote")
            .arg("view")
            .arg(page_id.to_string())
            .arg("--content-only")
            .output()
            .map_err(|_| DnoteClientError::DnoteCommand)?;
        let stdout =
            String::from_utf8(output.stdout).map_err(|_| DnoteClientError::UTF8ParseError)?;
        stdout.parse().map_err(|_| DnoteClientError::ParseError)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn should_parse_dnotebook_from_string() {
        let s = String::from("my notebook");
        let book: DnoteBook = s.parse().unwrap();
        assert_eq!(book.name, "my notebook")
    }

    #[test]
    fn should_parse_dnotepage_from_string() {
        let input1 = "(21) # Issues [---More---]";
        let input2 = "  (27) # Missed [---More---]";
        let page1: DnotePage = input1.parse().unwrap();
        let page2: DnotePage = input2.parse().unwrap();
        assert_eq!(page1.id, 21);
        assert_eq!(page2.id, 27);
    }

    #[test]
    fn should_parse_dnotepageinfo_from_string() {
        let input1 = "# E2E\n\n- Grab a list of all data test ids on a page\n- Make sure all those data test ids exist";
        let input2 = "   # E2E   \n\n   - Grab a list of all data test ids on a page   \n   - Make sure all those data test ids exist   ";
        let input3 = "";
        let page_info1: DnotePageInfo = input1.parse().unwrap();
        let page_info2: DnotePageInfo = input2.parse().unwrap();
        let page_info3: DnotePageInfo = input3.parse().unwrap();
        assert_eq!(page_info1.content, input1);
        assert_eq!(page_info2.content, input2.trim());
        assert_eq!(page_info3.content, input3);
    }
}