aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/filesystem.rs
blob: 758282afd870ff2358ac1a9dafcfacfb0da91551 (plain)
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
use std::env::{current_dir, set_current_dir};
use std::fs;
use std::fs::canonicalize;
use std::path::PathBuf;

use libceltchar::{Chapter, Cover, Error, Loader, Project, Raise};

const PROJECT_FILE : &str = "Book.toml";
pub struct Fs;

pub fn find_root() -> Result<PathBuf, Error> {
    let mut cwd : PathBuf = current_dir().or_raise("cannot get current directory")?;

    loop {
        cwd.push(PROJECT_FILE); // (*)

        if cwd.exists() {
            cwd.pop(); // we pop the `Book.toml` previously pushed (see (*))

            return Ok(cwd);
        } else {
            // We `pop` a first time for `Book.toml`, since we have pushed
            // previously it (see (*))
            cwd.pop();

            // We `pop` a second time to get the parent directory of cwd.  If
            // `pop` returns false, we are at the root of the current FS, and
            // there is no project file to find.
            if !cwd.pop() {
                return Err(Error::new("could not find Book.toml"));
            }
        }
    }
}

fn canonicalize_chapter(chapter : &Chapter<Vec<PathBuf>>) -> Result<Chapter<Vec<PathBuf>>, Error> {
    let title = chapter.title.clone();
    Ok(Chapter {
        title : title,
        content : chapter
            .content
            .iter()
            .map(|x| canonicalize(x).or_raise(&format!("Could not canonicalize {:?}", x)))
            .collect::<Result<_, Error>>()?,
    })
}

fn canonicalize_project(
    project : Project<PathBuf, Vec<PathBuf>>,
) -> Result<Project<PathBuf, Vec<PathBuf>>, Error> {
    Ok(Project {
        author : project.author,
        title : project.title,
        description : project.description,
        cover : project
            .cover
            .map(canonicalize)
            .map_or(Ok(None), |r| r.map(Some))
            .or_raise("…")?,
        numbering : project.numbering,
        chapters : project
            .chapters
            .iter()
            .map(canonicalize_chapter)
            .collect::<Result<_, Error>>()?,
        language : project.language,
    })
}

impl Loader for Fs {
    type ProjId = PathBuf;
    type CovId = PathBuf;
    type DocId = PathBuf;

    fn load_project(&self, id : &PathBuf) -> Result<Project<PathBuf, Vec<PathBuf>>, Error> {
        let cwd = current_dir().or_raise("could not get current dir")?;

        let input =
            fs::read_to_string(PROJECT_FILE).or_raise("found Book.toml, but cannot read it")?;

        // We have to modify set the current directory to the PROJECT_FILE directory,
        // otherwise `canonicalize` will not work.
        set_current_dir(id).or_raise("could not change the current directory")?;
        let res = canonicalize_project(
            toml::from_str(input.as_str()).or_raise(&format!("could not parse Book.toml"))?,
        )?;
        set_current_dir(cwd).or_raise("could not change the current directory")?;

        Ok(res)
    }

    fn load_cover(&self, id : &PathBuf) -> Result<Cover, Error> {
        let extension = id
            .extension()
            .or_raise("cover lacks an extension")?
            .to_str()
            .or_raise("cover extension is not valid utf-8")?;

        let content = fs::read(id).or_raise(&format!("could not read cover from {:?}", id))?;

        Ok(Cover {
            extension : String::from(extension),
            content : content,
        })
    }

    fn load_document(&self, id : &PathBuf) -> Result<String, Error> {
        fs::read_to_string(id).or_raise(&format!("Could not read {:?}", id))
    }
}