Skip to content

Instantly share code, notes, and snippets.

@connorskees
Created June 30, 2023 05:53
Show Gist options
  • Save connorskees/7af84cfe4b3157e0120dad06f33cd112 to your computer and use it in GitHub Desktop.
Save connorskees/7af84cfe4b3157e0120dad06f33cd112 to your computer and use it in GitHub Desktop.
use std::{
collections::{HashMap, HashSet},
io::{self, Write},
path::{Path, PathBuf},
};
use grass_compiler::{
sass_ast::{
AstAtRootRule, AstForwardRule, AstImport, AstImportRule, AstMedia, AstRuleSet,
AstSassImport, AstStmt, AstSupportsRule, AstUnknownAtRule, AstUseRule,
},
Options, Visitor,
};
#[derive(Debug)]
struct DependencyGraph {
edges: HashMap<PathBuf, HashSet<PathBuf>>,
}
impl DependencyGraph {
fn write_dot_file(&self, path: impl AsRef<Path>) -> io::Result<()> {
let mut file = std::fs::File::open(path)?;
writeln!(file, "digraph {{")?;
writeln!(file, " node [shape=record fontname=Arial];")?;
writeln!(file)?;
let strip_prefix = |path: &PathBuf| {
path.to_string_lossy()
.strip_prefix("/root/grass/pico/scss/")
.unwrap_or(&*path.to_string_lossy())
.to_owned()
};
for (node, edges) in &self.edges {
writeln!(
file,
" \"{}\" -> {{ {} }}",
strip_prefix(node),
edges
.iter()
.map(|n| format!("\"{}\"", strip_prefix(n)))
.collect::<Vec<_>>()
.join(", ")
)?;
}
writeln!(file, "}}")?;
file.flush()?;
Ok(())
}
}
fn main() {
let mut map = grass_compiler::codemap::CodeMap::new();
let path = "/root/grass/pico/scss/pico.scss";
let options = Options::default();
let input = std::fs::read_to_string(path).unwrap();
let file = map.add_file(path.to_owned(), input.clone());
let empty_span = file.span.subspan(0, 0);
let ss = grass_compiler::parse_stylesheet(input, path, &options).unwrap();
let mut graph = DependencyGraph {
edges: HashMap::new(),
};
let mut visitor = Visitor::new(path.as_ref(), &options, &mut map, empty_span);
for stmt in ss.body {
visit_stmt(&path.into(), &stmt, &mut graph, &mut visitor);
}
graph.write_dot_file("./graph.dot").unwrap();
}
fn import_url(
importing_url: &PathBuf,
imported_url: &PathBuf,
graph: &mut DependencyGraph,
visitor: &mut Visitor,
) {
if imported_url.to_string_lossy().starts_with("sass:") {
graph
.edges
.entry(importing_url.clone())
.or_default()
.insert(imported_url.clone());
return;
}
let resolved = std::fs::canonicalize(visitor.find_import(imported_url).unwrap()).unwrap();
graph
.edges
.entry(importing_url.clone())
.or_default()
.insert(resolved.clone());
let input = std::fs::read_to_string(&resolved).unwrap();
let ss = grass_compiler::parse_stylesheet(input, &resolved, &visitor.options).unwrap();
let mut url = resolved.clone();
std::mem::swap(&mut visitor.current_import_path, &mut url);
for stmt in ss.body {
visit_stmt(&resolved, &stmt, graph, visitor);
}
std::mem::swap(&mut visitor.current_import_path, &mut url);
}
fn visit_stmt(file: &PathBuf, stmt: &AstStmt, graph: &mut DependencyGraph, visitor: &mut Visitor) {
match stmt {
AstStmt::RuleSet(AstRuleSet { body, .. })
| AstStmt::Media(AstMedia { body, .. })
| AstStmt::AtRootRule(AstAtRootRule { children: body, .. })
| AstStmt::UnknownAtRule(AstUnknownAtRule {
children: Some(body),
..
})
| AstStmt::Supports(AstSupportsRule { children: body, .. }) => {
for stmt in body {
visit_stmt(file, stmt, graph, visitor);
}
}
AstStmt::ImportRule(AstImportRule { imports }) => {
for import in imports {
match import {
// plain css imports are those importing .css files that do
// not exist locally or `url(..)` imports. in some cases it
// may be desirable to track these as well
AstImport::Plain(_) => continue,
AstImport::Sass(AstSassImport { url, .. }) => {
import_url(file, &url.into(), graph, visitor)
}
}
}
}
AstStmt::Use(AstUseRule { url, .. }) | AstStmt::Forward(AstForwardRule { url, .. }) => {
import_url(file, url, graph, visitor);
}
_ => {}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment