1use std::str::FromStr;
7
8use miette::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
9
10mod code_display;
11mod diag;
12mod identifier;
13mod ord_map;
14mod output;
15mod rc;
16mod source;
17mod src_ref;
18mod tree_display;
19
20pub use compact_str::{CompactString, ToCompactString};
21
22pub type Id = CompactString;
24
25pub use url::Url;
27
28pub fn virtual_url(name: &str) -> Url {
29 Url::from_str(&format!("virtual://{name}")).unwrap()
30}
31
32pub trait ResourceLocation {
33 fn url(&self) -> &Url;
35
36 fn to_file_path(&self) -> Option<std::path::PathBuf> {
39 if self.url().scheme() == "file" {
40 self.url().to_file_path().ok()
41 } else {
42 None
43 }
44 }
45
46 fn is_local(&self) -> bool {
48 self.url().scheme() == "file"
49 }
50
51 fn relative_path(&self) -> Option<std::path::PathBuf> {
53 self.to_file_path().map(|path| {
54 let current_dir = std::env::current_dir().expect("current dir");
55 if let Ok(path) = path.canonicalize() {
56 pathdiff::diff_paths(path, current_dir).unwrap_or_default()
57 } else {
58 path.to_path_buf()
59 }
60 })
61 }
62
63 fn source_name(&self) -> String {
65 self.relative_path()
66 .map(|s| s.to_string_lossy().to_string())
67 .unwrap_or(self.url().path().to_string())
68 }
69}
70
71pub const MICROCAD_EXTENSIONS: &[&str] = &["mu", "µcad", "mcad", "ucad"];
73
74pub const MICROCAD_EXTENSION: &str = "µcad";
76
77pub use code_display::*;
78pub use diag::{
79 Diag, DiagError, DiagHandler, DiagRenderOptions, DiagResult, Diagnostic, Diagnostics, Level,
80 Level as DiagLevel, PushDiag,
81};
82pub use identifier::Identifier;
83pub use ord_map::{OrdMap, OrdMapValue};
84pub use output::{Capture, Output, Stdout};
85pub use rc::{Rc, RcMut};
86pub use src_ref::{LineCol, LineIndex, Refer, Span, Spanned, SrcRef, SrcReferrer};
87pub use tree_display::{FormatTree, TreeDisplay, TreeState};
88
89pub use microcad_core::hash::{ComputedHash, HashId, HashMap, HashSet, Hashed, Hasher};
90pub use source::Source;
91
92#[derive(Debug, PartialEq, Copy, Clone)]
94pub enum WorkbenchKind {
95 Sketch,
97 Part,
99 Op,
101}
102
103impl std::fmt::Display for WorkbenchKind {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 write!(
106 f,
107 "{}",
108 match &self {
109 WorkbenchKind::Sketch => "sketch",
110 WorkbenchKind::Part => "part",
111 WorkbenchKind::Op => "op",
112 }
113 )
114 }
115}
116
117pub struct SourceLocInfo<'a> {
119 pub code: &'a str,
121 pub url: Url,
123 pub line_offset: u32,
125}
126
127impl SourceLocInfo<'static> {
128 pub fn invalid() -> Self {
130 SourceLocInfo {
131 code: "NO FILE",
132 url: virtual_url("invalid"),
133 line_offset: 0,
134 }
135 }
136}
137
138impl<'a> ResourceLocation for SourceLocInfo<'a> {
139 fn url(&self) -> &Url {
140 &self.url
141 }
142}
143
144impl SourceCode for SourceLocInfo<'_> {
145 fn read_span<'a>(
146 &'a self,
147 span: &SourceSpan,
148 context_lines_before: usize,
149 context_lines_after: usize,
150 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
151 let inner_contents =
152 self.code
153 .read_span(span, context_lines_before, context_lines_after)?;
154 let contents = MietteSpanContents::new_named(
155 self.source_name(),
156 inner_contents.data(),
157 *inner_contents.span(),
158 inner_contents.line() + self.line_offset as usize,
159 inner_contents.column(),
160 inner_contents.line_count(),
161 )
162 .with_language("µcad");
163 Ok(Box::new(contents))
164 }
165}
166
167pub trait GetSourceLocInfoByHash {
169 fn get_source_loc_info_by_hash(&'_ self, hash: HashId) -> Option<SourceLocInfo<'_>>;
171}
172
173pub fn shorten(what: &str, max_chars: usize) -> String {
175 let short: String = what
176 .chars()
177 .enumerate()
178 .filter_map(|(p, ch)| {
179 if p == max_chars {
180 Some('…')
181 } else if p < max_chars {
182 if ch == '\n' { Some('⏎') } else { Some(ch) }
183 } else {
184 None
185 }
186 })
187 .collect();
188
189 if cfg!(feature = "ansi-color") && short.contains('\x1b') {
190 short + "\x1b[0m"
191 } else {
192 short
193 }
194}
195
196#[cfg(feature = "ansi-color")]
198#[macro_export]
199macro_rules! mark {
200 (FOUND!) => {
201 color_print::cformat!("<G!,k,s> FOUND </>")
202 };
203 (FOUND) => {
204 color_print::cformat!("<W!,k,s> FOUND </>")
205 };
206 (MATCH) => {
207 color_print::cformat!("<Y!,k,s> MATCH </>")
208 };
209 (NO_MATCH) => {
210 color_print::cformat!("<Y,k,s> NO MATCH </>")
211 };
212 (MATCH!) => {
213 color_print::cformat!("<G!,k,s> MATCH </>")
214 };
215 (NO_MATCH!) => {
216 color_print::cformat!("<R,k,s> NO MATCH </>")
217 };
218 (CALL) => {
219 color_print::cformat!("<B,k,s> CALL </>")
220 };
221 (LOOKUP) => {
222 color_print::cformat!("<c,s>LOOKUP</>")
223 };
224 (LOAD) => {
225 color_print::cformat!("<Y,k,s> LOADING </>")
226 };
227 (RESOLVE) => {
228 color_print::cformat!("<M,k,s> RESOLVE </>")
229 };
230 (AMBIGUOUS) => {
231 color_print::cformat!("<R,k,s> AMBIGUOUS </>")
232 };
233 (NOT_FOUND!) => {
234 color_print::cformat!("<R,k,s> NOT FOUND </>")
235 };
236 (NOT_FOUND) => {
237 color_print::cformat!("<Y,k,s> NOT FOUND </>")
238 };
239}
240
241pub trait WriteToFile: std::fmt::Display {
243 fn write_to_file(&self, filename: &impl AsRef<std::path::Path>) -> std::io::Result<()> {
245 use std::io::Write;
246 let file = std::fs::File::create(filename)?;
247 let mut writer = std::io::BufWriter::new(file);
248 write!(writer, "{self}")
249 }
250}