plantuml_server_client_rs/
collector.rs

1use crate::{IncludesMetadata, IncludesMetadataItem};
2use anyhow::{Context as _, Result};
3use derive_getters::Getters;
4use plantuml_parser::{IncludeToken, IncludesCollections, PathResolver, PlantUmlFileData};
5use std::collections::{BTreeMap, HashMap, HashSet};
6use std::fs::read_to_string;
7use std::path::PathBuf;
8
9/// A [`Collector`] collect [`IncludesMetadataItem`] and include files.
10pub struct Collector;
11
12/// A return data type for [`Collector::collect`]
13#[derive(Getters, Debug)]
14pub struct CollectedContainer {
15    path: PathBuf,
16    includes: IncludesCollections,
17    include_metadata: IncludesMetadata,
18}
19
20impl Collector {
21    /// Collects files to include recursively
22    pub fn collect(path: PathBuf, data: &PlantUmlFileData) -> Result<CollectedContainer> {
23        let mut includes = HashMap::new();
24        let mut include_metadata = BTreeMap::new();
25        let mut negative = HashSet::new();
26
27        Self::collect_includes_inner(
28            path.clone(),
29            data,
30            &mut includes,
31            &mut include_metadata,
32            &mut negative,
33        )?;
34
35        tracing::debug!("includes = {includes:?}");
36        tracing::debug!("include_metadata = {include_metadata:?}");
37
38        Ok(CollectedContainer {
39            path,
40            includes: IncludesCollections::new(includes),
41            include_metadata: include_metadata.into(),
42        })
43    }
44
45    /// Collects files to include recursively
46    fn collect_includes_inner(
47        path: PathBuf,
48        data: &PlantUmlFileData,
49        includes: &mut HashMap<PathBuf, PlantUmlFileData>,
50        include_metadata: &mut BTreeMap<PathBuf, Vec<IncludesMetadataItem>>,
51        negative: &mut HashSet<PathBuf>,
52    ) -> Result<()> {
53        tracing::info!("collect path = {path:?}");
54
55        let resolver = PathResolver::new(path);
56        tracing::debug!("resolver = {resolver:?}");
57
58        for data in data.iter() {
59            for include in data.includes().iter() {
60                let mut inner_resolver = resolver.clone();
61                let relative_path: PathBuf = include.filepath().into();
62                inner_resolver.add(relative_path.clone());
63                let path = inner_resolver
64                    .build()
65                    .context("failed to resolve path to collect files")?;
66                tracing::debug!("path from inner_resolver = {path:?}");
67
68                Self::insert_metadata_if_needed(
69                    include,
70                    &resolver,
71                    &inner_resolver,
72                    include_metadata,
73                )?;
74
75                // avoid infinite loop
76                if includes.contains_key(&path) || negative.contains(&path) {
77                    continue;
78                }
79
80                let Ok(data) = read_to_string(&path) else {
81                    tracing::warn!("failed to read file: path = {path:?}");
82                    negative.insert(path);
83                    continue;
84                };
85
86                let Ok(content) = PlantUmlFileData::parse_from_str(data) else {
87                    tracing::warn!("failed to parse PlantUML content: path = {path:?}");
88                    negative.insert(path);
89                    continue;
90                };
91
92                includes.insert(path.clone(), content.clone());
93                Self::collect_includes_inner(path, &content, includes, include_metadata, negative)?;
94            }
95        }
96
97        Ok(())
98    }
99
100    fn insert_metadata_if_needed(
101        include: &IncludeToken,
102        resolver: &PathResolver,
103        inner_resolver: &PathResolver,
104        include_metadata: &mut BTreeMap<PathBuf, Vec<IncludesMetadataItem>>,
105    ) -> Result<()> {
106        let relative_path: PathBuf = include.filepath().into();
107        let path = inner_resolver.build()?;
108        let raw_path = inner_resolver.build_without_normalize()?;
109        let base_path = resolver
110            .build()
111            .context("failed to resolve path to metadata base_path")?;
112
113        let metadata = IncludesMetadataItem::builder()
114            .path(raw_path)
115            .base_path(base_path)
116            .relative_path(relative_path)
117            .index(include.index())
118            .id(include.id().map(|x| x.into()))
119            .build();
120        include_metadata
121            .entry(path.clone())
122            .or_default()
123            .push(metadata);
124
125        Ok(())
126    }
127}