1use std::fs;
2use std::path::MAIN_SEPARATOR;
3
4use crate::prelude::*;
5use crate::util::PathBufExt as _;
6
7#[derive(Debug)]
9enum Node {
10 File {
11 path: &'static str,
12 content: &'static [u8],
13 },
14 Dir {
15 path: &'static str,
16 },
17}
18
19impl Node {
20 const fn dir(path: &'static str) -> Self {
21 Self::Dir { path }
22 }
23
24 fn path_buf(&self) -> PathBuf {
25 match self {
26 Self::File { path, .. } => path,
27 Self::Dir { path } => path,
28 }
29 .replace('/', &String::from(MAIN_SEPARATOR))
30 .into() }
32
33 fn resolve(&self, base: &Path) -> NodeResolved {
34 let mut path = self.path_buf();
35 path.resolve(base);
36 match self {
37 Self::File { content, .. } => NodeResolved::File { path, content },
38 Self::Dir { .. } => NodeResolved::Dir { path },
39 }
40 }
41}
42
43enum NodeResolved {
44 File {
45 path: PathBuf,
46 content: &'static [u8],
47 },
48 Dir {
49 path: PathBuf,
50 },
51}
52
53impl NodeResolved {
54 fn path(&self) -> &Path {
55 match self {
56 Self::File { path, .. } => path.as_ref(),
57 Self::Dir { path } => path.as_ref(),
58 }
59 }
60
61 fn create(&self) -> Result<()> {
62 let dir_path = match self {
63 Self::File { path, .. } => path.parent(),
64 Self::Dir { path } => Some(path.as_ref()),
65 };
66 if let Some(dir_path) = dir_path {
67 fs::create_dir_all(dir_path)
68 .with_context(|| format!("Could not create directory {:?}", dir_path))?;
69 }
70
71 if let Self::File { path, content } = self {
72 fs::write(path, content)
73 .with_context(|| format!("Could not initialize file {:?}", path))?;
74 }
75
76 Ok(())
77 }
78}
79
80macro_rules! node_file {
81 ($path:literal) => {
82 Node::File {
83 path: $path,
84 content: include_bytes!(concat!("../default/", $path)),
85 }
86 };
87}
88
89#[derive(Debug)]
90pub struct DefaultProject {
91 nodes: &'static [Node],
92}
93
94impl DefaultProject {
95 pub fn resolve(&self, project_dir: &Path) -> DefaultProjectResolved {
96 let nodes = self.nodes.iter().map(|n| n.resolve(project_dir)).collect();
97 DefaultProjectResolved { nodes }
98 }
99}
100
101pub const DEFAULT_PROJECT: DefaultProject = DefaultProject {
102 nodes: &[
103 node_file!("bard.toml"),
105 node_file!("songs/yippie.md"),
107 Node::dir("output"),
109 Node::dir("output/fonts"),
111 node_file!("output/fonts/BardSerif-Regular.ttf"),
112 node_file!("output/fonts/BardSerif-BoldItalic.ttf"),
113 node_file!("output/fonts/BardSerif-Bold.ttf"),
114 node_file!("output/fonts/BardSerif-Italic.ttf"),
115 node_file!("output/fonts/BardSerif-Regular.ttf"),
116 node_file!("output/fonts/BardSans-BoldItalic.ttf"),
117 node_file!("output/fonts/BardSans-Bold.ttf"),
118 node_file!("output/fonts/BardSans-Italic.ttf"),
119 node_file!("output/fonts/BardSans-Regular.ttf"),
120 node_file!("output/fonts/fonts.css"),
121 node_file!("output/fonts/fonts.tex"),
122 ],
123};
124
125pub struct DefaultProjectResolved {
126 nodes: Vec<NodeResolved>,
127}
128
129impl DefaultProjectResolved {
130 pub fn create(self) -> Result<()> {
131 let existing = self.nodes.iter().find(|n| n.path().exists());
132 if let Some(existing) = existing {
133 bail!("File already exists: {:?}", existing.path());
134 }
135
136 for node in &self.nodes[..] {
137 node.create()?;
138 }
139
140 Ok(())
141 }
142
143 pub fn files(&self) -> impl Iterator<Item = &Path> {
144 self.nodes.iter().filter_map(|node| match node {
145 NodeResolved::File { path, .. } => Some(path.as_path()),
146 NodeResolved::Dir { .. } => None,
147 })
148 }
149
150 pub fn dirs(&self) -> impl Iterator<Item = &Path> {
151 self.nodes.iter().filter_map(|node| match node {
152 NodeResolved::Dir { path } => Some(path.as_path()),
153 NodeResolved::File { .. } => None,
154 })
155 }
156}