Skip to main content

moar/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(clippy::all, clippy::pedantic)]
3#![allow(clippy::missing_errors_doc)]
4
5use std::{
6    collections::BTreeMap,
7    fs::{File, read_to_string},
8    io::Write,
9    path::{Path, PathBuf},
10};
11
12use ordinary_config::{OrdinaryConfig, TemplateRefFieldBind};
13use serde_json::Value;
14
15#[allow(clippy::too_many_lines)]
16pub async fn download(path: &str, url: &str, dest: &str) -> Result<(), Box<dyn std::error::Error>> {
17    let path = Path::new(path);
18
19    let config_str = read_to_string(path.join("./ordinary.json"))?;
20    let config: OrdinaryConfig = serde_json::from_str(&config_str)?;
21
22    // def name, field name, field value
23    let mut indexed_vals: BTreeMap<String, Vec<(String, Value)>> = BTreeMap::new();
24
25    if let Some(content) = config.content {
26        let mut def_map = BTreeMap::new();
27
28        for def in content.definitions {
29            def_map.insert(def.name.clone(), def);
30        }
31
32        let objects_json = read_to_string(path.join(&content.file_path))?;
33
34        let objects: Vec<ordinary_types::ContentObject> = serde_json::from_str(&objects_json)?;
35
36        for object in objects {
37            if let Some(def) = def_map.get(&object.instance_of) {
38                for def_field in &def.fields {
39                    if def_field.indexed == Some(true) {
40                        for obj_field in &object.fields {
41                            if def_field.name == obj_field.name {
42                                if let Some(vec) = indexed_vals.get_mut(&def.name) {
43                                    vec.push((obj_field.name.clone(), obj_field.value.clone()));
44                                } else {
45                                    indexed_vals.insert(
46                                        def.name.clone(),
47                                        vec![(obj_field.name.clone(), obj_field.value.clone())],
48                                    );
49                                }
50                            }
51                        }
52                    }
53                }
54            }
55        }
56    }
57
58    if let Some(templates) = config.templates {
59        let client = reqwest::Client::new();
60
61        for template_config in templates {
62            if template_config.models.is_some() {
63                tracing::info!(
64                    "skipping template {} because models cannot be statically generated.",
65                    template_config.name
66                );
67                continue;
68            }
69
70            let mut request_routes = vec![];
71
72            if let Some(template_content) = template_config.content {
73                let mut has_bindings = false;
74
75                for content_ref in template_content {
76                    for ref_field in content_ref.fields {
77                        if let Some(binding) = ref_field.bind {
78                            match binding {
79                                TemplateRefFieldBind::Segment {
80                                    name,
81                                    expression: _,
82                                } => {
83                                    has_bindings = true;
84
85                                    if let Some(vals) = indexed_vals.get(&content_ref.name) {
86                                        for (field_name, val) in vals {
87                                            if field_name == &ref_field.name
88                                                && let Some(val) = val.as_str()
89                                            {
90                                                request_routes.push(
91                                                    template_config
92                                                        .route
93                                                        .replace(&format!("{{{name}}}"), val),
94                                                );
95                                            }
96                                        }
97                                    }
98                                }
99                                TemplateRefFieldBind::Token {
100                                    field: _,
101                                    expression: _,
102                                } => {
103                                    tracing::warn!(
104                                        "cannot generate for templates whose content binds to token fields"
105                                    );
106                                }
107                            }
108                        }
109                    }
110                }
111
112                if !has_bindings {
113                    request_routes.push(template_config.route);
114                }
115            }
116
117            tracing::info!("generating files for template \"{}\"", template_config.name);
118
119            // for request_route in &request_routes {
120            // todo: create the matrix of route permutations for multi-slug
121            // }
122
123            for request_route in request_routes {
124                tracing::info!("route: \"{}\"", request_route);
125
126                let res = client
127                    .get(format!("{url}{request_route}"))
128                    .send()
129                    .await?
130                    .bytes()
131                    .await?;
132
133                let mut path: PathBuf = Path::new(&format!("{dest}{request_route}")).into();
134
135                path.set_extension("");
136
137                let file_path = if request_route.ends_with('/') {
138                    std::fs::create_dir_all(&path)?;
139
140                    match template_config.mime.as_str() {
141                        "text/html" => path.join("index.html"),
142                        "text/xml" => path.join("index.xml"),
143                        "application/rss+xml" => path.join("feed.xml"),
144                        "text/plain" => path.join("index.txt"),
145                        _ => path.join("unknown"),
146                    }
147                } else if let Some(file_name) = path.file_name()
148                    && let Some(file_name) = file_name.to_str()
149                    && let Some(path) = path.parent()
150                {
151                    std::fs::create_dir_all(path)?;
152
153                    match template_config.mime.as_str() {
154                        "text/html" => path.join(format!("{file_name}.html")),
155                        "text/xml" | "application/rss+xml" => path.join(format!("{file_name}.xml")),
156                        "text/plain" => path.join(format!("{file_name}.txt")),
157                        _ => path.join(file_name),
158                    }
159                } else {
160                    return Err("no file_name or path".into());
161                };
162
163                tracing::info!("output: {:?}", file_path);
164
165                let mut file = File::create(file_path)?;
166                file.write_all(&res)?;
167            }
168        }
169    }
170
171    Ok(())
172}