lssg_lib/sitetree/
site_node.rs

1use std::{
2    fs::{self, File},
3    io::{Cursor, Read},
4    path::{Path, PathBuf},
5};
6
7use crate::{path_extension::PathExtension, tree::Node, LssgError};
8use pathdiff::diff_paths;
9use reqwest::Url;
10
11use super::stylesheet::Stylesheet;
12use super::{page::Page, Resource};
13
14/// Wrapper around absolute path to either an internal or external (http://) file
15#[derive(Debug, Clone, Hash, Eq, PartialEq)] // TODO check if Hash is valid
16pub enum Input {
17    Local { path: PathBuf },
18    External { url: Url },
19}
20impl Input {
21    /// Create an Input from string
22    pub fn from_string(string: &str) -> Result<Input, LssgError> {
23        // if starts with http must be absolute
24        if string.starts_with("http") {
25            let url = Url::parse(&string).unwrap(); // TODO always ping url to check if exists
26            return Ok(Input::External { url });
27        }
28
29        let mut path = PathBuf::from(&string);
30        path = fs::canonicalize(path)?;
31
32        Ok(Input::Local { path })
33    }
34
35    pub fn make_relative(&self, to: &Input) -> Option<String> {
36        match self {
37            Input::Local { path: from_path } => match to {
38                Input::Local { path: to_path } => {
39                    let from_path = if from_path.is_file() {
40                        from_path.parent().unwrap_or(&from_path)
41                    } else {
42                        from_path
43                    };
44                    return diff_paths(to_path, from_path)
45                        .map(|p| p.to_str().map(|s| s.to_string()))
46                        .flatten();
47                }
48                _ => return None,
49            },
50            Input::External { url: from_url } => match to {
51                Input::External { url: to_url } => from_url.make_relative(to_url),
52                _ => return None,
53            },
54        }
55    }
56
57    /// check if path is a relative path
58    pub fn is_relative(path: &str) -> bool {
59        if path.starts_with("/") || path.starts_with("http") {
60            return false;
61        }
62        return true;
63    }
64    /// Create a new Input with path relative to `self` or absolute path
65    pub fn new(&self, path_string: &str) -> Result<Input, LssgError> {
66        // return new if absolute
67        if path_string.starts_with("http") {
68            let url = Url::parse(&path_string).unwrap();
69            return Ok(Input::External { url });
70        }
71
72        match self {
73            Input::Local { path } => {
74                // relative local path
75                let path: &Path = if path.filename_from_path()?.contains(".") {
76                    &path.parent().unwrap_or(&path)
77                } else {
78                    &path
79                };
80                let mut path = path.join(path_string);
81                path = fs::canonicalize(path)?;
82                return Ok(Input::Local { path });
83            }
84            Input::External { url } => {
85                // relative url path
86                let url = url.join(path_string).unwrap(); // TODO check if cannonical
87                return Ok(Input::External { url });
88            }
89        }
90    }
91    pub fn filestem(&self) -> Result<String, LssgError> {
92        match self {
93            Input::Local { path } => path.filestem_from_path(),
94            Input::External { url } => Path::new(url.path()).filestem_from_path(),
95        }
96    }
97    pub fn filename(&self) -> Result<String, LssgError> {
98        match self {
99            Input::Local { path } => path.filename_from_path(),
100            Input::External { url } => Path::new(url.path()).filename_from_path(),
101        }
102    }
103    pub fn to_string(&self) -> String {
104        match self {
105            Input::Local { path } => path.to_string_lossy().to_string(),
106            Input::External { url } => url.to_string(),
107        }
108    }
109    pub fn readable(&self) -> Result<Box<dyn Read>, LssgError> {
110        match self {
111            Input::Local { path } => {
112                let file = File::open(path)?;
113                Ok(Box::new(file))
114            }
115            Input::External { url } => {
116                // FIXME unwrap
117                let response = reqwest::blocking::get(url.clone()).unwrap();
118                let content = Cursor::new(response.bytes().unwrap());
119                Ok(Box::new(content))
120            }
121        }
122    }
123}
124
125#[derive(Debug)]
126pub enum SiteNodeKind {
127    Stylesheet(Stylesheet),
128    Page(Page),
129    Resource(Resource),
130    Folder,
131}
132impl SiteNodeKind {
133    pub fn input_is_page(input: &Input) -> bool {
134        input.to_string().ends_with(".md")
135    }
136    pub fn input_is_stylesheet(input: &Input) -> bool {
137        input.to_string().ends_with(".css")
138    }
139    pub fn is_page(&self) -> bool {
140        if let SiteNodeKind::Page { .. } = self {
141            true
142        } else {
143            false
144        }
145    }
146}
147impl ToString for SiteNodeKind {
148    fn to_string(&self) -> String {
149        match self {
150            SiteNodeKind::Stylesheet { .. } => "Stylesheet",
151            SiteNodeKind::Page { .. } => "Page",
152            SiteNodeKind::Resource { .. } => "Resource",
153            SiteNodeKind::Folder => "Folder",
154        }
155        .into()
156    }
157}
158
159#[derive(Debug)]
160pub struct SiteNode {
161    /// Unique name within children of node
162    pub name: String,
163    pub parent: Option<usize>,
164    pub children: Vec<usize>,
165    pub kind: SiteNodeKind,
166}
167impl Node for SiteNode {
168    fn children(&self) -> &Vec<usize> {
169        &self.children
170    }
171}
172impl SiteNode {
173    pub fn stylesheet(name: impl Into<String>, parent: usize, stylesheet: Stylesheet) -> SiteNode {
174        SiteNode {
175            name: name.into(),
176            parent: Some(parent),
177            children: vec![],
178            kind: SiteNodeKind::Stylesheet(stylesheet),
179        }
180    }
181}