use anyhow::Context;
use anyhow::Result;
use quick_xml::{events::Event, Reader};
use serde::Serialize;
use std::io::BufReader;
use std::io::Read;
use tokio::io::AsyncRead;
mod tags {
pub const ARTIFACT_ID: &[u8] = b"artifactId";
pub const GROUP_ID: &[u8] = b"groupId";
pub const VERSION: &[u8] = b"version";
pub const DEPENDENCIES: &[u8] = b"dependencies";
pub const PROJECT: &[u8] = b"project";
pub const DEPENDENCY: &[u8] = b"dependency";
pub const EXCLUSIONS: &[u8] = b"exclusions";
pub const EXCLUSION: &[u8] = b"exclusion";
pub const PACKAGING: &[u8] = b"packaging";
pub const SCOPE: &[u8] = b"scope";
}
#[derive(Clone, Default, Debug, PartialEq, Eq, Serialize)]
pub enum Scope {
#[default]
COMPILE,
TEST,
RUNTIME,
SYSTEM,
PROVIDED,
IMPORT,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Project {
artifact_id: String,
version: String,
group_id: String,
dependencies: Vec<Project>,
excludes: Vec<Exclusion>,
scope: Scope,
packaging: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Exclusion {
pub artifact_id: String,
pub group_id: String,
}
impl Exclusion {
pub fn new(group_id: &str, artifact_id: &str) -> Self {
Exclusion {
artifact_id: artifact_id.to_string(),
group_id: group_id.to_string(),
}
}
pub fn qualified_name(&self) -> String {
format!("{}:{}", self.group_id, self.artifact_id)
}
}
impl Default for Project {
fn default() -> Self {
Project {
artifact_id: "my_app".to_string(),
version: "1.0.0".to_string(),
group_id: "com.my_organization.name".to_string(),
dependencies: vec![],
excludes: vec![],
scope: Scope::COMPILE,
packaging: String::from("jar"),
}
}
}
impl Project {
pub fn new(group_id: &str, artifact_id: &str, version: &str) -> Self {
Project {
group_id: String::from(group_id),
artifact_id: String::from(artifact_id),
version: String::from(version),
..Default::default()
}
}
pub fn get_artifact_id(&self) -> String {
self.artifact_id.clone()
}
pub fn get_version(&self) -> String {
self.version.clone()
}
pub fn get_group_id(&self) -> String {
self.group_id.clone()
}
pub fn add_dependency(&mut self, dep: Project) {
self.dependencies.push(dep);
}
pub fn get_dependencies(&self) -> &Vec<Project> {
&self.dependencies
}
pub fn get_dependencies_mut(&mut self) -> &mut Vec<Project> {
&mut self.dependencies
}
pub fn qualified_name(&self) -> String {
format!("{}:{}:{}", self.group_id, self.artifact_id, self.version)
}
pub fn get_excludes(&self) -> &Vec<Exclusion> {
&self.excludes
}
pub fn add_exclusion(&mut self, exclude: Exclusion) {
self.excludes.push(exclude);
}
pub fn get_scope(&self) -> Scope {
self.scope.clone()
}
pub fn get_packaging(&self) -> String {
self.packaging.clone()
}
pub fn set_packaging(&mut self, packaging: String) {
self.packaging = packaging;
}
}
#[derive(Clone, Debug)]
enum ParserState {
Project,
ReadArtifactId,
ReadGroupId,
ReadVersion,
Dependencies(DependencyState),
ReadPackaging,
}
#[derive(Clone, Debug)]
enum DependencyState {
Dependencies,
Dependency,
ReadArtifactId,
ReadGroupId,
ReadVersion,
Exclusions(ExclusionsState),
ReadScope,
}
#[derive(Clone, Debug)]
enum ExclusionsState {
Exclusions,
Exclusion(Exclusion),
ReadArtifactId(Exclusion),
ReadGroupId(Exclusion),
}
struct Parser {
state: ParserState,
project: Project,
current_dependency: Option<Project>,
}
impl Parser {
pub fn new(project: Project) -> Self {
Parser {
state: ParserState::Project,
project,
current_dependency: None,
}
}
fn parse_deps(&mut self, event: Event, state: DependencyState) -> Result<DependencyState> {
let new_state = match state {
DependencyState::Dependencies => match event {
Event::Start(tag) => match tag.local_name().into_inner() {
tags::DEPENDENCY => {
self.current_dependency = Some(Project::default());
DependencyState::Dependency
}
_ => DependencyState::Dependencies,
},
_ => DependencyState::Dependencies,
},
DependencyState::Dependency => match event {
Event::Start(tag) => match tag.local_name().into_inner() {
tags::ARTIFACT_ID => DependencyState::ReadArtifactId,
tags::GROUP_ID => DependencyState::ReadGroupId,
tags::VERSION => DependencyState::ReadVersion,
tags::EXCLUSIONS => DependencyState::Exclusions(ExclusionsState::Exclusions),
tags::SCOPE => DependencyState::ReadScope,
_ => DependencyState::Dependency,
},
Event::End(end) if end.local_name().into_inner() == tags::DEPENDENCY => {
if let Some(dep) = self.current_dependency.clone() {
self.project.add_dependency(dep);
self.current_dependency = None;
}
DependencyState::Dependencies
}
_ => DependencyState::Dependency,
},
DependencyState::ReadArtifactId => match event {
Event::End(end) if end.local_name().into_inner() == tags::ARTIFACT_ID => {
DependencyState::Dependency
}
Event::Text(e) => {
if let Some(dep) = &mut self.current_dependency {
dep.artifact_id = e.unescape()?.to_string();
}
DependencyState::ReadArtifactId
}
_ => DependencyState::ReadArtifactId,
},
DependencyState::ReadGroupId => match event {
Event::End(end) if end.local_name().into_inner() == tags::GROUP_ID => {
DependencyState::Dependency
}
Event::Text(e) => {
if let Some(dep) = &mut self.current_dependency {
dep.group_id = e.unescape()?.to_string();
}
DependencyState::ReadGroupId
}
_ => DependencyState::ReadGroupId,
},
DependencyState::ReadVersion => match event {
Event::End(end) if end.local_name().into_inner() == tags::VERSION => {
DependencyState::Dependency
}
Event::Text(e) => {
if let Some(dep) = &mut self.current_dependency {
dep.version = e.unescape()?.to_string();
}
DependencyState::ReadVersion
}
_ => DependencyState::ReadVersion,
},
DependencyState::ReadScope => match event {
Event::End(end) if end.local_name().into_inner() == tags::SCOPE => {
DependencyState::Dependency
}
Event::Text(e) => {
if let Some(dep) = &mut self.current_dependency {
let scope = e.unescape()?;
dep.scope = match scope.to_string().as_str() {
"compile" => Scope::COMPILE,
"test" => Scope::TEST,
"provided" => Scope::PROVIDED,
"import" => Scope::IMPORT,
"system" => Scope::SYSTEM,
"runtime" => Scope::RUNTIME,
_ => Scope::COMPILE,
}
}
DependencyState::ReadScope
}
_ => DependencyState::ReadScope,
},
DependencyState::Exclusions(exclu_state) => match event {
Event::End(end) if end.local_name().into_inner() == tags::EXCLUSIONS => {
DependencyState::Dependency
}
event => DependencyState::Exclusions(self.parse_exclusions(event, exclu_state)?),
},
};
Ok(new_state)
}
fn parse_exclusions(
&mut self,
event: Event,
state: ExclusionsState,
) -> Result<ExclusionsState> {
let new_state = match state {
ExclusionsState::Exclusions => match event {
Event::Start(start) => match start.local_name().into_inner() {
tags::EXCLUSION => ExclusionsState::Exclusion(Exclusion::default()),
_ => ExclusionsState::Exclusions,
},
_ => ExclusionsState::Exclusions,
},
ExclusionsState::Exclusion(exclusion) => match event {
Event::End(end) if end.local_name().into_inner() == tags::EXCLUSION => {
if let Some(mut dependency) = self.current_dependency.clone() {
dependency.add_exclusion(exclusion);
self.current_dependency = Some(dependency);
}
ExclusionsState::Exclusions
}
Event::Start(start) => match start.local_name().into_inner() {
tags::ARTIFACT_ID => ExclusionsState::ReadArtifactId(exclusion),
tags::GROUP_ID => ExclusionsState::ReadGroupId(exclusion),
_ => ExclusionsState::Exclusion(exclusion),
},
_ => ExclusionsState::Exclusion(exclusion),
},
ExclusionsState::ReadArtifactId(mut exclusion) => match event {
Event::End(end) if end.local_name().into_inner() == tags::ARTIFACT_ID => {
ExclusionsState::Exclusion(exclusion)
}
Event::Text(e) => {
let artifact_id = e.unescape()?.to_string();
exclusion.artifact_id = artifact_id;
ExclusionsState::ReadArtifactId(exclusion)
}
_ => ExclusionsState::ReadArtifactId(exclusion),
},
ExclusionsState::ReadGroupId(mut exclusion) => match event {
Event::End(end) if end.local_name().into_inner() == tags::GROUP_ID => {
ExclusionsState::Exclusion(exclusion)
}
Event::Text(e) => {
let group_id = e.unescape()?.to_string();
exclusion.group_id = group_id;
ExclusionsState::ReadGroupId(exclusion)
}
_ => ExclusionsState::ReadGroupId(exclusion),
},
};
Ok(new_state)
}
pub fn process(&mut self, event: Event) -> Result<()> {
self.state = match self.state.clone() {
ParserState::Project => match event {
Event::Start(tag) => match tag.local_name().into_inner() {
tags::PROJECT => ParserState::Project,
tags::DEPENDENCIES => ParserState::Dependencies(DependencyState::Dependencies),
tags::ARTIFACT_ID => ParserState::ReadArtifactId,
tags::GROUP_ID => ParserState::ReadGroupId,
tags::VERSION => ParserState::ReadVersion,
tags::PACKAGING => ParserState::ReadPackaging,
_ => ParserState::Project,
},
_ => ParserState::Project,
},
ParserState::ReadArtifactId => match event {
Event::End(end) if end.local_name().into_inner() == tags::ARTIFACT_ID => {
ParserState::Project
}
Event::Text(e) => {
self.project.artifact_id = e.unescape()?.to_string();
ParserState::ReadArtifactId
}
_ => ParserState::ReadArtifactId,
},
ParserState::ReadGroupId => match event {
Event::End(end) if end.local_name().into_inner() == tags::GROUP_ID => {
ParserState::Project
}
Event::Text(e) => {
self.project.group_id = e.unescape()?.to_string();
ParserState::ReadGroupId
}
_ => ParserState::ReadGroupId,
},
ParserState::ReadVersion => match event {
Event::End(end) if end.local_name().into_inner() == tags::VERSION => {
ParserState::Project
}
Event::Text(e) => {
self.project.version = e.unescape()?.to_string();
ParserState::ReadVersion
}
_ => ParserState::ReadVersion,
},
ParserState::ReadPackaging => match event {
Event::End(end) if end.local_name().into_inner() == tags::PACKAGING => {
ParserState::Project
}
Event::Text(e) => {
self.project.packaging = e.unescape()?.to_string();
ParserState::ReadPackaging
}
_ => ParserState::ReadPackaging,
},
ParserState::Dependencies(dep_state) => match event {
Event::End(end) if end.local_name().into_inner() == tags::DEPENDENCIES => {
ParserState::Project
}
event => ParserState::Dependencies(self.parse_deps(event, dep_state)?),
},
};
Ok(())
}
}
pub fn parse_pom<R>(r: BufReader<R>, project: Project) -> anyhow::Result<Project>
where
R: Read,
{
let mut reader = Reader::from_reader(r);
const BUFFER_SIZE: usize = 4096;
let mut buf = Vec::with_capacity(BUFFER_SIZE);
let mut parser = Parser::new(project);
loop {
match reader
.read_event_into(&mut buf)
.context("Reading xml events")?
{
Event::Eof => {
break;
}
ev => parser.process(ev).context("Processing xml events")?,
}
buf.clear()
}
Ok(parser.project)
}
pub async fn parse_pom_async<R: AsyncRead + Unpin>(
r: tokio::io::BufReader<R>,
project: Project,
) -> anyhow::Result<Project> {
let mut reader = Reader::from_reader(r);
const BUFFER_SIZE: usize = 4096;
let mut buf = Vec::with_capacity(BUFFER_SIZE);
let mut parser = Parser::new(project);
loop {
match reader
.read_event_into_async(&mut buf)
.await
.context("Reading xml events")?
{
Event::Eof => {
break;
}
ev => parser.process(ev).context("Processing xml events")?,
}
buf.clear()
}
Ok(parser.project)
}