aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src/composer.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/mozart/src/composer.rs')
-rw-r--r--crates/mozart/src/composer.rs152
1 files changed, 152 insertions, 0 deletions
diff --git a/crates/mozart/src/composer.rs b/crates/mozart/src/composer.rs
new file mode 100644
index 0000000..108a5d3
--- /dev/null
+++ b/crates/mozart/src/composer.rs
@@ -0,0 +1,152 @@
+//! 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 std::path::{Path, PathBuf};
+
+use crate::factory::create_composer;
+use mozart_core::composer::{AutoloadGenerator, InstallationManager, Locker, RepositoryManager};
+use mozart_core::config::Config;
+use mozart_core::package::RawPackageData;
+use mozart_registry::download_manager::DownloadManager;
+
+/// 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: RawPackageData,
+ repository_manager: RepositoryManager,
+ installation_manager: InstallationManager,
+ download_manager: DownloadManager,
+ autoload_generator: AutoloadGenerator,
+ locker: Locker,
+}
+
+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: RawPackageData,
+ repository_manager: RepositoryManager,
+ installation_manager: InstallationManager,
+ download_manager: DownloadManager,
+ autoload_generator: AutoloadGenerator,
+ locker: Locker,
+ ) -> Self {
+ Self {
+ project_dir,
+ config,
+ package,
+ repository_manager,
+ installation_manager,
+ download_manager,
+ autoload_generator,
+ locker,
+ }
+ }
+
+ /// 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(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(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(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(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(file: &Path) -> anyhow::Result<Option<Self>> {
+ let project_dir = file
+ .parent()
+ .map(Path::to_path_buf)
+ .unwrap_or_else(|| PathBuf::from("."));
+ Self::try_load(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()`; ideally this would return a fully
+ /// resolved `RootPackageInterface` equivalent, but Mozart does not
+ /// yet have a `RootPackageLoader` port — for now callers see the
+ /// raw, pre-normalised JSON shape.
+ pub fn package(&self) -> &RawPackageData {
+ &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) -> &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
+ }
+}