Skip to content

Commit

Permalink
feat(issues): add initial support for issues
Browse files Browse the repository at this point in the history
  • Loading branch information
fiji-flo committed Oct 1, 2024
1 parent 05d39f2 commit df326d5
Show file tree
Hide file tree
Showing 25 changed files with 421 additions and 108 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,24 @@ reqwest = { version = "0.12", default-features = false, features = [


[dependencies]
rari-doc.workspace = true
rari-tools.workspace = true
rari-deps.workspace = true
rari-types.workspace = true

serde.workspace = true
serde_json.workspace = true
tracing.workspace = true
anyhow.workspace = true

self_update = { version = "0.41", default-features = false, features = [
"rustls",
"compression-flate2",
"compression-zip-deflate",
] }
anyhow = "1"
clap = { version = "4.5.1", features = ["derive"] }
clap-verbosity-flag = "2"
rari-doc = { path = "crates/rari-doc" }
rari-tools = { path = "crates/rari-tools" }
rari-deps = { path = "crates/rari-deps" }
rari-types = { path = "crates/rari-types" }
serde_json = { version = "1", features = ["preserve_order"] }
tiny_http = "0.12"
tracing = "0.1"
tracing-subscriber = "0.3"
tracing-log = "0.2"
tabwriter = "1"
174 changes: 174 additions & 0 deletions crates/rari-cli/issues.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use std::collections::BTreeMap;
use std::fmt;
use std::sync::{Arc, Mutex};

use serde::Serialize;
use tracing::field::{Field, Visit};
use tracing::span::{Attributes, Id};
use tracing::{Event, Subscriber};
use tracing_subscriber::registry::LookupSpan;
use tracing_subscriber::Layer;

#[derive(Debug, Default)]
pub struct Issue {
pub fields: Vec<(&'static str, String)>,
pub spans: Vec<(&'static str, String)>,
}

#[derive(Debug, Default)]
pub struct IssueEntries {
entries: Vec<(&'static str, String)>,
}

#[derive(Clone, Debug, Serialize)]
pub struct Issues<'a> {
pub templ: BTreeMap<&'a str, Vec<TemplIssue<'a>>>,
pub other: BTreeMap<&'a str, Vec<TemplIssue<'a>>>,
pub no_pos: BTreeMap<&'a str, Vec<TemplIssue<'a>>>,
}

#[derive(Clone, Debug, Serialize)]
pub struct TemplIssue<'a> {
pub source: &'a str,
pub file: &'a str,
pub slug: &'a str,
pub locale: &'a str,
pub line: &'a str,
pub col: &'a str,
pub tail: Vec<(&'static str, &'a str)>,
}

static UNKNOWN: &str = "unknown";
static DEFAULT_TEMPL_ISSUE: TemplIssue<'static> = TemplIssue {
source: UNKNOWN,
file: UNKNOWN,
slug: UNKNOWN,
locale: UNKNOWN,
line: UNKNOWN,
col: UNKNOWN,
tail: vec![],
};

impl<'a> From<&'a Issue> for TemplIssue<'a> {
fn from(value: &'a Issue) -> Self {
let mut tissue = DEFAULT_TEMPL_ISSUE.clone();
for (key, value) in value.spans.iter().chain(value.fields.iter()) {
match *key {
"file" => {
tissue.file = value.as_str();
}
"slug" => {
tissue.slug = value.as_str();
}
"locale" => {
tissue.locale = value.as_str();
}
"line" => tissue.line = value.as_str(),
"col" => tissue.col = value.as_str(),
"source" => {
tissue.source = value.as_str();
}
"message" => {}
_ => tissue.tail.push((key, value.as_str())),
}
}
tissue
}
}

pub fn issues_by(issues: &[Issue]) -> Issues {
let mut templ: BTreeMap<&str, Vec<TemplIssue>> = BTreeMap::new();
let mut other: BTreeMap<&str, Vec<TemplIssue>> = BTreeMap::new();
let mut no_pos: BTreeMap<&str, Vec<TemplIssue>> = BTreeMap::new();
for issue in issues.iter().map(TemplIssue::from) {
if let Some(templ_name) =
issue
.tail
.iter()
.find_map(|(key, value)| if *key == "templ" { Some(value) } else { None })
{
templ.entry(templ_name).or_default().push(issue);
} else if issue.line != UNKNOWN {
other.entry(issue.source).or_default().push(issue)
} else {
no_pos.entry(issue.source).or_default().push(issue);
}
}
Issues {
templ,
other,
no_pos,
}
}

#[derive(Clone)]
pub struct InMemoryLayer {
events: Arc<Mutex<Vec<Issue>>>,
}

impl InMemoryLayer {
pub fn new() -> Self {
InMemoryLayer {
events: Arc::new(Mutex::new(Vec::new())),
}
}

pub fn get_events(&self) -> Arc<Mutex<Vec<Issue>>> {
Arc::clone(&self.events)
}
}

impl Visit for IssueEntries {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.entries.push((field.name(), format!("{value:?}")));
}
fn record_str(&mut self, field: &Field, value: &str) {
self.entries.push((field.name(), value.to_string()));
}
}
impl Visit for Issue {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
self.fields.push((field.name(), format!("{value:?}")));
}
fn record_str(&mut self, field: &Field, value: &str) {
self.fields.push((field.name(), value.to_string()));
}
}
impl<S> Layer<S> for InMemoryLayer
where
S: Subscriber + for<'a> LookupSpan<'a>,
{
fn on_new_span(
&self,
attrs: &Attributes<'_>,
id: &Id,
ctx: tracing_subscriber::layer::Context<S>,
) {
let span = ctx.span(id).expect("Span not found, this is a bug");
let mut extensions = span.extensions_mut();

if extensions.get_mut::<IssueEntries>().is_none() {
let mut fields = IssueEntries::default();
attrs.values().record(&mut fields);
extensions.insert(fields);
}
}
fn on_event(&self, event: &Event, ctx: tracing_subscriber::layer::Context<S>) {
let mut issue = Issue {
fields: vec![],
spans: vec![],
};
let span = ctx.event_span(event);
let scope = span.into_iter().flat_map(|span| span.scope());
for span in scope {
let ext = span.extensions();
if let Some(entries) = ext.get::<IssueEntries>() {
issue.spans.extend(entries.entries.iter().rev().cloned());
}
}

event.record(&mut issue);
let mut events = self.events.lock().unwrap();
events.push(issue);
}
}
48 changes: 43 additions & 5 deletions crates/rari-cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::thread::spawn;

use anyhow::{anyhow, Error};
use clap::{Args, Parser, Subcommand};
use issues::{issues_by, InMemoryLayer};
use rari_doc::build::{
build_blog_pages, build_contributor_spotlight_pages, build_curriculum_pages, build_docs,
build_generic_pages, build_spas,
Expand All @@ -23,11 +24,13 @@ use rari_types::globals::{build_out_root, content_root, content_translated_root,
use rari_types::settings::Settings;
use self_update::cargo_crate_version;
use tabwriter::TabWriter;
use tracing::Level;
use tracing_log::AsTrace;
use tracing_subscriber::filter;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{filter, Layer};

mod issues;
mod serve;

#[derive(Parser)]
Expand Down Expand Up @@ -92,6 +95,8 @@ struct BuildArgs {
skip_sitemap: bool,
#[arg(long)]
templ_stats: bool,
#[arg(long)]
issues: Option<PathBuf>,
}

enum Cache {
Expand All @@ -110,14 +115,20 @@ fn main() -> Result<(), Error> {
rari_deps::web_ext_examples::update_web_ext_examples(rari_types::globals::data_dir())?;
}

let filter = filter::Targets::new()
.with_target("rari_builder", cli.verbose.log_level_filter().as_trace())
let fmt_filter = filter::Targets::new()
.with_target("rari_doc", cli.verbose.log_level_filter().as_trace())
.with_target("rari", cli.verbose.log_level_filter().as_trace());

let memory_filter = filter::Targets::new().with_target("rari_doc", Level::WARN);

let memory_layer = InMemoryLayer::new();
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer().without_time())
.with(filter)
.with(
tracing_subscriber::fmt::layer()
.without_time()
.with_filter(fmt_filter),
)
.with(memory_layer.clone().with_filter(memory_filter))
.init();

match cli.command {
Expand Down Expand Up @@ -249,6 +260,33 @@ fn main() -> Result<(), Error> {
.join()
.expect("unable to close templ recorder");
}

if let Some(issues_path) = args.issues {
let events = memory_layer.get_events();
let events = events.lock().unwrap();

let issues = issues_by(&events);

let mut tw = TabWriter::new(vec![]);
writeln!(&mut tw, "--- templ issues ---\t").expect("unable to write");
for (templ, templ_issues) in &issues.templ {
writeln!(&mut tw, "{}\t{:5}", templ, templ_issues.len())
.expect("unable to write");
}
writeln!(&mut tw, "--- other issues ---\t").expect("unable to write");
for (source, other_issues) in &issues.other {
writeln!(&mut tw, "{}\t{:5}", source, other_issues.len())
.expect("unable to write");
}
writeln!(&mut tw, "--- other issues w/o pos ---\t").expect("unable to write");
for (source, no_pos) in &issues.no_pos {
writeln!(&mut tw, "{}\t{:5}", source, no_pos.len()).expect("unable to write");
}
print!("{}", String::from_utf8_lossy(&tw.into_inner().unwrap()));
let file = File::create(issues_path).unwrap();
let mut buffed = BufWriter::new(file);
serde_json::to_writer_pretty(&mut buffed, &issues).unwrap();
}
}
Commands::Serve(args) => {
let mut settings = Settings::new()?;
Expand Down
11 changes: 8 additions & 3 deletions crates/rari-doc/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ use crate::pages::types::spa::SPA;
use crate::resolve::url_to_folder_path;

pub fn build_single_page(page: &Page) {
let slug = &page.slug();
let locale = page.locale();
let span = span!(Level::ERROR, "page", "{}:{}", locale, slug);
let file = page.full_path().to_string_lossy();
let span = span!(
Level::ERROR,
"page",
locale = page.locale().as_url_str(),
slug = page.slug(),
file = file.as_ref()
);
let _enter = span.enter();
let built_page = page.build();
match built_page {
Expand Down
6 changes: 5 additions & 1 deletion crates/rari-doc/src/html/links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,11 @@ pub fn render_link_via_page(
}
Err(e) => {
if !Page::ignore(url) {
warn!("Link via page not found for {url}: {e}")
warn!(
source = "link-check",
url = url,
"Link via page not found: {e}"
)
}
}
}
Expand Down
Loading

0 comments on commit df326d5

Please sign in to comment.