aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src/composer.rs
blob: c73902ee8fce63cd72036ffb2a76be5f1a579bc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//! Composer-equivalent root state: composer.json + effective config +
//! the manager objects commands look up off the root [`Composer`].
//!
//! Mirrors the role of `Composer\Composer` / `Composer\PartialComposer`
//! (PHP) — a state container with getters for the merged [`Config`], the
//! root [`RawPackageData`], the [`RepositoryManager`], and the
//! [`InstallationManager`]. Wiring lives in [`crate::factory`], the same
//! split as upstream's `Composer\Factory::createComposer`.
//!
//! See `Composer\Command\BaseCommand::requireComposer()` /
//! `Composer\Command\BaseCommand::tryComposer()` for the upstream contract
//! that [`Composer::require`] and [`Composer::try_load`] are modelled on.

use crate::factory::create_composer;
use mozart_core::composer::{AutoloadGenerator, InstallationManager, Locker, RepositoryManager};
use mozart_core::config::Config;
use mozart_core::console::IoInterface;
use mozart_core::downloader::DownloadManager;
use mozart_core::package::RootPackageData;
use mozart_core::package::archiver::ArchiveManager;
use std::path::{Path, PathBuf};

/// Project-level Composer state. Mirrors `Composer\PartialComposer` /
/// `Composer\Composer` in PHP, exposing the subset of getters command
/// handlers need today: config, root package, repository manager,
/// installation manager, autoload generator, and locker. More
/// managers (download, …) can be layered on as commands need them.
pub struct Composer {
    project_dir: PathBuf,
    config: Config,
    package: RootPackageData,
    repository_manager: RepositoryManager,
    installation_manager: InstallationManager,
    download_manager: std::sync::Arc<tokio::sync::Mutex<DownloadManager>>,
    autoload_generator: AutoloadGenerator,
    locker: Locker,
    archive_manager: std::sync::Arc<tokio::sync::Mutex<ArchiveManager>>,
}

impl Composer {
    /// All-args constructor used by [`crate::factory::create_composer`].
    /// Mirrors the PHP pattern of `new Composer()` followed by
    /// `setConfig` / `setPackage` / `setRepositoryManager` /
    /// `setInstallationManager` / `setAutoloadGenerator` / `setLocker`,
    /// collapsed into a single immutable build.
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        project_dir: PathBuf,
        config: Config,
        package: RootPackageData,
        repository_manager: RepositoryManager,
        installation_manager: InstallationManager,
        download_manager: std::sync::Arc<tokio::sync::Mutex<DownloadManager>>,
        autoload_generator: AutoloadGenerator,
        locker: Locker,
        archive_manager: std::sync::Arc<tokio::sync::Mutex<ArchiveManager>>,
    ) -> Self {
        Self {
            project_dir,
            config,
            package,
            repository_manager,
            installation_manager,
            download_manager,
            autoload_generator,
            locker,
            archive_manager,
        }
    }

    /// Load Composer state for `project_dir`, requiring a composer.json.
    /// Mirrors `BaseCommand::requireComposer()`, which delegates to
    /// `Factory::createComposer` after asserting the file exists.
    pub fn require(
        io: std::sync::Arc<std::sync::Mutex<Box<dyn IoInterface>>>,
        project_dir: impl Into<PathBuf>,
    ) -> anyhow::Result<Self> {
        let project_dir = project_dir.into();
        let composer_json = project_dir.join("composer.json");
        if !composer_json.exists() {
            anyhow::bail!(
                "Composer could not find a composer.json file in {}",
                project_dir.display()
            );
        }
        create_composer(io, project_dir, &composer_json)
    }

    /// Load Composer state for `project_dir`, returning `None` if no
    /// composer.json exists. Other I/O or parse errors still propagate.
    /// Mirrors `BaseCommand::tryComposer()`.
    pub fn try_load(
        io: std::sync::Arc<std::sync::Mutex<Box<dyn IoInterface>>>,
        project_dir: impl Into<PathBuf>,
    ) -> anyhow::Result<Option<Self>> {
        let project_dir = project_dir.into();
        let composer_json = project_dir.join("composer.json");
        if !composer_json.exists() {
            return Ok(None);
        }
        create_composer(io, project_dir, &composer_json).map(Some)
    }

    /// Load Composer state keyed on a specific `composer.json` file, deriving
    /// the project directory from `file.parent()`. Mirrors
    /// `ValidateCommand::createComposerInstance($file)` — Composer keys
    /// instances on a file rather than a directory for non-default paths.
    pub fn try_load_from_file(
        io: std::sync::Arc<std::sync::Mutex<Box<dyn IoInterface>>>,
        file: &Path,
    ) -> anyhow::Result<Option<Self>> {
        let project_dir = file
            .parent()
            .map(Path::to_path_buf)
            .unwrap_or_else(|| PathBuf::from("."));
        Self::try_load(io, project_dir)
    }

    pub fn project_dir(&self) -> &Path {
        &self.project_dir
    }

    pub fn config(&self) -> &Config {
        &self.config
    }

    /// Root package loaded from the project's `composer.json`.
    /// Mirrors `Composer::getPackage()` — returns a fully typed
    /// [`RootPackageData`] that implements the [`RootPackage`] trait
    /// hierarchy (`Package` → `CompletePackage` → `RootPackage`),
    /// equivalent to PHP's `RootPackageInterface`.
    pub fn package(&self) -> &RootPackageData {
        &self.package
    }

    /// Mirror of `Composer::getRepositoryManager()`.
    pub fn repository_manager(&self) -> &RepositoryManager {
        &self.repository_manager
    }

    /// Mirror of `Composer::getInstallationManager()`.
    pub fn installation_manager(&self) -> &InstallationManager {
        &self.installation_manager
    }

    pub fn download_manager(&self) -> &std::sync::Arc<tokio::sync::Mutex<DownloadManager>> {
        &self.download_manager
    }

    /// Mirror of `Composer::getAutoloadGenerator()`.
    ///
    /// Returned by shared reference because Mozart's
    /// [`AutoloadGenerator`] is stateless — per-call toggles live on
    /// [`AutoloadDumpOptions`] passed into `dump()`, not on the
    /// generator itself. Diverges from PHP's
    /// `$composer->getAutoloadGenerator()->setDryRun(...)` chain.
    pub fn autoload_generator(&self) -> &AutoloadGenerator {
        &self.autoload_generator
    }

    /// Mirror of `Composer::getLocker()`.
    pub fn locker(&self) -> &Locker {
        &self.locker
    }

    pub fn archive_manager(&self) -> &std::sync::Arc<tokio::sync::Mutex<ArchiveManager>> {
        &self.archive_manager
    }
}