Skip to content

Commit

Permalink
feat: add config file support
Browse files Browse the repository at this point in the history
  • Loading branch information
camshaft committed Nov 5, 2024
1 parent 517a0e7 commit d2bb2c4
Show file tree
Hide file tree
Showing 39 changed files with 1,279 additions and 715 deletions.
4 changes: 4 additions & 0 deletions duvet.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
version = "1"

[source]
pattern = "src/**/*.rs"
2 changes: 1 addition & 1 deletion duvet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ slug = { version = "0.1" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
toml = "0.5"
toml = "0.8"
triple_accel = "0.4"
url = "2"
v_jsonescape = "0.7"
Expand Down
84 changes: 62 additions & 22 deletions duvet/src/annotation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ use crate::{
};
use anyhow::anyhow;
use core::{fmt, ops::Range, str::FromStr};
use serde::Serialize;
use duvet_core::{diagnostic::IntoDiagnostic, path::Path, query, Result};
use serde::{Deserialize, Serialize};
use std::{
collections::{BTreeSet, HashMap},
path::{Path, PathBuf},
sync::Arc,
};

pub type AnnotationSet = BTreeSet<Annotation>;
Expand Down Expand Up @@ -44,19 +45,52 @@ impl AnnotationSetExt for AnnotationSet {
}
}

#[query]
pub async fn query() -> Result<Arc<AnnotationSet>> {
let mut errors = vec![];

// TODO use try_join after fixing `duvet_core::Query` concurrency issues
// let (sources, requirements) =
// try_join!(crate::manifest::sources(), crate::manifest::requirements())?;
let sources = crate::manifest::sources().await?;
let requirements = crate::manifest::requirements().await?;

let mut tasks = tokio::task::JoinSet::new();

for source in sources.iter().chain(requirements.iter()) {
let source = source.clone();
tasks.spawn(async move { source.annotations().await });
}

let mut annotations = AnnotationSet::default();
while let Some(res) = tasks.join_next().await {
match res.into_diagnostic() {
Ok((local_annotations, local_errors)) => {
annotations.extend(local_annotations);
errors.extend(local_errors.iter().cloned());
}
Err(err) => {
errors.push(err);
}
}
}

if !errors.is_empty() {
Err(errors.into())
} else {
Ok(Arc::new(annotations))
}
}

#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct Annotation {
pub source: PathBuf,
pub source: Path,
pub anno_line: u32,
pub anno_column: u32,
pub item_line: u32,
pub item_column: u32,
pub path: String,
pub anno: AnnotationType,
pub target: String,
pub quote: String,
pub comment: String,
pub manifest_dir: PathBuf,
pub level: AnnotationLevel,
pub format: Format,
pub tracking_issue: String,
Expand All @@ -65,6 +99,11 @@ pub struct Annotation {
}

impl Annotation {
pub fn relative_source(&self) -> &std::path::Path {
let cwd = duvet_core::env::current_dir().unwrap();
self.source.strip_prefix(&cwd).unwrap_or(&self.source)
}

pub fn target(&self) -> Result<Target, Error> {
Target::from_annotation(self)
}
Expand All @@ -82,7 +121,7 @@ impl Annotation {
true => target_path.into(),
// A file path needs to match
false => String::from(
self.resolve_file(Path::new(target_path))
self.resolve_file(target_path.into())
.unwrap()
.to_str()
.unwrap(),
Expand All @@ -102,20 +141,18 @@ impl Annotation {
})
}

pub fn resolve_file(&self, file: &Path) -> Result<PathBuf, Error> {
pub fn resolve_file(&self, file: Path) -> Result<Path, Error> {
// If we have the right path, just return it
if file.is_file() {
return Ok(file.to_path_buf());
return Ok(file);
}

let mut manifest_dir = self.manifest_dir.clone();
loop {
if manifest_dir.join(file).is_file() {
return Ok(manifest_dir.join(file));
}
let mut manifest_dir = self.source.as_ref();
while let Some(dir) = manifest_dir.parent() {
manifest_dir = dir;

if !manifest_dir.pop() {
break;
if manifest_dir.join(&file).is_file() {
return Ok(manifest_dir.join(file).into());
}
}

Expand All @@ -129,26 +166,26 @@ impl Annotation {

#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum AnnotationType {
Implementation,
Spec,
Test,
Citation,
Exception,
Todo,
Implication,
}

impl Default for AnnotationType {
fn default() -> Self {
Self::Citation
Self::Implementation
}
}

impl fmt::Display for AnnotationType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match self {
Self::Implementation => "IMPLEMENTATION",
Self::Spec => "SPEC",
Self::Test => "TEST",
Self::Citation => "CITATION",
Self::Exception => "EXCEPTION",
Self::Todo => "TODO",
Self::Implication => "IMPLICATION",
Expand All @@ -163,7 +200,9 @@ impl FromStr for AnnotationType {
match v {
"SPEC" | "spec" => Ok(Self::Spec),
"TEST" | "test" => Ok(Self::Test),
"CITATION" | "citation" => Ok(Self::Citation),
"CITATION" | "citation" | "IMPLEMENTATION" | "implementation" => {
Ok(Self::Implementation)
}
"EXCEPTION" | "exception" => Ok(Self::Exception),
"TODO" | "todo" => Ok(Self::Todo),
"IMPLICATION" | "implication" => Ok(Self::Implication),
Expand All @@ -173,7 +212,8 @@ impl FromStr for AnnotationType {
}

// The order is in terms of priority from least to greatest
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize)]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Deserialize, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum AnnotationLevel {
Auto,
May,
Expand Down
56 changes: 56 additions & 0 deletions duvet/src/arguments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::{
extract,
manifest::{Requirement, Source},
report,
};
use clap::Parser;
use duvet_core::{env, path::Path, query, Result};
use std::sync::Arc;

#[derive(Debug, Parser)]
pub struct Arguments {
#[clap(short, long, global = true)]
pub config: Option<Path>,

#[command(subcommand)]
pub command: Command,
}

#[derive(Debug, Parser)]
#[allow(clippy::large_enum_variant)]
pub enum Command {
Extract(extract::Extract),
Report(report::Report),
}

impl Arguments {
pub async fn exec(&self) -> Result<()> {
match &self.command {
Command::Extract(args) => args.exec().await,
Command::Report(args) => args.exec().await,
}
}

pub fn load_sources(&self, sources: &mut Vec<Source>) {
match &self.command {
Command::Extract(_) => (),
Command::Report(args) => args.load_sources(sources),
}
}

pub fn load_requirements(&self, requirements: &mut Vec<Requirement>) {
match &self.command {
Command::Extract(_) => (),
Command::Report(args) => args.load_requirements(requirements),
}
}
}

#[query]
pub async fn get() -> Arc<Arguments> {
let args = env::args();
Arc::new(Arguments::parse_from(args.iter()))
}
59 changes: 59 additions & 0 deletions duvet/src/comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::annotation::{AnnotationSet, AnnotationType};
use anyhow::anyhow;
use duvet_core::{diagnostic::Error, file::SourceFile};
use std::sync::Arc;

pub mod parser;
pub mod tokenizer;

#[cfg(test)]
mod tests;

pub fn extract(
file: &SourceFile,
pattern: &Pattern,
default_type: AnnotationType,
) -> (AnnotationSet, Vec<Error>) {
let tokens = tokenizer::tokens(file, pattern);
let mut parser = parser::parse(tokens, default_type);

let annotations = (&mut parser).collect();
let errors = parser.errors();

(annotations, errors)
}

#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub struct Pattern {
pub meta: Arc<str>,
pub content: Arc<str>,
}

impl Default for Pattern {
fn default() -> Self {
Self {
meta: "//=".into(),
content: "//#".into(),
}
}
}

impl Pattern {
pub fn from_arg(arg: &str) -> Result<Self, anyhow::Error> {
let mut parts = arg.split(',').filter(|p| !p.is_empty());
let meta = parts.next().expect("should have at least one pattern");
if meta.is_empty() {
return Err(anyhow!("compliance pattern cannot be empty"));
}

let content = parts.next().unwrap();

let meta = meta.into();
let content = content.into();

Ok(Self { meta, content })
}
}
Loading

0 comments on commit d2bb2c4

Please sign in to comment.