1mod template;
20
21#[cfg(test)]
22mod tests;
23
24pub use self::template::Template;
25pub use self::template::{
26 AVAILABLE_SPECS, DEFAULT_P2P_PORT, DEFAULT_RPC_PORT, DEFAULT_SPEC, TemplateContext,
27};
28pub use std::io::{Error, Result};
29
30use ckb_types::H256;
31use includedir::Files;
32use serde::{Deserialize, Serialize};
33use std::borrow::Cow;
34use std::fmt;
35use std::fs;
36use std::io::{self, BufReader, Cursor, Read};
37use std::path::{Path, PathBuf};
38
39use ckb_system_scripts::BUNDLED_CELL;
40
41mod bundled {
42 #![allow(missing_docs, clippy::unreadable_literal)]
43 include!(concat!(env!("OUT_DIR"), "/bundled.rs"));
44}
45pub use bundled::BUNDLED;
47
48include!(concat!(env!("OUT_DIR"), "/code_hashes.rs"));
49
50pub const CKB_CONFIG_FILE_NAME: &str = "ckb.toml";
52pub const MINER_CONFIG_FILE_NAME: &str = "ckb-miner.toml";
54pub const SPEC_DEV_FILE_NAME: &str = "specs/dev.toml";
56pub const DB_OPTIONS_FILE_NAME: &str = "default.db-options";
58
59#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
62#[serde(untagged)]
63pub enum Resource {
64 Bundled {
66 bundled: String,
68 },
69 FileSystem {
71 file: PathBuf,
73 },
74 Raw {
76 raw: String,
78 },
79}
80
81impl fmt::Display for Resource {
82 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 match self {
84 Resource::Bundled { bundled } => write!(f, "Bundled({bundled})"),
85 Resource::FileSystem { file } => write!(f, "FileSystem({})", file.display()),
86 Resource::Raw { raw } => write!(f, "Raw({})", raw),
87 }
88 }
89}
90
91impl Resource {
92 pub fn bundled(bundled: String) -> Resource {
94 Resource::Bundled { bundled }
95 }
96
97 pub fn file_system(file: PathBuf) -> Resource {
99 Resource::FileSystem { file }
100 }
101
102 pub fn raw(raw: String) -> Resource {
104 Resource::Raw { raw }
105 }
106
107 pub fn ckb_config<P: AsRef<Path>>(root_dir: P) -> Resource {
111 Resource::file_system(root_dir.as_ref().join(CKB_CONFIG_FILE_NAME))
112 }
113
114 pub fn miner_config<P: AsRef<Path>>(root_dir: P) -> Resource {
118 Resource::file_system(root_dir.as_ref().join(MINER_CONFIG_FILE_NAME))
119 }
120
121 pub fn db_options<P: AsRef<Path>>(root_dir: P) -> Resource {
125 Resource::file_system(root_dir.as_ref().join(DB_OPTIONS_FILE_NAME))
126 }
127
128 pub fn bundled_ckb_config() -> Resource {
130 Resource::bundled(CKB_CONFIG_FILE_NAME.to_string())
131 }
132
133 pub fn bundled_miner_config() -> Resource {
135 Resource::bundled(MINER_CONFIG_FILE_NAME.to_string())
136 }
137
138 pub fn bundled_db_options() -> Resource {
140 Resource::bundled(DB_OPTIONS_FILE_NAME.to_string())
141 }
142
143 pub fn exported_in<P: AsRef<Path>>(root_dir: P) -> bool {
148 BUNDLED
149 .file_names()
150 .chain(BUNDLED_CELL.file_names())
151 .any(|name| join_bundled_key(root_dir.as_ref().to_path_buf(), name).exists())
152 }
153
154 pub fn is_bundled(&self) -> bool {
156 matches!(self, Resource::Bundled { .. })
157 }
158
159 pub fn exists(&self) -> bool {
165 match self {
166 Resource::Bundled { bundled } => {
167 SourceFiles::new(&BUNDLED_CELL, &BUNDLED).is_available(bundled)
168 }
169 Resource::FileSystem { file } => file.exists(),
170 Resource::Raw { .. } => true,
171 }
172 }
173
174 pub fn parent(&self) -> Option<&Path> {
178 match self {
179 Resource::FileSystem { file } => file.parent(),
180 _ => None,
181 }
182 }
183
184 pub fn absolutize<P: AsRef<Path>>(&mut self, base: P) {
188 if let Resource::FileSystem { file: path } = self {
189 if path.is_relative() {
190 *path = base.as_ref().join(&path)
191 }
192 }
193 }
194
195 pub fn get(&self) -> Result<Cow<'static, [u8]>> {
197 match self {
198 Resource::Bundled { bundled } => SourceFiles::new(&BUNDLED_CELL, &BUNDLED).get(bundled),
199 Resource::FileSystem { file } => Ok(Cow::Owned(fs::read(file)?)),
200 Resource::Raw { raw } => Ok(Cow::Owned(raw.to_owned().into_bytes())),
201 }
202 }
203
204 pub fn read(&self) -> Result<Box<dyn Read>> {
206 match self {
207 Resource::Bundled { bundled } => {
208 SourceFiles::new(&BUNDLED_CELL, &BUNDLED).read(bundled)
209 }
210 Resource::FileSystem { file } => Ok(Box::new(BufReader::new(fs::File::open(file)?))),
211 Resource::Raw { raw } => Ok(Box::new(Cursor::new(raw.to_owned().into_bytes()))),
212 }
213 }
214
215 pub fn export<P: AsRef<Path>>(&self, context: &TemplateContext<'_>, root_dir: P) -> Result<()> {
224 let key = match self {
225 Resource::Bundled { bundled } => bundled,
226 _ => return Ok(()),
227 };
228 let target = join_bundled_key(root_dir.as_ref().to_path_buf(), key);
229 let template = Template::new(from_utf8(self.get()?)?);
230 if let Some(dir) = target.parent() {
231 fs::create_dir_all(dir)?;
232 }
233 let mut f = fs::File::create(&target)?;
234 template.render_to(&mut f, context)?;
235 Ok(())
236 }
237}
238
239struct SourceFiles<'a> {
240 system_cells: &'a Files,
241 config: &'a Files,
242}
243
244impl<'a> SourceFiles<'a> {
245 fn new(system_cells: &'a Files, config: &'a Files) -> Self {
246 SourceFiles {
247 system_cells,
248 config,
249 }
250 }
251
252 fn get(&self, path: &str) -> Result<Cow<'static, [u8]>> {
253 self.config
254 .get(path)
255 .or_else(|_| self.system_cells.get(path))
256 }
257
258 fn read(&self, path: &str) -> Result<Box<dyn Read>> {
259 self.config
260 .read(path)
261 .or_else(|_| self.system_cells.read(path))
262 }
263
264 fn is_available(&self, path: &str) -> bool {
265 self.config.is_available(path) || self.system_cells.is_available(path)
266 }
267}
268
269fn from_utf8(data: Cow<[u8]>) -> Result<String> {
270 String::from_utf8(data.to_vec()).map_err(|err| Error::new(io::ErrorKind::Other, err))
271}
272
273fn join_bundled_key(mut root_dir: PathBuf, key: &str) -> PathBuf {
274 key.split('/')
275 .for_each(|component| root_dir.push(component));
276 root_dir
277}