use std::fmt::Write as _;
use crate::links_format::sanitize_lino_value;
use super::file::{formalize_repository_file, RepositoryFileFormalization};
use super::{SummarizationConfig, SummarizationMode};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RepositoryEntry {
File { path: String, content: String },
Directory { path: String, children: Vec<Self> },
}
impl RepositoryEntry {
#[must_use]
pub fn file(path: impl Into<String>, content: impl Into<String>) -> Self {
Self::File {
path: path.into(),
content: content.into(),
}
}
#[must_use]
pub fn directory(path: impl Into<String>, children: Vec<Self>) -> Self {
Self::Directory {
path: path.into(),
children,
}
}
#[must_use]
pub fn path(&self) -> &str {
match self {
Self::File { path, .. } | Self::Directory { path, .. } => path,
}
}
}
#[derive(Debug, Clone)]
pub struct RepositoryDirectoryFormalization {
pub path: String,
pub direct_file_count: usize,
pub direct_directory_count: usize,
pub total_file_count: usize,
pub total_directory_count: usize,
pub total_line_count: usize,
pub total_byte_count: usize,
pub children: Vec<RepositoryResourceFormalization>,
}
#[derive(Debug, Clone)]
pub enum RepositoryResourceFormalization {
File(RepositoryFileFormalization),
Directory(RepositoryDirectoryFormalization),
}
impl RepositoryResourceFormalization {
#[must_use]
pub fn path(&self) -> &str {
match self {
Self::File(file) => &file.path,
Self::Directory(directory) => &directory.path,
}
}
#[must_use]
pub const fn is_directory(&self) -> bool {
matches!(self, Self::Directory(_))
}
#[must_use]
pub fn summary(&self, config: &SummarizationConfig) -> String {
match self {
Self::File(file) => file.summary(config),
Self::Directory(directory) => directory.summary(config),
}
}
#[must_use]
pub fn links_notation(&self) -> String {
match self {
Self::File(file) => file.links_notation(),
Self::Directory(directory) => directory.links_notation(),
}
}
}
impl RepositoryDirectoryFormalization {
#[must_use]
pub fn summary(&self, config: &SummarizationConfig) -> String {
let mut parts = vec![self.identity_sentence()];
if config.mode == SummarizationMode::Topic {
return parts.remove(0);
}
let child_config = config.clone().with_mode(config.mode.one_step_shorter());
let cap = child_summary_cap(config.mode);
let mut child_summaries = Vec::new();
for child in self.children.iter().take(cap) {
let summary = child.summary(&child_config);
if !summary.is_empty() {
child_summaries.push(summary);
}
}
let hidden = self.children.len().saturating_sub(cap);
if !child_summaries.is_empty() {
parts.push(format!("Contents: {}", child_summaries.join(" ")));
}
if hidden > 0 {
parts.push(format!(
"{hidden} more {} omitted for brevity.",
pluralize(hidden, "entry", "entries")
));
}
parts.join(" ")
}
fn identity_sentence(&self) -> String {
format!(
"{} is a repository directory with {} {} and {} {} ({} {} total across {} {}).",
self.path,
self.direct_file_count,
pluralize(self.direct_file_count, "file", "files"),
self.direct_directory_count,
pluralize(
self.direct_directory_count,
"subdirectory",
"subdirectories"
),
self.total_line_count,
pluralize(self.total_line_count, "line", "lines"),
self.total_file_count,
pluralize(self.total_file_count, "file", "files"),
)
}
#[must_use]
pub fn links_notation(&self) -> String {
let mut out = String::from("repository_directory\n");
push_field(&mut out, 1, "path", &self.path);
push_field(
&mut out,
1,
"direct_file_count",
&self.direct_file_count.to_string(),
);
push_field(
&mut out,
1,
"direct_directory_count",
&self.direct_directory_count.to_string(),
);
push_field(
&mut out,
1,
"total_file_count",
&self.total_file_count.to_string(),
);
push_field(
&mut out,
1,
"total_directory_count",
&self.total_directory_count.to_string(),
);
push_field(
&mut out,
1,
"total_line_count",
&self.total_line_count.to_string(),
);
push_field(
&mut out,
1,
"total_byte_count",
&self.total_byte_count.to_string(),
);
for child in &self.children {
match child {
RepositoryResourceFormalization::File(file) => {
push_field(&mut out, 1, "file", &file.path);
}
RepositoryResourceFormalization::Directory(directory) => {
push_field(&mut out, 1, "directory", &directory.path);
}
}
}
out.trim_end().to_owned()
}
}
#[must_use]
pub fn formalize_repository_resource(entry: &RepositoryEntry) -> RepositoryResourceFormalization {
match entry {
RepositoryEntry::File { path, content } => {
RepositoryResourceFormalization::File(formalize_repository_file(path, content))
}
RepositoryEntry::Directory { path, children } => {
RepositoryResourceFormalization::Directory(formalize_repository_directory(
path, children,
))
}
}
}
#[must_use]
pub fn formalize_repository_directory(
path: &str,
children: &[RepositoryEntry],
) -> RepositoryDirectoryFormalization {
let formalized_children: Vec<RepositoryResourceFormalization> =
children.iter().map(formalize_repository_resource).collect();
let mut direct_file_count = 0;
let mut direct_directory_count = 0;
let mut total_file_count = 0;
let mut total_directory_count = 0;
let mut total_line_count = 0;
let mut total_byte_count = 0;
for child in &formalized_children {
match child {
RepositoryResourceFormalization::File(file) => {
direct_file_count += 1;
total_file_count += 1;
total_line_count += file.line_count;
total_byte_count += file.byte_count;
}
RepositoryResourceFormalization::Directory(directory) => {
direct_directory_count += 1;
total_directory_count += 1 + directory.total_directory_count;
total_file_count += directory.total_file_count;
total_line_count += directory.total_line_count;
total_byte_count += directory.total_byte_count;
}
}
}
RepositoryDirectoryFormalization {
path: path.to_owned(),
direct_file_count,
direct_directory_count,
total_file_count,
total_directory_count,
total_line_count,
total_byte_count,
children: formalized_children,
}
}
#[must_use]
pub fn summarize_repository_resource(
entry: &RepositoryEntry,
config: &SummarizationConfig,
) -> String {
formalize_repository_resource(entry).summary(config)
}
const fn child_summary_cap(mode: SummarizationMode) -> usize {
match mode {
SummarizationMode::Topic => 0,
SummarizationMode::Short => 2,
SummarizationMode::Standard => 4,
SummarizationMode::Full | SummarizationMode::Expand => usize::MAX,
}
}
const fn pluralize(count: usize, singular: &'static str, plural: &'static str) -> &'static str {
if count == 1 {
singular
} else {
plural
}
}
fn push_field(out: &mut String, indent: usize, name: &str, value: &str) {
for _ in 0..indent {
out.push_str(" ");
}
let _ = writeln!(out, "{name} {}", sanitize_lino_value(value));
}