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 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 {
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}