diff options
Diffstat (limited to 'crates')
55 files changed, 321 insertions, 298 deletions
diff --git a/crates/mozart-autoload/Cargo.toml b/crates/mozart-autoload/Cargo.toml index 98c4f6f..1f1ed5a 100644 --- a/crates/mozart-autoload/Cargo.toml +++ b/crates/mozart-autoload/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true mozart-class-map-generator.workspace = true mozart-registry.workspace = true anyhow.workspace = true +indexmap.workspace = true md5.workspace = true serde_json.workspace = true diff --git a/crates/mozart-autoload/src/autoload.rs b/crates/mozart-autoload/src/autoload.rs index 069fcfa..5e5d702 100644 --- a/crates/mozart-autoload/src/autoload.rs +++ b/crates/mozart-autoload/src/autoload.rs @@ -1,7 +1,8 @@ +use indexmap::IndexSet; use mozart_class_map_generator::{scan_classmap_dirs, scan_psr_for_classmap}; use mozart_registry::installed::InstalledPackages; use mozart_registry::lockfile::LockedPackage; -use std::collections::{BTreeMap, HashSet}; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; // Embed Composer PHP files from the submodule at compile time. @@ -470,7 +471,7 @@ fn generate_platform_check( packages: &[LockedPackage], root_require: Option<&serde_json::Value>, mode: &PlatformCheckMode, - dev_package_names: &HashSet<String>, + dev_package_names: &IndexSet<String>, ) -> Option<String> { if matches!(mode, PlatformCheckMode::Disabled) { return None; @@ -934,7 +935,7 @@ pub fn generate(config: &AutoloadConfig) -> anyhow::Result<GenerateResult> { } // 4a. Generate platform_check.php if needed - let dev_package_names_set: HashSet<String> = installed + let dev_package_names_set: IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) diff --git a/crates/mozart-core/Cargo.toml b/crates/mozart-core/Cargo.toml index 391c310..fb114ff 100644 --- a/crates/mozart-core/Cargo.toml +++ b/crates/mozart-core/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] +mozart-console-macros.workspace = true mozart-spdx-licenses.workspace = true anyhow.workspace = true colored.workspace = true @@ -11,7 +12,6 @@ dialoguer.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true -mozart-console-macros = { version = "0.1.0", path = "../mozart-console-macros" } [dev-dependencies] tempfile.workspace = true diff --git a/crates/mozart-registry/Cargo.toml b/crates/mozart-registry/Cargo.toml index abde30d..83e42b5 100644 --- a/crates/mozart-registry/Cargo.toml +++ b/crates/mozart-registry/Cargo.toml @@ -13,6 +13,7 @@ anyhow.workspace = true async-trait.workspace = true filetime.workspace = true flate2.workspace = true +indexmap.workspace = true md5.workspace = true reqwest.workspace = true serde.workspace = true diff --git a/crates/mozart-registry/src/downloader.rs b/crates/mozart-registry/src/downloader.rs index 8c1f0b0..c13ebdc 100644 --- a/crates/mozart-registry/src/downloader.rs +++ b/crates/mozart-registry/src/downloader.rs @@ -1,6 +1,6 @@ use crate::cache::Cache; +use indexmap::IndexSet; use sha1::{Digest, Sha1}; -use std::collections::HashSet; use std::fs; use std::io::{Cursor, Read, Write}; use std::path::Path; @@ -168,7 +168,7 @@ fn find_top_level_dir(entries: &[String]) -> Option<String> { return None; } - let mut prefixes: HashSet<String> = HashSet::new(); + let mut prefixes: IndexSet<String> = IndexSet::new(); for entry in entries { let slash_pos = entry.find('/')?; prefixes.insert(entry[..slash_pos + 1].to_string()); diff --git a/crates/mozart-registry/src/inline_package.rs b/crates/mozart-registry/src/inline_package.rs index bad00fb..0cc38d6 100644 --- a/crates/mozart-registry/src/inline_package.rs +++ b/crates/mozart-registry/src/inline_package.rs @@ -6,8 +6,8 @@ //! pool and into the generated lockfile entry verbatim. use crate::packagist::PackagistVersion; +use indexmap::IndexSet; use mozart_core::package::RawRepository; -use std::collections::HashSet; /// One package extracted from a `type: package` repository. pub struct InlinePackage { @@ -28,7 +28,7 @@ pub struct InlinePackage { /// first-repo-wins priority via `RepositorySet::findPackages`. pub fn collect_inline_packages(repositories: &[RawRepository]) -> Vec<InlinePackage> { let mut packages = Vec::new(); - let mut claimed: HashSet<String> = HashSet::new(); + let mut claimed: IndexSet<String> = IndexSet::new(); for repo in repositories { if repo.repo_type != "package" { continue; @@ -54,7 +54,7 @@ pub fn collect_inline_packages(repositories: &[RawRepository]) -> Vec<InlinePack _ => {} } - let mut names_this_repo: HashSet<String> = HashSet::new(); + let mut names_this_repo: IndexSet<String> = IndexSet::new(); for pkg in from_this_repo { if claimed.contains(&pkg.name) { continue; diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs index 99e87c8..de2c030 100644 --- a/crates/mozart-registry/src/lockfile.rs +++ b/crates/mozart-registry/src/lockfile.rs @@ -1,9 +1,11 @@ use crate::packagist::{PackagistDist, PackagistSource, PackagistVersion}; use crate::repository::RepositorySet; use crate::resolver::ResolvedPackage; +use indexmap::IndexMap; +use indexmap::IndexSet; use mozart_core::package::{RawPackageData, to_json_pretty}; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeMap, VecDeque}; use std::fs; use std::path::Path; @@ -566,14 +568,14 @@ fn classify_dev_packages( resolved: &[ResolvedPackage], require: &BTreeMap<String, String>, _require_dev: &BTreeMap<String, String>, - package_metadata: &HashMap<String, PackagistVersion>, -) -> HashSet<String> { + package_metadata: &IndexMap<String, PackagistVersion>, +) -> IndexSet<String> { // Build set of all resolved package names for quick lookup - let resolved_names: HashSet<&str> = resolved.iter().map(|p| p.name.as_str()).collect(); + let resolved_names: IndexSet<&str> = resolved.iter().map(|p| p.name.as_str()).collect(); // BFS from non-dev root dependencies through each package's `require` map. // All reachable packages are production packages. - let mut production: HashSet<String> = HashSet::new(); + let mut production: IndexSet<String> = IndexSet::new(); let mut queue: VecDeque<String> = VecDeque::new(); // Seed queue with non-dev root dependencies that are actual packages (not platform) @@ -659,7 +661,7 @@ pub async fn generate_lock_file(request: &LockFileGenerationRequest) -> anyhow:: // — short-circuit those before hitting the network. Everything else goes // through `RepositorySet`, which today contains only Packagist; future // steps will move VCS / inline through the same set. - let mut package_metadata: HashMap<String, PackagistVersion> = HashMap::new(); + let mut package_metadata: IndexMap<String, PackagistVersion> = IndexMap::new(); let repo_set = &request.repositories; for pkg in &real_resolved { if let Some(inline) = request.inline_lookup(&pkg.name, &pkg.version_normalized) { @@ -1120,7 +1122,7 @@ mod tests { let mut require_dev = BTreeMap::new(); require_dev.insert("vendor/b".to_string(), "^1.0".to_string()); - let mut metadata: HashMap<String, PackagistVersion> = HashMap::new(); + let mut metadata: IndexMap<String, PackagistVersion> = IndexMap::new(); // A requires C let mut a_require = BTreeMap::new(); @@ -1196,7 +1198,7 @@ mod tests { let mut require_dev = BTreeMap::new(); require_dev.insert("vendor/b".to_string(), "^1.0".to_string()); - let mut metadata: HashMap<String, PackagistVersion> = HashMap::new(); + let mut metadata: IndexMap<String, PackagistVersion> = IndexMap::new(); // A requires C let mut a_require = BTreeMap::new(); @@ -1388,7 +1390,7 @@ mod tests { require_dev: vec![], include_dev: false, minimum_stability: Stability::Stable, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: true, prefer_lowest: false, platform: PlatformConfig::new(), @@ -1398,10 +1400,10 @@ mod tests { std::env::temp_dir().join("mozart-test-cache"), false, ))), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: vec![], - root_provide: HashMap::new(), - root_replace: HashMap::new(), + root_provide: IndexMap::new(), + root_replace: IndexMap::new(), }; let resolved = resolve(&resolve_request) diff --git a/crates/mozart-registry/src/repository/mod.rs b/crates/mozart-registry/src/repository/mod.rs index 0f742a3..21752b9 100644 --- a/crates/mozart-registry/src/repository/mod.rs +++ b/crates/mozart-registry/src/repository/mod.rs @@ -117,10 +117,10 @@ impl RepositorySet { &self, queries: &[PackageQuery<'_>], ) -> anyhow::Result<Vec<NamedPackagistVersion>> { - use std::collections::HashSet; + use indexmap::IndexSet; let mut packages: Vec<NamedPackagistVersion> = Vec::new(); - let mut answered: HashSet<String> = HashSet::new(); + let mut answered: IndexSet<String> = IndexSet::new(); for repo in &self.repos { let pending: Vec<PackageQuery<'_>> = queries diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs index 6499130..89d3a68 100644 --- a/crates/mozart-registry/src/resolver.rs +++ b/crates/mozart-registry/src/resolver.rs @@ -4,7 +4,7 @@ //! candidate packages, generates SAT rules, and runs the CDCL solver to find //! a compatible set of packages to install. -use std::collections::{HashMap, HashSet}; +use indexmap::{IndexMap, IndexSet}; use std::fmt; use std::sync::Arc; @@ -275,7 +275,7 @@ impl PackageName { /// Platform package configuration. /// Maps package names to version strings (normalized, e.g. "8.1.0.0"). pub struct PlatformConfig { - pub packages: HashMap<String, String>, + pub packages: IndexMap<String, String>, } impl Default for PlatformConfig { @@ -288,7 +288,7 @@ impl PlatformConfig { /// Detect platform packages from the local PHP installation. pub fn new() -> Self { let detected = mozart_core::platform::detect_platform(); - let mut packages = HashMap::new(); + let mut packages = IndexMap::new(); for pkg in detected { packages.insert(pkg.name, pkg.version); } @@ -308,7 +308,7 @@ impl PlatformConfig { for (name, value) in obj { let key = name.to_lowercase(); if value.as_bool() == Some(false) { - self.packages.remove(&key); + self.packages.shift_remove(&key); continue; } if let Some(s) = value.as_str() { @@ -318,7 +318,7 @@ impl PlatformConfig { } /// Parse platform packages into `Version` values. - pub fn to_versions(&self) -> HashMap<String, Version> { + pub fn to_versions(&self) -> IndexMap<String, Version> { self.packages .iter() .filter_map(|(name, version_str)| { @@ -380,7 +380,7 @@ fn passes_stability_filter( package_name: &str, version: &Version, minimum_stability: Stability, - stability_flags: &HashMap<String, Stability>, + stability_flags: &IndexMap<String, Stability>, ) -> bool { let min_stability = stability_flags .get(package_name) @@ -417,7 +417,7 @@ fn packagist_to_pool_inputs( package_name: &str, pv: &packagist::PackagistVersion, minimum_stability: Stability, - stability_flags: &HashMap<String, Stability>, + stability_flags: &IndexMap<String, Stability>, ) -> Vec<PoolPackageInput> { let mut results = Vec::new(); @@ -570,7 +570,7 @@ pub struct ResolveRequest { /// Minimum stability from composer.json. pub minimum_stability: Stability, /// Per-package stability overrides. - pub stability_flags: HashMap<String, Stability>, + pub stability_flags: IndexMap<String, Stability>, /// Whether prefer-stable is enabled. pub prefer_stable: bool, /// Whether prefer-lowest is enabled. @@ -589,7 +589,7 @@ pub struct ResolveRequest { pub repositories: Arc<RepositorySet>, /// Temporary version constraint overrides (from --with flag). /// Maps package name (lowercase) to constraint string. - pub temporary_constraints: HashMap<String, String>, + pub temporary_constraints: IndexMap<String, String>, /// VCS / inline-package repository entries from composer.json's /// `repositories` section, used by the eager VCS scan and inline-package /// preload that still live in `resolve()` (Step B follow-up will move @@ -600,10 +600,10 @@ pub struct ResolveRequest { /// `require` names something the root itself `provide`s with a matching /// constraint, no install-one-of rule is emitted, mirroring Composer's /// `RuleSetGenerator::createRequireRule` self-fulfillment branch. - pub root_provide: HashMap<String, String>, + pub root_provide: IndexMap<String, String>, /// Root composer.json's `replace` map. Same role as `root_provide` for the /// `replace` link: a replaced target counts as fulfilled by the root. - pub root_replace: HashMap<String, String>, + pub root_replace: IndexMap<String, String>, } /// A single package in the resolution output. @@ -633,12 +633,12 @@ pub struct ResolvedPackage { /// or a human-readable error. pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, ResolveError> { // 1. Build root requirements - let mut root_requires: HashMap<String, Option<String>> = HashMap::new(); + let mut root_requires: IndexMap<String, Option<String>> = IndexMap::new(); // Per-package stability overrides extracted from `@dev`/`@beta`/etc. // suffixes on root constraints. Mirrors Composer's // `RootPackageLoader::extractStabilityFlags`. Merged on top of the // request's caller-supplied flags (which today are usually empty). - let mut stability_flags: HashMap<String, Stability> = request.stability_flags.clone(); + let mut stability_flags: IndexMap<String, Stability> = request.stability_flags.clone(); let minimum_stability = request.minimum_stability; let mut insert_root_require = |name: &str, constraint: &str| { @@ -700,7 +700,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R let mut builder = PoolBuilder::new(); // Set up ignore list for platform requirements - let mut ignore_set: HashSet<String> = HashSet::new(); + let mut ignore_set: IndexSet<String> = IndexSet::new(); for name in &request.ignore_platform_req_list { ignore_set.insert(name.clone()); } @@ -709,7 +709,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // Add platform packages as fixed entries let platform_config = request.platform.to_versions(); - let mut fixed_packages_by_name: HashMap<String, u32> = HashMap::new(); + let mut fixed_packages_by_name: IndexMap<String, u32> = IndexMap::new(); for (name, version) in &platform_config { if should_skip_platform_dep( name, @@ -734,7 +734,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // Scan VCS repositories and collect packages from them let vcs_packages = vcs_bridge::scan_vcs_repositories(&request.raw_repositories).await; - let mut vcs_package_names: HashSet<String> = HashSet::new(); + let mut vcs_package_names: IndexSet<String> = IndexSet::new(); for vpkg in &vcs_packages { vcs_package_names.insert(vpkg.name.clone()); } @@ -752,7 +752,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // network fetch; they go straight into the pool and are also tracked by // name so the Packagist seed/transitive loops below skip them. let inline_packages = crate::inline_package::collect_inline_packages(&request.raw_repositories); - let mut inline_package_names: HashSet<String> = HashSet::new(); + let mut inline_package_names: IndexSet<String> = IndexSet::new(); for ipkg in &inline_packages { inline_package_names.insert(ipkg.name.clone()); let inputs = packagist_to_pool_inputs( @@ -773,7 +773,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // into the pool, with names recorded so Packagist loops skip them. let composer_repo_packages = crate::composer_repo::collect_composer_packages(&request.raw_repositories); - let mut composer_repo_names: HashSet<String> = HashSet::new(); + let mut composer_repo_names: IndexSet<String> = IndexSet::new(); for cpkg in &composer_repo_packages { composer_repo_names.insert(cpkg.name.clone()); let inputs = packagist_to_pool_inputs( @@ -917,7 +917,7 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R // Create policy and solve let policy = DefaultPolicy::new(request.prefer_stable, request.prefer_lowest); - let fixed_set: HashSet<u32> = fixed_ids.into_iter().collect(); + let fixed_set: IndexSet<u32> = fixed_ids.into_iter().collect(); let solver = Solver::new(rules, &pool, policy, fixed_set); match solver.solve() { @@ -1143,7 +1143,7 @@ mod tests { let rc_v = v_pre(1, 0, 0, 0, "RC1"); let dev_v = v_pre(1, 0, 0, 0, "dev"); - let flags = HashMap::new(); + let flags = IndexMap::new(); assert!(passes_stability_filter( "foo/foo", @@ -1184,7 +1184,7 @@ mod tests { let alpha_v = v_pre(1, 0, 0, 0, "alpha1"); let dev_v = v_pre(1, 0, 0, 0, "dev"); - let flags = HashMap::new(); + let flags = IndexMap::new(); assert!(passes_stability_filter( "foo/foo", @@ -1215,7 +1215,7 @@ mod tests { #[test] fn test_stability_filter_dev() { let dev_v = v_pre(1, 0, 0, 0, "dev"); - let flags = HashMap::new(); + let flags = IndexMap::new(); assert!(passes_stability_filter( "foo/foo", &dev_v, @@ -1308,14 +1308,14 @@ mod tests { vec![], ); - let mut requires = HashMap::new(); + let mut requires = IndexMap::new(); requires.insert("foo/foo".to_string(), Some("^1.0".to_string())); let generator = RuleSetGenerator::new(&mut pool); - let (rules, _) = generator.generate(&requires, &[], &HashMap::new(), &HashMap::new()); + let (rules, _) = generator.generate(&requires, &[], &IndexMap::new(), &IndexMap::new()); let policy = DefaultPolicy::default(); - let solver = Solver::new(rules, &pool, policy, HashSet::new()); + let solver = Solver::new(rules, &pool, policy, IndexSet::new()); let result = solver.solve().unwrap(); // Should install foo/foo (id=1) and bar/bar (id=2) @@ -1335,7 +1335,7 @@ mod tests { require_dev: vec![], include_dev: false, minimum_stability: Stability::Stable, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: true, prefer_lowest: false, platform: PlatformConfig::new(), @@ -1345,10 +1345,10 @@ mod tests { std::env::temp_dir().join("mozart-test-cache"), false, ))), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: vec![], - root_provide: HashMap::new(), - root_replace: HashMap::new(), + root_provide: IndexMap::new(), + root_replace: IndexMap::new(), }; let result = resolve(&request).await; diff --git a/crates/mozart-registry/src/vcs_bridge.rs b/crates/mozart-registry/src/vcs_bridge.rs index 1f93a51..e9a2f37 100644 --- a/crates/mozart-registry/src/vcs_bridge.rs +++ b/crates/mozart-registry/src/vcs_bridge.rs @@ -3,7 +3,8 @@ //! Scans VCS repositories defined in composer.json and converts //! discovered package versions into pool inputs for the SAT resolver. -use std::collections::{BTreeMap, HashMap}; +use indexmap::IndexMap; +use std::collections::BTreeMap; use mozart_core::package::{RawRepository, Stability}; use mozart_sat_resolver::{PoolPackageInput, make_pool_links}; @@ -57,7 +58,7 @@ pub async fn scan_vcs_repositories(repositories: &[RawRepository]) -> Vec<VcsPac pub fn vcs_to_pool_inputs( vpkg: &VcsPackageVersion, minimum_stability: Stability, - stability_flags: &HashMap<String, Stability>, + stability_flags: &IndexMap<String, Stability>, ) -> Vec<PoolPackageInput> { let mut results = Vec::new(); @@ -207,7 +208,7 @@ fn passes_vcs_stability_filter( package_name: &str, version: &mozart_semver::Version, minimum_stability: Stability, - stability_flags: &HashMap<String, Stability>, + stability_flags: &IndexMap<String, Stability>, ) -> bool { let stability = version_stability(version); let pkg_flag = stability_flags.get(&package_name.to_lowercase()); diff --git a/crates/mozart-sat-resolver/Cargo.toml b/crates/mozart-sat-resolver/Cargo.toml index c02904f..5b8a46c 100644 --- a/crates/mozart-sat-resolver/Cargo.toml +++ b/crates/mozart-sat-resolver/Cargo.toml @@ -6,5 +6,6 @@ edition.workspace = true [dependencies] mozart-semver.workspace = true mozart-core.workspace = true +indexmap.workspace = true [dev-dependencies] diff --git a/crates/mozart-sat-resolver/src/decisions.rs b/crates/mozart-sat-resolver/src/decisions.rs index abfbe3d..e9cc935 100644 --- a/crates/mozart-sat-resolver/src/decisions.rs +++ b/crates/mozart-sat-resolver/src/decisions.rs @@ -1,7 +1,7 @@ use crate::error::SolverBugError; use crate::pool::{Literal, PackageId, literal_to_package_id}; use crate::rule_set::RuleId; -use std::collections::HashMap; +use indexmap::IndexMap; /// A decision entry: which literal was decided and which rule caused it. #[derive(Debug, Clone)] @@ -16,7 +16,7 @@ pub struct Decision { pub struct Decisions { /// Package ID → signed level. Positive = install, negative = uninstall. /// The absolute value is the decision level. - decision_map: HashMap<PackageId, i32>, + decision_map: IndexMap<PackageId, i32>, /// Queue of decisions in order. decision_queue: Vec<Decision>, } @@ -24,7 +24,7 @@ pub struct Decisions { impl Decisions { pub fn new() -> Self { Decisions { - decision_map: HashMap::new(), + decision_map: IndexMap::new(), decision_queue: Vec::new(), } } diff --git a/crates/mozart-sat-resolver/src/policy.rs b/crates/mozart-sat-resolver/src/policy.rs index a66719f..a2678d5 100644 --- a/crates/mozart-sat-resolver/src/policy.rs +++ b/crates/mozart-sat-resolver/src/policy.rs @@ -1,5 +1,5 @@ use crate::pool::{Literal, Pool}; -use std::collections::HashMap; +use indexmap::IndexMap; /// Version selection policy: decides which version to prefer when multiple /// candidates satisfy a requirement. @@ -35,7 +35,7 @@ impl DefaultPolicy { } // Group literals by package name - let mut groups: HashMap<&str, Vec<Literal>> = HashMap::new(); + let mut groups: IndexMap<&str, Vec<Literal>> = IndexMap::new(); for &lit in literals { let pkg = pool.literal_to_package(lit); groups.entry(pkg.name.as_str()).or_default().push(lit); diff --git a/crates/mozart-sat-resolver/src/pool.rs b/crates/mozart-sat-resolver/src/pool.rs index 0312c24..9268675 100644 --- a/crates/mozart-sat-resolver/src/pool.rs +++ b/crates/mozart-sat-resolver/src/pool.rs @@ -1,5 +1,5 @@ +use indexmap::IndexMap; use mozart_semver::VersionConstraint; -use std::collections::HashMap; use std::fmt; /// Unique identifier for a package in the pool. 1-based. @@ -122,9 +122,9 @@ pub struct Pool { /// All packages, indexed by (id - 1). packages: Vec<PoolPackage>, /// Index: package name → list of package IDs providing that name. - package_by_name: HashMap<String, Vec<PackageId>>, + package_by_name: IndexMap<String, Vec<PackageId>>, /// Cache for what_provides results. - provider_cache: HashMap<(String, String), Vec<PackageId>>, + provider_cache: IndexMap<(String, String), Vec<PackageId>>, /// Packages that are fixed/locked but unacceptable (e.g. failed stability). unacceptable_fixed_packages: Vec<PackageId>, } @@ -133,7 +133,7 @@ impl Pool { /// Create a new pool from a list of package inputs. pub fn new(inputs: Vec<PoolPackageInput>, unacceptable_fixed_ids: Vec<PackageId>) -> Self { let mut packages: Vec<PoolPackage> = Vec::with_capacity(inputs.len()); - let mut package_by_name: HashMap<String, Vec<PackageId>> = HashMap::new(); + let mut package_by_name: IndexMap<String, Vec<PackageId>> = IndexMap::new(); // Collect alias links (alias_idx, target_name, target_normalized) for // a second pass once every input has a stable ID. let mut pending_aliases: Vec<(usize, String, String)> = Vec::new(); @@ -189,7 +189,7 @@ impl Pool { Pool { packages, package_by_name, - provider_cache: HashMap::new(), + provider_cache: IndexMap::new(), unacceptable_fixed_packages: unacceptable_fixed_ids, } } diff --git a/crates/mozart-sat-resolver/src/pool_builder.rs b/crates/mozart-sat-resolver/src/pool_builder.rs index 83684aa..3883d85 100644 --- a/crates/mozart-sat-resolver/src/pool_builder.rs +++ b/crates/mozart-sat-resolver/src/pool_builder.rs @@ -1,5 +1,6 @@ use crate::pool::{Pool, PoolLink, PoolPackageInput}; -use std::collections::{HashSet, VecDeque}; +use indexmap::IndexSet; +use std::collections::VecDeque; /// Builder for constructing a Pool from package metadata. /// @@ -10,13 +11,13 @@ pub struct PoolBuilder { /// Packages to add to the pool. inputs: Vec<PoolPackageInput>, /// Names already added (to avoid duplicates). - added: HashSet<String>, + added: IndexSet<String>, /// Queue of package names that need to be explored. pending_names: VecDeque<String>, /// Package names that have already been explored (returned by next_pending). - explored_names: HashSet<String>, + explored_names: IndexSet<String>, /// Specific platform packages to ignore (from `--ignore-platform-req=name`). - ignore_platform_reqs: HashSet<String>, + ignore_platform_reqs: IndexSet<String>, /// When true, ignore every platform package (php, ext-*, lib-*, composer-*). /// Mirrors `--ignore-platform-reqs` (no value). ignore_all_platform_reqs: bool, @@ -26,16 +27,16 @@ impl PoolBuilder { pub fn new() -> Self { PoolBuilder { inputs: Vec::new(), - added: HashSet::new(), + added: IndexSet::new(), pending_names: VecDeque::new(), - explored_names: HashSet::new(), - ignore_platform_reqs: HashSet::new(), + explored_names: IndexSet::new(), + ignore_platform_reqs: IndexSet::new(), ignore_all_platform_reqs: false, } } /// Set platform requirements to ignore during exploration. - pub fn set_ignore_platform_reqs(&mut self, names: HashSet<String>) { + pub fn set_ignore_platform_reqs(&mut self, names: IndexSet<String>) { self.ignore_platform_reqs = names; } diff --git a/crates/mozart-sat-resolver/src/problem.rs b/crates/mozart-sat-resolver/src/problem.rs index c453fa9..a1692fd 100644 --- a/crates/mozart-sat-resolver/src/problem.rs +++ b/crates/mozart-sat-resolver/src/problem.rs @@ -75,7 +75,7 @@ impl Problem { } // Deduplicate - let mut seen = std::collections::HashSet::new(); + let mut seen = indexmap::IndexSet::new(); let mut unique = Vec::new(); for msg in messages { if seen.insert(msg.clone()) { @@ -367,7 +367,7 @@ fn rule_pretty_string(pool: &Pool, rule: &Rule) -> String { /// Similar to Composer's formatPackagesUnique. fn format_providers(pool: &Pool, literals: &[Literal]) -> String { // Group by package name - let mut groups: std::collections::HashMap<&str, Vec<&str>> = std::collections::HashMap::new(); + let mut groups: indexmap::IndexMap<&str, Vec<&str>> = indexmap::IndexMap::new(); for &lit in literals { let pkg = pool.literal_to_package(lit); groups diff --git a/crates/mozart-sat-resolver/src/request.rs b/crates/mozart-sat-resolver/src/request.rs index 94891f0..26c17ba 100644 --- a/crates/mozart-sat-resolver/src/request.rs +++ b/crates/mozart-sat-resolver/src/request.rs @@ -1,5 +1,5 @@ use crate::pool::PackageId; -use std::collections::HashMap; +use indexmap::IndexMap; /// A requirement: package name + version constraint string. #[derive(Debug, Clone)] @@ -14,7 +14,7 @@ pub struct Require { #[derive(Debug, Clone)] pub struct Request { /// Root requirements: package name → constraint string. - pub requires: HashMap<String, Option<String>>, + pub requires: IndexMap<String, Option<String>>, /// Fixed packages (must be installed, cannot be modified). pub fixed_packages: Vec<PackageId>, /// Locked packages (installed but can be removed if nothing requires them). @@ -24,7 +24,7 @@ pub struct Request { impl Request { pub fn new() -> Self { Request { - requires: HashMap::new(), + requires: IndexMap::new(), fixed_packages: Vec::new(), locked_packages: Vec::new(), } diff --git a/crates/mozart-sat-resolver/src/rule_set.rs b/crates/mozart-sat-resolver/src/rule_set.rs index 4d1a8a6..918bdae 100644 --- a/crates/mozart-sat-resolver/src/rule_set.rs +++ b/crates/mozart-sat-resolver/src/rule_set.rs @@ -1,5 +1,5 @@ use crate::rule::{Rule, RuleType}; -use std::collections::HashMap; +use indexmap::IndexMap; /// A unique identifier for a rule within the RuleSet. pub type RuleId = usize; @@ -18,7 +18,7 @@ pub struct RuleSet { /// Total rule count. next_rule_id: usize, /// Deduplication index. - rules_by_hash: HashMap<String, Vec<usize>>, + rules_by_hash: IndexMap<String, Vec<usize>>, /// Maps rule ID → (type, index within type's vec). rule_type_index: Vec<(RuleType, usize)>, } @@ -31,7 +31,7 @@ impl RuleSet { request_rules: Vec::new(), learned_rules: Vec::new(), next_rule_id: 0, - rules_by_hash: HashMap::new(), + rules_by_hash: IndexMap::new(), rule_type_index: Vec::new(), } } diff --git a/crates/mozart-sat-resolver/src/rule_set_generator.rs b/crates/mozart-sat-resolver/src/rule_set_generator.rs index 11c04cc..2ab9f86 100644 --- a/crates/mozart-sat-resolver/src/rule_set_generator.rs +++ b/crates/mozart-sat-resolver/src/rule_set_generator.rs @@ -1,8 +1,10 @@ use crate::pool::{Literal, PackageId, Pool, PoolLink}; use crate::rule::{ReasonData, Rule, RuleReason, RuleType}; use crate::rule_set::RuleSet; +use indexmap::IndexMap; +use indexmap::IndexSet; use mozart_semver::VersionConstraint; -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::VecDeque; /// Generates SAT rules from the pool and request. /// @@ -11,11 +13,11 @@ pub struct RuleSetGenerator<'a> { pool: &'a mut Pool, rules: RuleSet, /// Packages already processed. - added_map: HashSet<PackageId>, + added_map: IndexSet<PackageId>, /// Package names → list of package IDs with that name (non-alias). - added_packages_by_name: HashMap<String, Vec<PackageId>>, + added_packages_by_name: IndexMap<String, Vec<PackageId>>, /// Specific platform packages to ignore (from `--ignore-platform-req=name`). - ignore_platform_reqs: HashSet<String>, + ignore_platform_reqs: IndexSet<String>, /// When true, every platform package is treated as ignored. /// Mirrors `--ignore-platform-reqs` (no value). ignore_all_platform_reqs: bool, @@ -26,15 +28,15 @@ impl<'a> RuleSetGenerator<'a> { RuleSetGenerator { pool, rules: RuleSet::new(), - added_map: HashSet::new(), - added_packages_by_name: HashMap::new(), - ignore_platform_reqs: HashSet::new(), + added_map: IndexSet::new(), + added_packages_by_name: IndexMap::new(), + ignore_platform_reqs: IndexSet::new(), ignore_all_platform_reqs: false, } } /// Set platform requirements to ignore. - pub fn set_ignore_platform_reqs(&mut self, names: HashSet<String>) { + pub fn set_ignore_platform_reqs(&mut self, names: IndexSet<String>) { self.ignore_platform_reqs = names; } @@ -76,10 +78,10 @@ impl<'a> RuleSetGenerator<'a> { /// unresolvable problem. pub fn generate( mut self, - requires: &HashMap<String, Option<String>>, + requires: &IndexMap<String, Option<String>>, fixed_packages: &[PackageId], - root_provides: &HashMap<String, String>, - root_replaces: &HashMap<String, String>, + root_provides: &IndexMap<String, String>, + root_replaces: &IndexMap<String, String>, ) -> (RuleSet, Vec<(String, Option<String>)>) { let mut missing_root_requires: Vec<(String, Option<String>)> = Vec::new(); // Process fixed packages @@ -351,7 +353,7 @@ impl<'a> RuleSetGenerator<'a> { fn root_self_fulfills( target: &str, require_constraint: Option<&str>, - root_links: &HashMap<String, String>, + root_links: &IndexMap<String, String>, ) -> bool { let Some(link_constraint_str) = root_links.get(target) else { return false; @@ -394,11 +396,11 @@ mod tests { vec![], ); - let mut requires = HashMap::new(); + let mut requires = IndexMap::new(); requires.insert("a/a".to_string(), None); let generator = RuleSetGenerator::new(&mut pool); - let (rules, _) = generator.generate(&requires, &[], &HashMap::new(), &HashMap::new()); + let (rules, _) = generator.generate(&requires, &[], &IndexMap::new(), &IndexMap::new()); // Should have a request rule: (1 | 2) let request_count = rules.iter_type(RuleType::Request).count(); @@ -434,11 +436,11 @@ mod tests { vec![], ); - let mut requires = HashMap::new(); + let mut requires = IndexMap::new(); requires.insert("a/a".to_string(), None); let generator = RuleSetGenerator::new(&mut pool); - let (rules, _) = generator.generate(&requires, &[], &HashMap::new(), &HashMap::new()); + let (rules, _) = generator.generate(&requires, &[], &IndexMap::new(), &IndexMap::new()); // Should have: // 1. Request rule: (1) — root requires a/a @@ -452,7 +454,7 @@ mod tests { let generator = RuleSetGenerator::new(&mut pool); let (rules, _) = - generator.generate(&HashMap::new(), &[1], &HashMap::new(), &HashMap::new()); + generator.generate(&IndexMap::new(), &[1], &IndexMap::new(), &IndexMap::new()); // Should have an assertion rule: (1) let request_rules: Vec<_> = rules.iter_type(RuleType::Request).collect(); diff --git a/crates/mozart-sat-resolver/src/rule_watch_graph.rs b/crates/mozart-sat-resolver/src/rule_watch_graph.rs index 1b7604d..202dcca 100644 --- a/crates/mozart-sat-resolver/src/rule_watch_graph.rs +++ b/crates/mozart-sat-resolver/src/rule_watch_graph.rs @@ -2,7 +2,7 @@ use crate::decisions::Decisions; use crate::pool::Literal; use crate::rule::Rule; use crate::rule_set::RuleId; -use std::collections::HashMap; +use indexmap::IndexMap; /// A watch node: tracks which 2 literals a rule watches. /// @@ -24,7 +24,7 @@ struct WatchNode { /// Port of Composer's RuleWatchGraph.php. pub struct RuleWatchGraph { /// Literal → list of watch node indices watching that literal. - watch_chains: HashMap<Literal, Vec<usize>>, + watch_chains: IndexMap<Literal, Vec<usize>>, /// All watch nodes. nodes: Vec<WatchNode>, } @@ -32,7 +32,7 @@ pub struct RuleWatchGraph { impl RuleWatchGraph { pub fn new() -> Self { RuleWatchGraph { - watch_chains: HashMap::new(), + watch_chains: IndexMap::new(), nodes: Vec::new(), } } diff --git a/crates/mozart-sat-resolver/src/solver.rs b/crates/mozart-sat-resolver/src/solver.rs index 49a4ce4..8739381 100644 --- a/crates/mozart-sat-resolver/src/solver.rs +++ b/crates/mozart-sat-resolver/src/solver.rs @@ -6,7 +6,7 @@ use crate::problem::Problem; use crate::rule::{ReasonData, Rule, RuleReason, RuleType}; use crate::rule_set::{RuleId, RuleSet}; use crate::rule_watch_graph::RuleWatchGraph; -use std::collections::{HashMap, HashSet}; +use indexmap::{IndexMap, IndexSet}; /// Result of solving: the list of package IDs to install. #[derive(Debug)] @@ -25,7 +25,7 @@ pub struct Solver<'a> { watch_graph: RuleWatchGraph, decisions: Decisions, /// Fixed packages by ID. - fixed_map: HashSet<PackageId>, + fixed_map: IndexSet<PackageId>, /// Current propagation index in decision queue. propagate_index: usize, /// Branch points: (alternative literals, decision level). @@ -35,7 +35,7 @@ pub struct Solver<'a> { /// Learned rule pool: for each learned rule, the chain of rules that led to it. learned_pool: Vec<Vec<RuleId>>, /// Map from rule ID → learned pool index. - learned_why: HashMap<RuleId, usize>, + learned_why: IndexMap<RuleId, usize>, } impl<'a> Solver<'a> { @@ -44,7 +44,7 @@ impl<'a> Solver<'a> { rules: RuleSet, pool: &'a Pool, policy: DefaultPolicy, - fixed_packages: HashSet<PackageId>, + fixed_packages: IndexSet<PackageId>, ) -> Self { Solver { pool, @@ -57,7 +57,7 @@ impl<'a> Solver<'a> { branches: Vec::new(), problems: Vec::new(), learned_pool: Vec::new(), - learned_why: HashMap::new(), + learned_why: IndexMap::new(), } } @@ -333,7 +333,7 @@ impl<'a> Solver<'a> { let mut rule_level: i32 = 1; let mut num: i32 = 0; let mut l1num: i32 = 0; - let mut seen: HashSet<PackageId> = HashSet::new(); + let mut seen: IndexSet<PackageId> = IndexSet::new(); let mut learned_literal: Option<Literal> = None; let mut other_learned_literals: Vec<Literal> = Vec::new(); @@ -430,7 +430,7 @@ impl<'a> Solver<'a> { let decision = self.decisions.at_offset(decision_id); let literal = decision.literal; - seen.remove(&literal_to_package_id(literal)); + seen.shift_remove(&literal_to_package_id(literal)); if num != 0 { num -= 1; @@ -456,7 +456,7 @@ impl<'a> Solver<'a> { // Only level 1 marks left for other in &other_learned_literals { - seen.remove(&literal_to_package_id(*other)); + seen.shift_remove(&literal_to_package_id(*other)); } l1num += 1; l1retry = true; @@ -504,7 +504,7 @@ impl<'a> Solver<'a> { &self, problem: &mut Problem, conflict_rule_id: RuleId, - rule_seen: &mut HashSet<RuleId>, + rule_seen: &mut IndexSet<RuleId>, ) { if rule_seen.contains(&conflict_rule_id) { return; @@ -542,11 +542,11 @@ impl<'a> Solver<'a> { let mut problem = Problem::new(); problem.add_rule(conflict_rule_id); - let mut rule_seen = HashSet::new(); + let mut rule_seen = IndexSet::new(); self.analyze_unsolvable_rule(&mut problem, conflict_rule_id, &mut rule_seen); // Collect related decisions - let mut seen: HashSet<PackageId> = HashSet::new(); + let mut seen: IndexSet<PackageId> = IndexSet::new(); let conflict_literals = self.rules.rule_by_id(conflict_rule_id).literals().to_vec(); for &lit in &conflict_literals { if self.decisions.satisfy(lit) { @@ -835,7 +835,7 @@ mod tests { /// Creates a pool with N dummy packages (1..=max_id). fn make_rules_and_solve( rules: Vec<(Rule, RuleType)>, - fixed: HashSet<PackageId>, + fixed: IndexSet<PackageId>, max_id: u32, ) -> Result<SolverResult, SolverError> { let mut rs = RuleSet::new(); @@ -859,7 +859,7 @@ mod tests { Rule::new(vec![1], RuleReason::RootRequire, ReasonData::None), RuleType::Request, )], - HashSet::new(), + IndexSet::new(), 3, ) .unwrap(); @@ -881,7 +881,7 @@ mod tests { RuleType::Request, ), ], - HashSet::new(), + IndexSet::new(), 3, ) .unwrap(); @@ -907,7 +907,7 @@ mod tests { RuleType::Package, ), ], - HashSet::new(), + IndexSet::new(), 3, ) .unwrap(); @@ -944,7 +944,7 @@ mod tests { RuleType::Request, ), ], - HashSet::new(), + IndexSet::new(), 3, ) .unwrap(); @@ -970,7 +970,7 @@ mod tests { RuleType::Package, ), ], - HashSet::new(), + IndexSet::new(), 3, ) .unwrap(); @@ -999,7 +999,7 @@ mod tests { RuleType::Package, ), ], - HashSet::new(), + IndexSet::new(), 3, ); diff --git a/crates/mozart-sat-resolver/src/transaction.rs b/crates/mozart-sat-resolver/src/transaction.rs index 176b862..de7c9a0 100644 --- a/crates/mozart-sat-resolver/src/transaction.rs +++ b/crates/mozart-sat-resolver/src/transaction.rs @@ -1,6 +1,6 @@ use crate::decisions::Decisions; use crate::pool::{PackageId, Pool, literal_to_package_id}; -use std::collections::{HashMap, HashSet}; +use indexmap::{IndexMap, IndexSet}; /// An operation to perform on a package. /// @@ -104,14 +104,14 @@ impl<'a> Transaction<'a> { /// Calculate the delta between present and result packages. fn calculate_operations(&mut self) { // Build maps: name -> package_id for present packages - let mut present_by_name: HashMap<&str, PackageId> = HashMap::new(); + let mut present_by_name: IndexMap<&str, PackageId> = IndexMap::new(); for &id in &self.present_ids { let pkg = self.pool.package_by_id(id); present_by_name.insert(&pkg.name, id); } // Track which present packages have been matched - let mut matched_present: HashSet<PackageId> = HashSet::new(); + let mut matched_present: IndexSet<PackageId> = IndexSet::new(); // Build topologically sorted result packages via DFS let sorted_results = self.topological_sort(); @@ -158,9 +158,9 @@ impl<'a> Transaction<'a> { /// Topologically sort result packages by their dependency order. /// Uses DFS: dependencies are processed before dependents. fn topological_sort(&self) -> Vec<PackageId> { - let result_set: HashSet<PackageId> = self.result_ids.iter().copied().collect(); - let result_by_name: HashMap<&str, Vec<PackageId>> = { - let mut map: HashMap<&str, Vec<PackageId>> = HashMap::new(); + let result_set: IndexSet<PackageId> = self.result_ids.iter().copied().collect(); + let result_by_name: IndexMap<&str, Vec<PackageId>> = { + let mut map: IndexMap<&str, Vec<PackageId>> = IndexMap::new(); for &id in &self.result_ids { let pkg = self.pool.package_by_id(id); map.entry(&pkg.name).or_default().push(id); @@ -168,7 +168,7 @@ impl<'a> Transaction<'a> { map }; - let mut visited: HashSet<PackageId> = HashSet::new(); + let mut visited: IndexSet<PackageId> = IndexSet::new(); let mut order: Vec<PackageId> = Vec::new(); // Find root packages (not required by any other result package) @@ -221,11 +221,11 @@ impl<'a> Transaction<'a> { /// Find root packages: result packages not required by any other result package. fn get_root_packages( &self, - result_set: &HashSet<PackageId>, - _result_by_name: &HashMap<&str, Vec<PackageId>>, + result_set: &IndexSet<PackageId>, + _result_by_name: &IndexMap<&str, Vec<PackageId>>, ) -> Vec<PackageId> { // Collect all required package names - let mut required_names: HashSet<&str> = HashSet::new(); + let mut required_names: IndexSet<&str> = IndexSet::new(); for &id in result_set { let pkg = self.pool.package_by_id(id); for req in &pkg.requires { @@ -270,7 +270,7 @@ impl<'a> LockTransaction<'a> { pub fn new( pool: &'a Pool, present_ids: Vec<PackageId>, - unlockable_ids: HashSet<PackageId>, + unlockable_ids: IndexSet<PackageId>, decisions: &Decisions, ) -> Self { // Extract result packages from decisions @@ -300,7 +300,7 @@ impl<'a> LockTransaction<'a> { /// Set the non-dev packages from an extraction-only solve result. /// `extraction_ids` are the package IDs that were resolved without dev deps. pub fn set_non_dev_packages(&mut self, extraction_ids: &[PackageId]) { - let extraction_names: HashSet<String> = extraction_ids + let extraction_names: IndexSet<String> = extraction_ids .iter() .map(|&id| self.transaction.pool.package_by_id(id).name.clone()) .collect(); diff --git a/crates/mozart-spdx-licenses/Cargo.toml b/crates/mozart-spdx-licenses/Cargo.toml index fc31b9f..1500932 100644 --- a/crates/mozart-spdx-licenses/Cargo.toml +++ b/crates/mozart-spdx-licenses/Cargo.toml @@ -4,6 +4,7 @@ version.workspace = true edition.workspace = true [dependencies] +indexmap.workspace = true [build-dependencies] serde.workspace = true diff --git a/crates/mozart-spdx-licenses/src/lib.rs b/crates/mozart-spdx-licenses/src/lib.rs index 81fa329..668270f 100644 --- a/crates/mozart-spdx-licenses/src/lib.rs +++ b/crates/mozart-spdx-licenses/src/lib.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use indexmap::IndexMap; use std::sync::LazyLock; include!(concat!(env!("OUT_DIR"), "/spdx_data.rs")); @@ -21,16 +21,16 @@ pub struct ExceptionInfo { /// SPDX license database with expression validation. pub struct SpdxLicenses { - licenses: HashMap<&'static str, LicenseInfo>, - exceptions: HashMap<&'static str, ExceptionInfo>, - name_to_id: HashMap<&'static str, &'static str>, + licenses: IndexMap<&'static str, LicenseInfo>, + exceptions: IndexMap<&'static str, ExceptionInfo>, + name_to_id: IndexMap<&'static str, &'static str>, } impl SpdxLicenses { /// Build the license database from generated data. pub fn new() -> Self { - let mut licenses = HashMap::with_capacity(LICENSES.len()); - let mut name_to_id = HashMap::with_capacity(LICENSES.len()); + let mut licenses = IndexMap::with_capacity(LICENSES.len()); + let mut name_to_id = IndexMap::with_capacity(LICENSES.len()); for &(lower, id, full_name, osi, deprecated) in LICENSES { licenses.insert( lower, @@ -44,7 +44,7 @@ impl SpdxLicenses { name_to_id.insert(full_name, id); } - let mut exceptions = HashMap::with_capacity(EXCEPTIONS.len()); + let mut exceptions = IndexMap::with_capacity(EXCEPTIONS.len()); for &(lower, id, full_name) in EXCEPTIONS { exceptions.insert( lower, diff --git a/crates/mozart-test-harness/Cargo.toml b/crates/mozart-test-harness/Cargo.toml index 61b9109..6c9421c 100644 --- a/crates/mozart-test-harness/Cargo.toml +++ b/crates/mozart-test-harness/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] anyhow.workspace = true +indexmap.workspace = true regex.workspace = true serde_json.workspace = true tempfile.workspace = true diff --git a/crates/mozart-test-harness/src/parser.rs b/crates/mozart-test-harness/src/parser.rs index dbc71ae..827272b 100644 --- a/crates/mozart-test-harness/src/parser.rs +++ b/crates/mozart-test-harness/src/parser.rs @@ -1,5 +1,5 @@ use anyhow::{Context, Result, bail}; -use std::collections::HashMap; +use indexmap::IndexMap; use std::fs; use std::path::Path; @@ -53,7 +53,7 @@ pub fn parse_test_str(content: &str) -> Result<ParsedTest> { } } - let mut take = |key: &str| sections.remove(key); + let mut take = |key: &str| sections.shift_remove(key); let test = take("TEST").unwrap(); let composer = take("COMPOSER").unwrap(); @@ -86,10 +86,10 @@ pub fn parse_test_str(content: &str) -> Result<ParsedTest> { }) } -fn split_sections(content: &str) -> Result<HashMap<String, String>> { +fn split_sections(content: &str) -> Result<IndexMap<String, String>> { let header_re = regex::Regex::new(r"^--([A-Z][A-Z-]*)--$").unwrap(); - let mut sections: HashMap<String, String> = HashMap::new(); + let mut sections: IndexMap<String, String> = IndexMap::new(); let mut current_section: Option<String> = None; let mut current_body = String::new(); diff --git a/crates/mozart-vcs/Cargo.toml b/crates/mozart-vcs/Cargo.toml index dc6dfc3..18eff25 100644 --- a/crates/mozart-vcs/Cargo.toml +++ b/crates/mozart-vcs/Cargo.toml @@ -8,6 +8,7 @@ mozart-core.workspace = true mozart-semver.workspace = true anyhow.workspace = true base64.workspace = true +indexmap.workspace = true regex.workspace = true reqwest.workspace = true serde.workspace = true diff --git a/crates/mozart-vcs/src/driver/bitbucket.rs b/crates/mozart-vcs/src/driver/bitbucket.rs index d47987d..77704fa 100644 --- a/crates/mozart-vcs/src/driver/bitbucket.rs +++ b/crates/mozart-vcs/src/driver/bitbucket.rs @@ -1,4 +1,5 @@ -use std::collections::{BTreeMap, HashMap}; +use indexmap::IndexMap; +use std::collections::BTreeMap; use anyhow::{Result, bail}; use regex::Regex; @@ -16,7 +17,7 @@ pub struct BitbucketDriver { root_identifier: Option<String>, tags: Option<BTreeMap<String, String>>, branches: Option<BTreeMap<String, String>>, - info_cache: HashMap<String, Option<serde_json::Value>>, + info_cache: IndexMap<String, Option<serde_json::Value>>, git_driver: Option<Box<GitDriver>>, http_client: Client, config: DriverConfig, @@ -34,7 +35,7 @@ impl BitbucketDriver { root_identifier: None, tags: None, branches: None, - info_cache: HashMap::new(), + info_cache: IndexMap::new(), git_driver: None, http_client: Client::new(), config, diff --git a/crates/mozart-vcs/src/driver/forgejo.rs b/crates/mozart-vcs/src/driver/forgejo.rs index ec2ca14..488e165 100644 --- a/crates/mozart-vcs/src/driver/forgejo.rs +++ b/crates/mozart-vcs/src/driver/forgejo.rs @@ -1,4 +1,5 @@ -use std::collections::{BTreeMap, HashMap}; +use indexmap::IndexMap; +use std::collections::BTreeMap; use anyhow::{Result, bail}; use regex::Regex; @@ -20,7 +21,7 @@ pub struct ForgejoDriver { root_identifier: Option<String>, tags: Option<BTreeMap<String, String>>, branches: Option<BTreeMap<String, String>>, - info_cache: HashMap<String, Option<serde_json::Value>>, + info_cache: IndexMap<String, Option<serde_json::Value>>, git_driver: Option<Box<GitDriver>>, http_client: Client, config: DriverConfig, @@ -39,7 +40,7 @@ impl ForgejoDriver { root_identifier: None, tags: None, branches: None, - info_cache: HashMap::new(), + info_cache: IndexMap::new(), git_driver: None, http_client: Client::new(), config, diff --git a/crates/mozart-vcs/src/driver/git.rs b/crates/mozart-vcs/src/driver/git.rs index cc9a210..43f4ecb 100644 --- a/crates/mozart-vcs/src/driver/git.rs +++ b/crates/mozart-vcs/src/driver/git.rs @@ -1,4 +1,5 @@ -use std::collections::{BTreeMap, HashMap}; +use indexmap::IndexMap; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use anyhow::Result; @@ -17,7 +18,7 @@ pub struct GitDriver { root_identifier: Option<String>, tags: Option<BTreeMap<String, String>>, branches: Option<BTreeMap<String, String>>, - info_cache: HashMap<String, Option<serde_json::Value>>, + info_cache: IndexMap<String, Option<serde_json::Value>>, git_util: GitUtil, is_local: bool, } @@ -37,7 +38,7 @@ impl GitDriver { root_identifier: None, tags: None, branches: None, - info_cache: HashMap::new(), + info_cache: IndexMap::new(), git_util, is_local, } @@ -85,7 +86,7 @@ impl GitDriver { fn parse_tags(output: &str) -> BTreeMap<String, String> { let mut tags = BTreeMap::new(); // First pass: collect dereferenced tags (^{}) - let mut dereferenced = HashMap::new(); + let mut dereferenced = IndexMap::new(); for line in output.lines() { let line = line.trim(); if line.is_empty() { diff --git a/crates/mozart-vcs/src/driver/github.rs b/crates/mozart-vcs/src/driver/github.rs index c47c2fe..9c11389 100644 --- a/crates/mozart-vcs/src/driver/github.rs +++ b/crates/mozart-vcs/src/driver/github.rs @@ -1,4 +1,5 @@ -use std::collections::{BTreeMap, HashMap}; +use indexmap::IndexMap; +use std::collections::BTreeMap; use anyhow::{Result, bail}; use regex::Regex; @@ -19,7 +20,7 @@ pub struct GitHubDriver { tags: Option<BTreeMap<String, String>>, branches: Option<BTreeMap<String, String>>, repo_data: Option<serde_json::Value>, - info_cache: HashMap<String, Option<serde_json::Value>>, + info_cache: IndexMap<String, Option<serde_json::Value>>, git_driver: Option<Box<GitDriver>>, http_client: Client, config: DriverConfig, @@ -37,7 +38,7 @@ impl GitHubDriver { tags: None, branches: None, repo_data: None, - info_cache: HashMap::new(), + info_cache: IndexMap::new(), git_driver: None, http_client: Client::new(), config, diff --git a/crates/mozart-vcs/src/driver/gitlab.rs b/crates/mozart-vcs/src/driver/gitlab.rs index f96c078..c1afbcb 100644 --- a/crates/mozart-vcs/src/driver/gitlab.rs +++ b/crates/mozart-vcs/src/driver/gitlab.rs @@ -1,4 +1,5 @@ -use std::collections::{BTreeMap, HashMap}; +use indexmap::IndexMap; +use std::collections::BTreeMap; use anyhow::{Result, bail}; use regex::Regex; @@ -21,7 +22,7 @@ pub struct GitLabDriver { root_identifier: Option<String>, tags: Option<BTreeMap<String, String>>, branches: Option<BTreeMap<String, String>>, - info_cache: HashMap<String, Option<serde_json::Value>>, + info_cache: IndexMap<String, Option<serde_json::Value>>, git_driver: Option<Box<GitDriver>>, http_client: Client, config: DriverConfig, @@ -41,7 +42,7 @@ impl GitLabDriver { root_identifier: None, tags: None, branches: None, - info_cache: HashMap::new(), + info_cache: IndexMap::new(), git_driver: None, http_client: Client::new(), config, diff --git a/crates/mozart-vcs/src/driver/hg.rs b/crates/mozart-vcs/src/driver/hg.rs index f884c50..0782775 100644 --- a/crates/mozart-vcs/src/driver/hg.rs +++ b/crates/mozart-vcs/src/driver/hg.rs @@ -1,4 +1,5 @@ -use std::collections::{BTreeMap, HashMap}; +use indexmap::IndexMap; +use std::collections::BTreeMap; use std::path::PathBuf; use anyhow::Result; @@ -17,7 +18,7 @@ pub struct HgDriver { root_identifier: Option<String>, tags: Option<BTreeMap<String, String>>, branches: Option<BTreeMap<String, String>>, - info_cache: HashMap<String, Option<serde_json::Value>>, + info_cache: IndexMap<String, Option<serde_json::Value>>, hg_util: HgUtil, config: DriverConfig, } @@ -31,7 +32,7 @@ impl HgDriver { root_identifier: None, tags: None, branches: None, - info_cache: HashMap::new(), + info_cache: IndexMap::new(), hg_util: HgUtil::new(process), config, } diff --git a/crates/mozart-vcs/src/driver/svn.rs b/crates/mozart-vcs/src/driver/svn.rs index eea2d08..16363e1 100644 --- a/crates/mozart-vcs/src/driver/svn.rs +++ b/crates/mozart-vcs/src/driver/svn.rs @@ -1,4 +1,5 @@ -use std::collections::{BTreeMap, HashMap}; +use indexmap::IndexMap; +use std::collections::BTreeMap; use anyhow::Result; use regex::Regex; @@ -20,7 +21,7 @@ pub struct SvnDriver { root_identifier: Option<String>, tags: Option<BTreeMap<String, String>>, branches: Option<BTreeMap<String, String>>, - info_cache: HashMap<String, Option<serde_json::Value>>, + info_cache: IndexMap<String, Option<serde_json::Value>>, svn_util: SvnUtil, } @@ -36,7 +37,7 @@ impl SvnDriver { root_identifier: None, tags: None, branches: None, - info_cache: HashMap::new(), + info_cache: IndexMap::new(), svn_util: SvnUtil::new(process), } } diff --git a/crates/mozart-vcs/src/process.rs b/crates/mozart-vcs/src/process.rs index 91741a8..8ccc11d 100644 --- a/crates/mozart-vcs/src/process.rs +++ b/crates/mozart-vcs/src/process.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use indexmap::IndexMap; use std::path::Path; use std::process::Command; use std::time::{Duration, Instant}; @@ -18,7 +18,7 @@ pub struct ProcessOutput { /// Corresponds to Composer's `ProcessExecutor`. pub struct ProcessExecutor { timeout: Option<Duration>, - env_overrides: HashMap<String, Option<String>>, + env_overrides: IndexMap<String, Option<String>>, } impl Default for ProcessExecutor { @@ -31,14 +31,14 @@ impl ProcessExecutor { pub fn new() -> Self { Self { timeout: None, - env_overrides: HashMap::new(), + env_overrides: IndexMap::new(), } } pub fn with_timeout(secs: u64) -> Self { Self { timeout: Some(Duration::from_secs(secs)), - env_overrides: HashMap::new(), + env_overrides: IndexMap::new(), } } diff --git a/crates/mozart/Cargo.toml b/crates/mozart/Cargo.toml index fb47195..bb052dc 100644 --- a/crates/mozart/Cargo.toml +++ b/crates/mozart/Cargo.toml @@ -14,6 +14,7 @@ anyhow.workspace = true clap.workspace = true clap_complete.workspace = true colored.workspace = true +indexmap.workspace = true regex.workspace = true reqwest.workspace = true self-replace.workspace = true diff --git a/crates/mozart/src/commands/audit.rs b/crates/mozart/src/commands/audit.rs index 9cbff31..163a43a 100644 --- a/crates/mozart/src/commands/audit.rs +++ b/crates/mozart/src/commands/audit.rs @@ -187,7 +187,7 @@ fn load_installed_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<V let vendor_dir = working_dir.join("vendor"); let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; - let dev_names: std::collections::HashSet<String> = installed + let dev_names: indexmap::IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) @@ -257,7 +257,7 @@ fn filter_advisories( ignore_severity: &[String], console: &mozart_core::console::Console, ) -> BTreeMap<String, Vec<MatchedAdvisory>> { - let ignore_set: std::collections::HashSet<String> = + let ignore_set: indexmap::IndexSet<String> = ignore_severity.iter().map(|s| s.to_lowercase()).collect(); let mut result: BTreeMap<String, Vec<MatchedAdvisory>> = BTreeMap::new(); diff --git a/crates/mozart/src/commands/bump.rs b/crates/mozart/src/commands/bump.rs index 60b055f..3ad6ed6 100644 --- a/crates/mozart/src/commands/bump.rs +++ b/crates/mozart/src/commands/bump.rs @@ -1,7 +1,7 @@ use clap::Args; +use indexmap::IndexMap; use mozart_core::console::Verbosity; use mozart_core::console_format; -use std::collections::HashMap; use std::path::PathBuf; /// Exit code for stale lock file (matches Composer's BumpCommand::ERROR_LOCK_OUTDATED) @@ -226,8 +226,8 @@ pub async fn execute( /// Build a map of lowercase package names to (pretty_version, version_normalized) from composer.lock. fn build_locked_versions_map( lock: &mozart_registry::lockfile::LockFile, -) -> HashMap<String, (String, Option<String>)> { - let mut map: HashMap<String, (String, Option<String>)> = HashMap::new(); +) -> IndexMap<String, (String, Option<String>)> { + let mut map: IndexMap<String, (String, Option<String>)> = IndexMap::new(); let all_packages = lock .packages diff --git a/crates/mozart/src/commands/check_platform_reqs.rs b/crates/mozart/src/commands/check_platform_reqs.rs index e9954f4..a890e0c 100644 --- a/crates/mozart/src/commands/check_platform_reqs.rs +++ b/crates/mozart/src/commands/check_platform_reqs.rs @@ -227,7 +227,7 @@ fn collect_from_installed_data( no_dev: bool, requirements: &mut BTreeMap<String, Vec<PlatformRequirement>>, ) { - let dev_names: std::collections::HashSet<String> = installed + let dev_names: indexmap::IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs index af77ba6..139550a 100644 --- a/crates/mozart/src/commands/create_project.rs +++ b/crates/mozart/src/commands/create_project.rs @@ -1,4 +1,5 @@ use clap::Args; +use indexmap::IndexMap; use mozart_core::console_format; use mozart_core::package::{self, Stability}; use mozart_core::validation; @@ -7,7 +8,6 @@ use mozart_registry::lockfile; use mozart_registry::packagist; use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest}; use mozart_registry::version; -use std::collections::HashMap; use std::path::{Path, PathBuf}; #[derive(Args)] @@ -413,7 +413,7 @@ pub async fn execute( require_dev, include_dev: dev_mode, minimum_stability: proj_minimum_stability, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: composer_prefer_stable, prefer_lowest: false, platform: PlatformConfig::new(), @@ -422,7 +422,7 @@ pub async fn execute( repositories: std::sync::Arc::new( mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: raw.repositories.clone(), root_provide: raw .provide diff --git a/crates/mozart/src/commands/dependency.rs b/crates/mozart/src/commands/dependency.rs index 6dcaec8..d044432 100644 --- a/crates/mozart/src/commands/dependency.rs +++ b/crates/mozart/src/commands/dependency.rs @@ -4,7 +4,8 @@ //! `prohibits` (aka `why-not`) answers: "Which packages prevent version X of package Y from being //! installed?" -use std::collections::{BTreeMap, HashSet}; +use indexmap::IndexSet; +use std::collections::BTreeMap; use std::path::Path; use anyhow::Result; @@ -233,7 +234,7 @@ fn get_dependents_forward( needles: &[String], recursive: bool, ) -> Result<Vec<DependencyResult>> { - let needle_set: HashSet<String> = needles.iter().map(|n| n.to_lowercase()).collect(); + let needle_set: IndexSet<String> = needles.iter().map(|n| n.to_lowercase()).collect(); // Build name→PackageInfo lookup let pkg_map: BTreeMap<String, &PackageInfo> = packages @@ -243,7 +244,7 @@ fn get_dependents_forward( if recursive { // Recursive: BFS from needles upward to root, building a tree - let mut visited: HashSet<String> = HashSet::new(); + let mut visited: IndexSet<String> = IndexSet::new(); let mut results: Vec<DependencyResult> = Vec::new(); for needle in needles { @@ -318,8 +319,8 @@ fn recurse_dependents( packages: &[PackageInfo], needle: &str, pkg_map: &BTreeMap<String, &PackageInfo>, - visited: &mut HashSet<String>, - _original_needles: &HashSet<String>, + visited: &mut IndexSet<String>, + _original_needles: &IndexSet<String>, ) -> Vec<DependencyResult> { let _ = pkg_map; // kept for potential future use let direct = collect_direct_requires(packages, needle); @@ -545,7 +546,7 @@ pub fn print_table(results: &[DependencyResult], console: &mozart_core::console: .max() .unwrap_or(0); - let mut seen: HashSet<String> = HashSet::new(); + let mut seen: IndexSet<String> = IndexSet::new(); for r in results { let key = format!( "{}|{}|{}|{}", diff --git a/crates/mozart/src/commands/exec.rs b/crates/mozart/src/commands/exec.rs index 350ff5c..eaaf465 100644 --- a/crates/mozart/src/commands/exec.rs +++ b/crates/mozart/src/commands/exec.rs @@ -171,8 +171,7 @@ fn get_binaries(working_dir: &Path, bin_dir: &Path) -> Vec<(String, bool)> { // Collect from root composer.json bin entries let composer_json_path = working_dir.join("composer.json"); if let Ok(root) = mozart_core::package::read_from_file(&composer_json_path) { - let existing: std::collections::HashSet<&str> = - binaries.iter().map(|(n, _)| n.as_str()).collect(); + let existing: indexmap::IndexSet<&str> = binaries.iter().map(|(n, _)| n.as_str()).collect(); let mut local: Vec<String> = root .bin .iter() diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index 9555ba7..b38194e 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -1,4 +1,5 @@ use clap::Args; +use indexmap::IndexSet; use mozart_core::console; use mozart_core::console_format; use mozart_registry::installed; @@ -6,7 +7,7 @@ use mozart_registry::installer_executor::{ ExecuteContext, FilesystemExecutor, InstallerExecutor, PackageOperation, }; use mozart_registry::lockfile; -use std::collections::{BTreeMap, HashSet}; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; #[derive(Args)] @@ -198,7 +199,7 @@ pub fn compute_operations<'a>( } // Compute removals: packages in installed but not in locked - let locked_names: HashSet<String> = locked.iter().map(|p| p.name.to_lowercase()).collect(); + let locked_names: IndexSet<String> = locked.iter().map(|p| p.name.to_lowercase()).collect(); let removals: Vec<String> = installed .packages @@ -254,7 +255,7 @@ fn topological_sort<'a>( // Identify root packages: those not pulled in by any other package's // requires (counting provides/replaces as a match). - let mut required_by_others: HashSet<String> = HashSet::new(); + let mut required_by_others: IndexSet<String> = IndexSet::new(); for pkg in &sorted { let pkg_lower = pkg.name.to_lowercase(); for dep in pkg.require.keys() { @@ -276,8 +277,8 @@ fn topological_sort<'a>( .copied() .collect(); - let mut visited: HashSet<String> = HashSet::new(); - let mut processed: HashSet<String> = HashSet::new(); + let mut visited: IndexSet<String> = IndexSet::new(); + let mut processed: IndexSet<String> = IndexSet::new(); let mut ordered: Vec<&'a lockfile::LockedPackage> = Vec::with_capacity(packages.len()); while let Some(pkg) = stack.pop() { @@ -444,7 +445,7 @@ fn check_platform_requirements_against( return Vec::new(); } - let ignored: HashSet<String> = ignore_platform_req + let ignored: IndexSet<String> = ignore_platform_req .iter() .map(|s| s.to_lowercase()) .collect(); @@ -503,7 +504,7 @@ fn warn_platform_requirements( return; } - let ignored_set: HashSet<String> = ignore_platform_req + let ignored_set: IndexSet<String> = ignore_platform_req .iter() .map(|s| s.to_lowercase()) .collect(); diff --git a/crates/mozart/src/commands/licenses.rs b/crates/mozart/src/commands/licenses.rs index b066fde..5ce2b35 100644 --- a/crates/mozart/src/commands/licenses.rs +++ b/crates/mozart/src/commands/licenses.rs @@ -1,7 +1,7 @@ use clap::Args; +use indexmap::IndexSet; use mozart_core::console::{Console, Verbosity}; use serde::Serialize; -use std::collections::HashSet; use std::path::{Path, PathBuf}; #[derive(Args)] @@ -102,7 +102,7 @@ fn load_installed_licenses(working_dir: &Path, no_dev: bool) -> anyhow::Result<V let vendor_dir = working_dir.join("vendor"); let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; - let dev_names: HashSet<String> = installed + let dev_names: IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) diff --git a/crates/mozart/src/commands/outdated.rs b/crates/mozart/src/commands/outdated.rs index 4d0226d..5a4f854 100644 --- a/crates/mozart/src/commands/outdated.rs +++ b/crates/mozart/src/commands/outdated.rs @@ -1,7 +1,7 @@ use clap::Args; +use indexmap::IndexSet; use mozart_core::matches_wildcard; use std::cmp::Ordering; -use std::collections::HashSet; use std::path::{Path, PathBuf}; #[derive(Args)] @@ -136,14 +136,14 @@ pub async fn execute( }; // Build set of direct dependency names - let direct_names: HashSet<String> = if let Some(ref root) = root_package { - let mut names: HashSet<String> = root.require.keys().map(|k| k.to_lowercase()).collect(); + let direct_names: IndexSet<String> = if let Some(ref root) = root_package { + let mut names: IndexSet<String> = root.require.keys().map(|k| k.to_lowercase()).collect(); if !args.no_dev { names.extend(root.require_dev.keys().map(|k| k.to_lowercase())); } names } else { - HashSet::new() + IndexSet::new() }; // Process each package @@ -242,7 +242,7 @@ fn load_installed_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<V let vendor_dir = working_dir.join("vendor"); let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; - let dev_names: HashSet<String> = installed + let dev_names: IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) diff --git a/crates/mozart/src/commands/reinstall.rs b/crates/mozart/src/commands/reinstall.rs index c19d926..2df55c5 100644 --- a/crates/mozart/src/commands/reinstall.rs +++ b/crates/mozart/src/commands/reinstall.rs @@ -112,7 +112,7 @@ pub async fn execute( // Step 5: Determine packages to reinstall. // Build the full set of installed packages (prod + dev unless --no-dev). - let dev_package_names: std::collections::HashSet<String> = installed + let dev_package_names: indexmap::IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) @@ -666,7 +666,7 @@ mod tests { installed.packages.push(e2.clone()); installed.dev_package_names = vec!["phpunit/phpunit".to_string()]; - let dev_package_names: std::collections::HashSet<String> = installed + let dev_package_names: indexmap::IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs index 20cb6a2..df8bf2b 100644 --- a/crates/mozart/src/commands/remove.rs +++ b/crates/mozart/src/commands/remove.rs @@ -1,11 +1,11 @@ use clap::Args; +use indexmap::IndexMap; use mozart_core::console::Verbosity; use mozart_core::console_format; use mozart_core::package; use mozart_core::validation; use mozart_registry::lockfile; use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest}; -use std::collections::HashMap; #[derive(Args)] pub struct RemoveArgs { @@ -247,7 +247,7 @@ pub async fn execute( require_dev, include_dev: dev_mode, minimum_stability, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: composer_prefer_stable, prefer_lowest: false, platform: PlatformConfig::new(), @@ -256,7 +256,7 @@ pub async fn execute( repositories: std::sync::Arc::new( mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: raw.repositories.clone(), root_provide: raw .provide @@ -517,7 +517,7 @@ async fn remove_unused( require_dev, include_dev: dev_mode, minimum_stability, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: composer_prefer_stable, prefer_lowest: false, platform: PlatformConfig::new(), @@ -526,7 +526,7 @@ async fn remove_unused( repositories: std::sync::Arc::new( mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: raw.repositories.clone(), root_provide: raw .provide @@ -550,7 +550,7 @@ async fn remove_unused( })?; // Build set of resolved package names - let resolved_names: std::collections::HashSet<String> = + let resolved_names: indexmap::IndexSet<String> = resolved.iter().map(|p| p.name.to_lowercase()).collect(); // Find packages in the old lock that are not in the new resolution @@ -847,9 +847,9 @@ mod tests { #[tokio::test] #[ignore] async fn test_remove_full_e2e() { + use indexmap::IndexMap; use mozart_registry::lockfile::{LockFileGenerationRequest, generate_lock_file}; use mozart_registry::resolver::{ResolveRequest, resolve}; - use std::collections::HashMap; use tempfile::tempdir; let dir = tempdir().unwrap(); @@ -870,7 +870,7 @@ mod tests { require_dev: vec![], include_dev: false, minimum_stability: mozart_core::package::Stability::Stable, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: true, prefer_lowest: false, platform: mozart_registry::resolver::PlatformConfig::new(), @@ -884,10 +884,10 @@ mod tests { ), ), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: vec![], - root_provide: HashMap::new(), - root_replace: HashMap::new(), + root_provide: IndexMap::new(), + root_replace: IndexMap::new(), }; let resolved = resolve(&request) .await @@ -923,7 +923,7 @@ mod tests { require_dev: vec![], include_dev: false, minimum_stability: mozart_core::package::Stability::Stable, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: true, prefer_lowest: false, platform: mozart_registry::resolver::PlatformConfig::new(), @@ -937,10 +937,10 @@ mod tests { ), ), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: vec![], - root_provide: HashMap::new(), - root_replace: HashMap::new(), + root_provide: IndexMap::new(), + root_replace: IndexMap::new(), }; let resolved2 = resolve(&request2) .await diff --git a/crates/mozart/src/commands/repository.rs b/crates/mozart/src/commands/repository.rs index 0931b66..e8ca920 100644 --- a/crates/mozart/src/commands/repository.rs +++ b/crates/mozart/src/commands/repository.rs @@ -1085,7 +1085,7 @@ mod tests { }); let result = normalize_repositories(&val); assert_eq!(result.len(), 2); - let names: std::collections::HashSet<&str> = result + let names: indexmap::IndexSet<&str> = result .iter() .filter_map(|v| v.get("name").and_then(|n| n.as_str())) .collect(); diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs index 95b26ea..69d7ea2 100644 --- a/crates/mozart/src/commands/require.rs +++ b/crates/mozart/src/commands/require.rs @@ -1,4 +1,5 @@ use clap::Args; +use indexmap::IndexMap; use mozart_core::console::Verbosity; use mozart_core::console_format; use mozart_core::package::{self, Stability}; @@ -7,7 +8,6 @@ use mozart_registry::lockfile; use mozart_registry::packagist; use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest}; use mozart_registry::version; -use std::collections::HashMap; use std::io::{BufRead, IsTerminal, Write}; #[derive(Args)] @@ -133,7 +133,7 @@ pub struct RequireArgs { /// Returns a list of `"vendor/package:constraint"` strings that the user confirmed, /// or an empty vec if the user typed nothing / pressed Ctrl-D immediately. async fn interactive_search_packages( - already_required: &std::collections::HashSet<String>, + already_required: &indexmap::IndexSet<String>, preferred_stability: Stability, fixed: bool, repo_cache: &mozart_registry::cache::Cache, @@ -359,8 +359,7 @@ pub async fn execute( let raw_check = package::read_from_file(&composer_path)?; // Build set of already-required packages - let mut already_required: std::collections::HashSet<String> = - std::collections::HashSet::new(); + let mut already_required: indexmap::IndexSet<String> = indexmap::IndexSet::new(); for k in raw_check.require.keys() { already_required.insert(k.to_lowercase()); } @@ -636,7 +635,7 @@ pub async fn execute( require_dev, include_dev: dev_mode, minimum_stability, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable, prefer_lowest: args.prefer_lowest, platform: PlatformConfig::new(), @@ -645,7 +644,7 @@ pub async fn execute( repositories: std::sync::Arc::new( mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: raw.repositories.clone(), root_provide: raw .provide @@ -1036,7 +1035,7 @@ mod tests { require_dev: vec![], include_dev: false, minimum_stability: Stability::Stable, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: true, prefer_lowest: false, platform: PlatformConfig::new(), @@ -1050,10 +1049,10 @@ mod tests { ), ), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: vec![], - root_provide: HashMap::new(), - root_replace: HashMap::new(), + root_provide: IndexMap::new(), + root_replace: IndexMap::new(), }; let resolved = resolver::resolve(&request) @@ -1106,7 +1105,7 @@ mod tests { require_dev: vec![], include_dev: false, minimum_stability: Stability::Stable, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: true, prefer_lowest: false, platform: PlatformConfig::new(), @@ -1120,10 +1119,10 @@ mod tests { ), ), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: vec![], - root_provide: HashMap::new(), - root_replace: HashMap::new(), + root_provide: IndexMap::new(), + root_replace: IndexMap::new(), }; let resolved = resolver::resolve(&request) diff --git a/crates/mozart/src/commands/search.rs b/crates/mozart/src/commands/search.rs index accd6af..023bfdf 100644 --- a/crates/mozart/src/commands/search.rs +++ b/crates/mozart/src/commands/search.rs @@ -139,7 +139,7 @@ pub async fn execute( // Deduplicate to unique vendor names (Composer returns vendor-only names // for SEARCH_VENDOR mode). - let mut seen = std::collections::HashSet::new(); + let mut seen = indexmap::IndexSet::new(); let mut vendor_names: Vec<String> = Vec::new(); for r in &results { let vendor = r.name.split('/').next().unwrap_or("").to_string(); @@ -515,7 +515,7 @@ mod tests { ]; let refs: Vec<&SearchResult> = results.iter().collect(); - let mut seen = std::collections::HashSet::new(); + let mut seen = indexmap::IndexSet::new(); let mut vendor_names: Vec<String> = Vec::new(); for r in &refs { let vendor = r.name.split('/').next().unwrap_or("").to_string(); diff --git a/crates/mozart/src/commands/show.rs b/crates/mozart/src/commands/show.rs index fa77321..c59a595 100644 --- a/crates/mozart/src/commands/show.rs +++ b/crates/mozart/src/commands/show.rs @@ -1,8 +1,8 @@ use clap::Args; +use indexmap::{IndexMap, IndexSet}; use mozart_core::console::Verbosity; use mozart_core::console_format; use mozart_core::matches_wildcard; -use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; #[derive(Args)] @@ -292,7 +292,7 @@ fn filter_installed_packages<'a>( // --no-dev: exclude dev packages if args.no_dev { - let dev_names: HashSet<String> = installed + let dev_names: IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) @@ -305,7 +305,7 @@ fn filter_installed_packages<'a>( let composer_json_path = working_dir.join("composer.json"); if composer_json_path.exists() { let root = mozart_core::package::read_from_file(&composer_json_path)?; - let mut direct_names: HashSet<String> = + let mut direct_names: IndexSet<String> = root.require.keys().map(|k| k.to_lowercase()).collect(); if !args.no_dev { direct_names.extend(root.require_dev.keys().map(|k| k.to_lowercase())); @@ -812,7 +812,7 @@ async fn execute_locked( let composer_json_path = working_dir.join("composer.json"); if composer_json_path.exists() { let root = mozart_core::package::read_from_file(&composer_json_path)?; - let mut direct_names: HashSet<String> = + let mut direct_names: IndexSet<String> = root.require.keys().map(|k| k.to_lowercase()).collect(); if !args.no_dev { direct_names.extend(root.require_dev.keys().map(|k| k.to_lowercase())); @@ -1346,7 +1346,7 @@ fn show_tree( let root = mozart_core::package::read_from_file(&composer_json_path)?; // Load all locked packages into a map for quick lookup - let pkg_map: HashMap<String, &mozart_registry::lockfile::LockedPackage>; + let pkg_map: IndexMap<String, &mozart_registry::lockfile::LockedPackage>; let lock_storage; if lock_path.exists() { lock_storage = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; @@ -1357,7 +1357,7 @@ fn show_tree( .map(|p| (p.name.to_lowercase(), p)) .collect(); } else { - pkg_map = HashMap::new(); + pkg_map = IndexMap::new(); } // Determine roots to display: package filter or full tree @@ -1389,7 +1389,7 @@ fn show_tree( ); // Render each root dependency as a tree - let mut visited_global: HashSet<String> = HashSet::new(); + let mut visited_global: IndexSet<String> = IndexSet::new(); let count = root_reqs.len(); for (i, (dep_name, dep_constraint)) in root_reqs.iter().enumerate() { let is_last = i == count - 1; @@ -1415,10 +1415,10 @@ fn show_tree( fn print_tree_node( pkg_name: &str, constraint: &str, - pkg_map: &HashMap<String, &mozart_registry::lockfile::LockedPackage>, + pkg_map: &IndexMap<String, &mozart_registry::lockfile::LockedPackage>, prefix: &str, child_prefix: &str, - visited: &mut HashSet<String>, + visited: &mut IndexSet<String>, depth: usize, console: &mozart_core::console::Console, ) { @@ -1491,7 +1491,7 @@ fn print_tree_node( ); } - visited.remove(&key); + visited.shift_remove(&key); } else { // Package not found in lock file (platform package or not installed) if !is_platform_package(&key) { diff --git a/crates/mozart/src/commands/status.rs b/crates/mozart/src/commands/status.rs index 6d8fc98..29b1e1b 100644 --- a/crates/mozart/src/commands/status.rs +++ b/crates/mozart/src/commands/status.rs @@ -1,7 +1,7 @@ use clap::Args; +use indexmap::IndexMap; use mozart_core::console::Verbosity; use sha1::{Digest, Sha1}; -use std::collections::HashMap; use std::path::{Path, PathBuf}; #[derive(Args)] @@ -293,8 +293,8 @@ fn make_temp_dir(package_name: &str) -> anyhow::Result<PathBuf> { /// Recursively hash all files in a directory. /// /// Returns a map from relative path string to SHA-1 hex digest. -fn hash_directory(dir: &Path) -> anyhow::Result<HashMap<String, String>> { - let mut map = HashMap::new(); +fn hash_directory(dir: &Path) -> anyhow::Result<IndexMap<String, String>> { + let mut map = IndexMap::new(); hash_dir_recursive(dir, dir, &mut map)?; Ok(map) } @@ -302,7 +302,7 @@ fn hash_directory(dir: &Path) -> anyhow::Result<HashMap<String, String>> { fn hash_dir_recursive( root: &Path, current: &Path, - map: &mut HashMap<String, String>, + map: &mut IndexMap<String, String>, ) -> anyhow::Result<()> { let entries = match std::fs::read_dir(current) { Ok(e) => e, @@ -337,8 +337,8 @@ fn hash_dir_recursive( /// Compare two hash maps (original vs installed) and return a list of changes. fn compute_diff( - original: &HashMap<String, String>, - installed: &HashMap<String, String>, + original: &IndexMap<String, String>, + installed: &IndexMap<String, String>, ) -> Vec<FileChange> { let mut changes: Vec<FileChange> = Vec::new(); @@ -418,7 +418,7 @@ mod tests { #[test] fn test_compute_diff_no_changes() { - let mut map: HashMap<String, String> = HashMap::new(); + let mut map: IndexMap<String, String> = IndexMap::new(); map.insert("src/Foo.php".to_string(), "abc123".to_string()); map.insert("src/Bar.php".to_string(), "def456".to_string()); @@ -430,10 +430,10 @@ mod tests { #[test] fn test_compute_diff_modified() { - let mut original: HashMap<String, String> = HashMap::new(); + let mut original: IndexMap<String, String> = IndexMap::new(); original.insert("src/Foo.php".to_string(), "abc123".to_string()); - let mut installed: HashMap<String, String> = HashMap::new(); + let mut installed: IndexMap<String, String> = IndexMap::new(); installed.insert("src/Foo.php".to_string(), "xyz999".to_string()); let changes = compute_diff(&original, &installed); @@ -446,9 +446,9 @@ mod tests { #[test] fn test_compute_diff_added() { - let original: HashMap<String, String> = HashMap::new(); + let original: IndexMap<String, String> = IndexMap::new(); - let mut installed: HashMap<String, String> = HashMap::new(); + let mut installed: IndexMap<String, String> = IndexMap::new(); installed.insert("src/NewFile.php".to_string(), "aabbcc".to_string()); let changes = compute_diff(&original, &installed); @@ -461,10 +461,10 @@ mod tests { #[test] fn test_compute_diff_removed() { - let mut original: HashMap<String, String> = HashMap::new(); + let mut original: IndexMap<String, String> = IndexMap::new(); original.insert("src/OldFile.php".to_string(), "112233".to_string()); - let installed: HashMap<String, String> = HashMap::new(); + let installed: IndexMap<String, String> = IndexMap::new(); let changes = compute_diff(&original, &installed); assert_eq!(changes.len(), 1); @@ -476,12 +476,12 @@ mod tests { #[test] fn test_compute_diff_mixed() { - let mut original: HashMap<String, String> = HashMap::new(); + let mut original: IndexMap<String, String> = IndexMap::new(); original.insert("src/Unchanged.php".to_string(), "same".to_string()); original.insert("src/Modified.php".to_string(), "old".to_string()); original.insert("src/Removed.php".to_string(), "gone".to_string()); - let mut installed: HashMap<String, String> = HashMap::new(); + let mut installed: IndexMap<String, String> = IndexMap::new(); installed.insert("src/Unchanged.php".to_string(), "same".to_string()); installed.insert("src/Modified.php".to_string(), "new".to_string()); installed.insert("src/Added.php".to_string(), "extra".to_string()); diff --git a/crates/mozart/src/commands/suggests.rs b/crates/mozart/src/commands/suggests.rs index 394778f..6d1765e 100644 --- a/crates/mozart/src/commands/suggests.rs +++ b/crates/mozart/src/commands/suggests.rs @@ -1,8 +1,10 @@ use clap::Args; +use indexmap::IndexMap; +use indexmap::IndexSet; use mozart_core::console; use mozart_core::console::Verbosity; use mozart_core::console_format; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::BTreeMap; use std::path::{Path, PathBuf}; #[derive(Args)] @@ -76,17 +78,17 @@ pub async fn execute( }; // 3. Determine direct-deps-only filter - let (package_filter, direct_deps_only): (HashSet<String>, Option<HashSet<String>>) = { + let (package_filter, direct_deps_only): (IndexSet<String>, Option<IndexSet<String>>) = { if !args.packages.is_empty() { // Filter by the explicitly named packages - let filter: HashSet<String> = args.packages.iter().map(|s| s.to_lowercase()).collect(); + let filter: IndexSet<String> = args.packages.iter().map(|s| s.to_lowercase()).collect(); (filter, None) } else if args.all { - (HashSet::new(), None) + (IndexSet::new(), None) } else { // Default: only direct deps from composer.json let direct = compute_direct_deps(&working_dir)?; - (HashSet::new(), Some(direct)) + (IndexSet::new(), Some(direct)) } }; @@ -210,7 +212,7 @@ fn collect_suggestions_from_installed( } } - let dev_names: HashSet<String> = installed + let dev_names: IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) @@ -273,11 +275,11 @@ fn collect_suggestions_from_root(working_dir: &Path) -> anyhow::Result<Vec<Sugge fn collect_installed_names_from_lock( working_dir: &Path, no_dev: bool, -) -> anyhow::Result<HashSet<String>> { +) -> anyhow::Result<IndexSet<String>> { let lock_path = working_dir.join("composer.lock"); let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; - let mut names: HashSet<String> = HashSet::new(); + let mut names: IndexSet<String> = IndexSet::new(); let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> = lock.packages.iter().collect(); @@ -309,17 +311,17 @@ fn collect_installed_names_from_lock( fn collect_installed_names_from_installed( working_dir: &Path, no_dev: bool, -) -> anyhow::Result<HashSet<String>> { +) -> anyhow::Result<IndexSet<String>> { let vendor_dir = working_dir.join("vendor"); let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; - let dev_names: HashSet<String> = installed + let dev_names: IndexSet<String> = installed .dev_package_names .iter() .map(|n| n.to_lowercase()) .collect(); - let mut names: HashSet<String> = HashSet::new(); + let mut names: IndexSet<String> = IndexSet::new(); for pkg in &installed.packages { if no_dev && dev_names.contains(&pkg.name.to_lowercase()) { @@ -356,7 +358,7 @@ fn collect_installed_names_from_installed( fn add_platform_names_from_lock( lock: &mozart_registry::lockfile::LockFile, - names: &mut HashSet<String>, + names: &mut IndexSet<String>, ) { // Collect platform keys from the lock's platform and platform_dev objects if let Some(obj) = lock.platform.as_object() { @@ -382,13 +384,13 @@ fn is_platform_package(name: &str) -> bool { // ─── Direct deps helper ─────────────────────────────────────────────────────── -fn compute_direct_deps(working_dir: &Path) -> anyhow::Result<HashSet<String>> { +fn compute_direct_deps(working_dir: &Path) -> anyhow::Result<IndexSet<String>> { let composer_json_path = working_dir.join("composer.json"); if !composer_json_path.exists() { - return Ok(HashSet::new()); + return Ok(IndexSet::new()); } let root = mozart_core::package::read_from_file(&composer_json_path)?; - let mut deps: HashSet<String> = HashSet::new(); + let mut deps: IndexSet<String> = IndexSet::new(); // Include the root package itself so its suggestions are shown if !root.name.is_empty() { deps.insert(root.name.to_lowercase()); @@ -417,7 +419,7 @@ fn sanitize_reason(reason: &str) -> String { /// If the same source suggests the same target multiple times, the last reason wins. /// This matches Composer's behavior where map insertion overwrites previous entries. fn deduplicate_suggestions(suggestions: Vec<Suggestion>) -> Vec<Suggestion> { - let mut seen: HashMap<(String, String), usize> = HashMap::new(); + let mut seen: IndexMap<(String, String), usize> = IndexMap::new(); let mut deduped: Vec<Suggestion> = Vec::new(); for s in suggestions { @@ -648,7 +650,7 @@ mod tests { ]; let refs: Vec<&Suggestion> = suggestions.iter().collect(); - let mut installed: HashSet<String> = HashSet::new(); + let mut installed: IndexSet<String> = IndexSet::new(); installed.insert("ext-intl".to_string()); installed.insert("ext-mbstring".to_string()); @@ -671,7 +673,7 @@ mod tests { ]; let refs: Vec<&Suggestion> = suggestions.iter().collect(); - let mut filter: HashSet<String> = HashSet::new(); + let mut filter: IndexSet<String> = IndexSet::new(); filter.insert("vendor/a".to_string()); filter.insert("vendor/c".to_string()); @@ -694,7 +696,7 @@ mod tests { ]; let refs: Vec<&Suggestion> = suggestions.iter().collect(); - let mut direct: HashSet<String> = HashSet::new(); + let mut direct: IndexSet<String> = IndexSet::new(); direct.insert("vendor/direct".to_string()); let filtered: Vec<&Suggestion> = refs @@ -715,7 +717,7 @@ mod tests { make_suggestion("vendor/c", "vendor/z", ""), ]; let refs: Vec<&Suggestion> = suggestions.iter().collect(); - let installed: HashSet<String> = HashSet::new(); + let installed: IndexSet<String> = IndexSet::new(); let filtered: Vec<&Suggestion> = refs .iter() @@ -755,7 +757,7 @@ mod tests { let suggestions = collect_suggestions_from_locked(working_dir, false).unwrap(); assert_eq!(suggestions.len(), 2); assert!(suggestions.iter().all(|s| s.source == "vendor/a")); - let targets: HashSet<&str> = suggestions.iter().map(|s| s.target.as_str()).collect(); + let targets: IndexSet<&str> = suggestions.iter().map(|s| s.target.as_str()).collect(); assert!(targets.contains("ext-intl")); assert!(targets.contains("vendor/optional")); } diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index 17b7c97..0c25a9e 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -1,10 +1,10 @@ use clap::Args; +use indexmap::{IndexMap, IndexSet}; use mozart_core::console; use mozart_core::console_format; use mozart_core::package::{self, Stability}; use mozart_registry::lockfile; use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest, ResolvedPackage}; -use std::collections::{HashMap, HashSet}; #[derive(Args)] pub struct UpdateArgs { @@ -198,7 +198,7 @@ pub fn compute_update_changes( dev_mode: bool, ) -> Vec<UpdateChange> { // Build map of old lock packages keyed by lowercase name -> version string - let mut old_map: HashMap<String, String> = HashMap::new(); + let mut old_map: IndexMap<String, String> = IndexMap::new(); if let Some(old) = old_lock { for pkg in &old.packages { old_map.insert(pkg.name.to_lowercase(), pkg.version.clone()); @@ -211,7 +211,7 @@ pub fn compute_update_changes( } // Build map of new lock packages keyed by lowercase name -> version string - let mut new_map: HashMap<String, String> = HashMap::new(); + let mut new_map: IndexMap<String, String> = IndexMap::new(); for pkg in &new_lock.packages { new_map.insert(pkg.name.to_lowercase(), pkg.version.clone()); } @@ -302,11 +302,11 @@ pub fn apply_partial_update( update_packages: &[String], ) -> Vec<ResolvedPackage> { // Build a set of normalized package names we want to update - let update_set: std::collections::HashSet<String> = + let update_set: indexmap::IndexSet<String> = update_packages.iter().map(|s| s.to_lowercase()).collect(); // Build a map of old locked packages by name -> (version, version_normalized, is_dev) - let mut old_pkg_map: HashMap<String, &lockfile::LockedPackage> = HashMap::new(); + let mut old_pkg_map: IndexMap<String, &lockfile::LockedPackage> = IndexMap::new(); for pkg in &old_lock.packages { old_pkg_map.insert(pkg.name.to_lowercase(), pkg); } @@ -414,7 +414,7 @@ pub fn expand_wildcards( .collect(); let mut result: Vec<String> = Vec::new(); - let mut seen: HashSet<String> = HashSet::new(); + let mut seen: IndexSet<String> = IndexSet::new(); for spec in specifiers { if spec.contains('*') { @@ -448,8 +448,8 @@ pub fn expand_wildcards( // ───────────────────────────────────────────────────────────────────────────── /// Build a lookup map from package name (lowercase) to its LockedPackage. -fn build_lock_map(lock: &lockfile::LockFile) -> HashMap<String, &lockfile::LockedPackage> { - let mut map = HashMap::new(); +fn build_lock_map(lock: &lockfile::LockFile) -> IndexMap<String, &lockfile::LockedPackage> { + let mut map = IndexMap::new(); for pkg in &lock.packages { map.insert(pkg.name.to_lowercase(), pkg); } @@ -468,7 +468,7 @@ pub fn expand_with_direct_dependencies( lock: &lockfile::LockFile, ) -> Vec<String> { let lock_map = build_lock_map(lock); - let mut result_set: HashSet<String> = packages.iter().cloned().collect(); + let mut result_set: IndexSet<String> = packages.iter().cloned().collect(); let mut result: Vec<String> = packages; for name in result.clone() { @@ -503,7 +503,7 @@ pub fn expand_with_all_dependencies( lock: &lockfile::LockFile, ) -> Vec<String> { let lock_map = build_lock_map(lock); - let mut result_set: HashSet<String> = packages.iter().cloned().collect(); + let mut result_set: IndexSet<String> = packages.iter().cloned().collect(); let mut queue: Vec<String> = packages.clone(); let mut result: Vec<String> = packages; @@ -665,7 +665,7 @@ pub fn apply_patch_only( resolved: Vec<ResolvedPackage>, old_lock: &lockfile::LockFile, ) -> Vec<ResolvedPackage> { - let mut old_pkg_map: HashMap<String, &lockfile::LockedPackage> = HashMap::new(); + let mut old_pkg_map: IndexMap<String, &lockfile::LockedPackage> = IndexMap::new(); for pkg in &old_lock.packages { old_pkg_map.insert(pkg.name.to_lowercase(), pkg); } @@ -806,7 +806,7 @@ pub async fn run( let dev_mode = !args.no_dev; // Fix 1C + Fix 2: Parse --with constraints and inline constraint shorthand. - let mut temporary_constraints: HashMap<String, String> = HashMap::new(); + let mut temporary_constraints: IndexMap<String, String> = IndexMap::new(); // Parse --with constraints (format: "vendor/package:constraint") for with_entry in &args.with { @@ -887,7 +887,7 @@ pub async fn run( require_dev, include_dev: dev_mode, minimum_stability, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable, prefer_lowest: args.prefer_lowest, platform, @@ -1166,7 +1166,7 @@ pub async fn run( let bump_require_dev = mode == "all" || mode == "dev"; // Build locked versions map from the new lock - let mut locked_versions: HashMap<String, (String, Option<String>)> = HashMap::new(); + let mut locked_versions: IndexMap<String, (String, Option<String>)> = IndexMap::new(); for pkg in &new_lock.packages { locked_versions.insert( pkg.name.to_lowercase(), @@ -1997,7 +1997,7 @@ mod tests { require_dev: vec![], include_dev: false, minimum_stability: Stability::Stable, - stability_flags: HashMap::new(), + stability_flags: IndexMap::new(), prefer_stable: true, prefer_lowest: false, platform: PlatformConfig::new(), @@ -2011,10 +2011,10 @@ mod tests { ), ), ), - temporary_constraints: HashMap::new(), + temporary_constraints: IndexMap::new(), raw_repositories: vec![], - root_provide: HashMap::new(), - root_replace: HashMap::new(), + root_provide: IndexMap::new(), + root_replace: IndexMap::new(), }; let resolved = resolve(&request).await.expect("Resolution should succeed"); diff --git a/crates/mozart/src/commands/validate.rs b/crates/mozart/src/commands/validate.rs index cec36b5..e2345c8 100644 --- a/crates/mozart/src/commands/validate.rs +++ b/crates/mozart/src/commands/validate.rs @@ -421,7 +421,7 @@ fn check_scripts_orphans( obj: &serde_json::Map<String, serde_json::Value>, result: &mut ValidationResult, ) { - let script_keys: std::collections::HashSet<&str> = obj + let script_keys: indexmap::IndexSet<&str> = obj .get("scripts") .and_then(|v| v.as_object()) .map(|m| m.keys().map(|k| k.as_str()).collect()) |
