use std::fmt;
use std::path::{Path, PathBuf};
use smart_default::SmartDefault;
use super::VersionedSource;
use crate::Results;
mod helm;
mod terraform;
#[derive(Debug, Clone)]
pub struct Location {
path: PathBuf,
loc: (usize, usize),
span: Option<(usize, usize)>,
}
impl Location {
pub fn new(path: impl Into<PathBuf>, line: usize, col: usize) -> Self {
Self {
path: path.into(),
loc: (line, col),
span: None,
}
}
pub fn with_span(self, start: usize, end: usize) -> Self {
Self {
span: Some((start, end)),
..self
}
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn loc(&self) -> (usize, usize) {
self.loc
}
pub fn line(&self) -> usize {
self.loc.0
}
pub fn col(&self) -> usize {
self.loc.1
}
pub fn span(&self) -> Option<(usize, usize)> {
self.span
}
}
impl PartialEq for Location {
fn eq(&self, other: &Self) -> bool {
self.path == other.path && self.loc == other.loc
}
}
impl Eq for Location {}
impl PartialOrd for Location {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Location {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.path.cmp(&other.path).then(self.loc.cmp(&other.loc))
}
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{file}:{line}",
file = self.path.display(),
line = self.line(),
)
}
}
#[derive(Debug, Clone, SmartDefault)]
pub struct ParseOptions {
#[default = false]
pub recursive: bool,
#[default = false]
pub hidden: bool,
}
pub async fn parse_cwd(options: &ParseOptions) -> Results<VersionedSource> {
let cwd = std::env::current_dir().expect("Should be run in a valid directory");
parse_dir(&cwd, options).await
}
pub async fn parse_dir(path: impl AsRef<Path>, options: &ParseOptions) -> Results<VersionedSource> {
use core::iter;
use itertools::Itertools;
iter::empty()
.chain(helm::parse_dir(path.as_ref(), options).await)
.chain(terraform::parse_dir(path.as_ref(), options).await)
.partition_result()
.into()
}
pub fn print_parsing_errors<E>(errors: Vec<E>)
where
E: miette::Diagnostic + Sync + Send + 'static,
{
if !errors.is_empty() {
println!("Following errors are ignored in the next steps.");
for err in errors {
println!("{:?}", miette::Report::new(err));
}
}
}
#[cfg(test)]
mod tests {
use crate::sources::{
VersionedSource, data,
parse::{self, Location},
};
const TEST_DIRECTORY: &str = "tests/sources/parse";
fn result_non_recursive() -> Vec<VersionedSource> {
vec![
VersionedSource::new(data::TerraformSource::new_provider("gitlabhq/gitlab"))
.with_current("~> 3.8")
.with_required("~> 3.8")
.with_location(Location::new("main.sample.test.tf", 4, 17).with_span(100, 108)),
VersionedSource::new(data::TerraformSource::new_module(
"gitlab.com/vigigloo/tf-modules/k8slimitednamespace",
))
.with_current("0.3.5")
.with_required("0.3.5")
.with_location(Location::new("main.sample.test.tf", 12, 18).with_span(271, 278)),
VersionedSource::new(data::TerraformSource::new_module(
"gitlab.com/vigigloo/tf-modules/k8slimitednamespace",
))
.with_location(Location::new("main.sample.test.tf", 17, 18)),
VersionedSource::new(data::TerraformSource::new_module(
"gitlab.com/vigigloo/tools-k8s/mattermost",
))
.with_current("0.1.0")
.with_required("0.1.0")
.with_location(Location::new("main.sample.test.tf", 21, 13).with_span(477, 484)),
VersionedSource::new(data::HelmSource::new(
"https://helm.nginx.com/stable",
"nginx-ingress",
))
.with_required("> 0.10.0 , < 0.18.0")
.with_current("0.17.1")
.with_location(Location::new("main.sample.test.tf", 29, 18).with_span(772, 780)),
VersionedSource::new(data::TerraformSource::new_module(
"gitlab.com/vigigloo/tf-modules/k8slimitednamespace",
))
.with_current("2.0.0")
.with_required("2.0.0")
.with_location(Location::new("main.sample.test.tf", 33, 18).with_span(951, 958)),
VersionedSource::new(data::HelmSource::new(
"https://helm.nginx.com/stable",
"nginx-ingress",
))
.with_current("0.17.0")
.with_required("0.17.0")
.with_location(Location::new("Chart.yaml", 6, 14).with_span(92, 100)),
]
}
fn result_non_recursive_with_hidden() -> Vec<VersionedSource> {
let mut vec = result_non_recursive();
vec.extend(vec![
VersionedSource::new(data::TerraformSource::new_provider("gitlabhq/gitlab"))
.with_current("> 3.0")
.with_required("> 3.0")
.with_location(Location::new(".hidden.sample.test.tf", 4, 17).with_span(100, 107)),
]);
vec
}
fn result_recursive() -> Vec<VersionedSource> {
let mut vec = result_non_recursive();
vec.extend(vec![
VersionedSource::new(data::TerraformSource::new_module(
"gitlab.com/vigigloo/tf-modules/k8slimitednamespace",
))
.with_current("0.1.0")
.with_required("0.1.0")
.with_location(Location::new("subdir/child.sample.test.tf", 2, 18).with_span(118, 125)),
]);
vec
}
#[tokio::test]
async fn parse_succeeds() {
use std::default::Default;
let (mut versions, errors): (Vec<_>, Vec<_>) =
parse::parse_dir(TEST_DIRECTORY, &super::ParseOptions::default())
.await
.split();
let mut wanted = result_non_recursive();
versions.sort_unstable_by_key(|source| source.source.loc.clone());
wanted.sort_unstable_by_key(|source| source.source.loc.clone());
pretty_assertions::assert_eq!(versions, wanted);
pretty_assertions::assert_eq!(errors.len(), 1);
}
#[tokio::test]
async fn parse_succeeds_with_hidden() {
let (mut versions, errors): (Vec<_>, Vec<_>) = (parse::parse_dir(
TEST_DIRECTORY,
&parse::ParseOptions {
recursive: false,
hidden: true,
},
))
.await
.split();
let mut wanted = result_non_recursive_with_hidden();
versions.sort_unstable_by_key(|source| source.source.loc.clone());
wanted.sort_unstable_by_key(|source| source.source.loc.clone());
pretty_assertions::assert_eq!(versions, wanted);
pretty_assertions::assert_eq!(errors.len(), 1);
}
#[tokio::test]
async fn parse_succeeds_recursive() {
let (mut versions, errors): (Vec<_>, Vec<_>) = parse::parse_dir(
TEST_DIRECTORY,
&parse::ParseOptions {
recursive: true,
hidden: false,
},
)
.await
.split();
let mut wanted = result_recursive();
versions.sort_unstable_by_key(|source| source.source.loc.clone());
wanted.sort_unstable_by_key(|source| source.source.loc.clone());
pretty_assertions::assert_eq!(versions, wanted);
pretty_assertions::assert_eq!(errors.len(), 1);
}
}