Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add config file support #128

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading