use crate::changes::{CommitInfo, FileChange, FileChangeType, PackageChangeStats};
use crate::types::{Version, VersionBump};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use sublime_standard_tools::monorepo::WorkspacePackage;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PackageChanges {
#[serde(skip, default = "default_workspace_package")]
pub package_info: WorkspacePackage,
pub package_name: String,
pub package_version: String,
pub package_location: PathBuf,
pub current_version: Option<Version>,
pub next_version: Option<Version>,
pub bump_type: Option<VersionBump>,
pub files: Vec<FileChange>,
pub commits: Vec<CommitInfo>,
pub has_changes: bool,
pub stats: PackageChangeStats,
}
impl PackageChanges {
#[must_use]
pub fn new(package_info: WorkspacePackage) -> Self {
let package_name = package_info.name.clone();
let package_version = package_info.version.clone();
let package_location = package_info.location.clone();
Self {
package_info,
package_name,
package_version,
package_location,
current_version: None,
next_version: None,
bump_type: None,
files: Vec::new(),
commits: Vec::new(),
has_changes: false,
stats: PackageChangeStats::new(),
}
}
#[must_use]
pub fn package_name(&self) -> &str {
&self.package_info.name
}
#[must_use]
pub fn package_location(&self) -> &Path {
&self.package_info.location
}
pub fn add_file(&mut self, file_change: FileChange) {
self.stats.files_changed += 1;
match file_change.change_type {
FileChangeType::Added | FileChangeType::Untracked | FileChangeType::Copied => {
self.stats.files_added += 1;
}
FileChangeType::Modified | FileChangeType::Renamed => {
self.stats.files_modified += 1;
}
FileChangeType::Deleted => {
self.stats.files_deleted += 1;
}
}
if let Some(added) = file_change.lines_added {
self.stats.lines_added += added;
}
if let Some(deleted) = file_change.lines_deleted {
self.stats.lines_deleted += deleted;
}
self.files.push(file_change);
self.has_changes = true;
}
pub fn add_commit(&mut self, commit: CommitInfo) {
self.commits.push(commit);
self.stats.commits = self.commits.len();
}
#[must_use]
pub fn files_by_type(&self, change_type: FileChangeType) -> Vec<&FileChange> {
self.files.iter().filter(|f| f.change_type == change_type).collect()
}
#[must_use]
pub fn package_json_modified(&self) -> bool {
self.files.iter().any(|f| f.is_package_json())
}
#[must_use]
pub fn files_by_directory(&self) -> HashMap<PathBuf, Vec<&FileChange>> {
let mut result: HashMap<PathBuf, Vec<&FileChange>> = HashMap::new();
for file in &self.files {
let dir = file.package_relative_dir().unwrap_or_else(|| Path::new("")).to_path_buf();
result.entry(dir).or_default().push(file);
}
result
}
#[must_use]
pub fn files_by_extension(&self) -> HashMap<String, Vec<&FileChange>> {
let mut result: HashMap<String, Vec<&FileChange>> = HashMap::new();
for file in &self.files {
if let Some(ext) = file.extension() {
result.entry(ext.to_string()).or_default().push(file);
} else {
result.entry("(no extension)".to_string()).or_default().push(file);
}
}
result
}
#[must_use]
pub fn added_files(&self) -> Vec<&FileChange> {
self.files.iter().filter(|f| f.is_addition()).collect()
}
#[must_use]
pub fn modified_files(&self) -> Vec<&FileChange> {
self.files.iter().filter(|f| f.is_modification()).collect()
}
#[must_use]
pub fn deleted_files(&self) -> Vec<&FileChange> {
self.files.iter().filter(|f| f.is_deletion()).collect()
}
}
fn default_workspace_package() -> WorkspacePackage {
WorkspacePackage {
name: String::new(),
version: String::new(),
location: PathBuf::new(),
absolute_path: PathBuf::new(),
workspace_dependencies: Vec::new(),
workspace_dev_dependencies: Vec::new(),
}
}