Skip to content

Commit f055618

Browse files
authored
add configurable dependency sorting (#5)
1 parent e3fcf55 commit f055618

File tree

6 files changed

+103
-12
lines changed

6 files changed

+103
-12
lines changed

src/cargo/manifest_writer.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,19 @@ impl Default for ManifestWriter {
2121
}
2222

2323
impl ManifestWriter {
24-
/// Creates a new manifest writer
24+
/// Creates a new manifest writer with default settings
2525
pub fn new() -> Self {
2626
Self {
2727
formatter: TomlFormatter::new(),
2828
}
2929
}
3030

31+
/// Sets whether to sort dependencies when writing manifests
32+
pub fn with_dependency_sort(mut self, sort: bool) -> Self {
33+
self.formatter.sort_dependencies = sort;
34+
self
35+
}
36+
3137
/// Write unified dependencies to workspace Cargo.toml
3238
///
3339
/// IMPORTANT: This MERGES new deps with existing workspace.dependencies.

src/commands/unify.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,12 @@ pub fn run_unify_apply(
315315
created_backup_id = Some(backup_id);
316316
}
317317

318-
let writer = ManifestWriter::new();
318+
let sort_mode = ctx
319+
.config
320+
.as_ref()
321+
.map(|c| c.unify.sort_dependencies)
322+
.unwrap_or(true);
323+
let writer = ManifestWriter::new().with_dependency_sort(sort_mode);
319324

320325
if !plan.workspace_deps.is_empty() {
321326
progress!("writing [workspace.dependencies]...");

src/config/schema.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ pub const SYNCABLE_FIELDS: &[FieldSpec] = &[
140140
default_toml: "3",
141141
comment: "Number of backup files to keep (default: 3)",
142142
},
143+
FieldSpec {
144+
section: "unify",
145+
key: "sort_dependencies",
146+
default_toml: "true",
147+
comment: "Sort deps alphabetically (default: true)",
148+
},
143149
// =========================================================================
144150
// [release] section - 10 fields
145151
// =========================================================================
@@ -270,7 +276,7 @@ mod tests {
270276
#[test]
271277
fn test_fields_for_section() {
272278
let unify_fields: Vec<_> = fields_for_section("unify").collect();
273-
assert_eq!(unify_fields.len(), 19); // 16 + detect_undeclared_features + fix_undeclared_features + skip_undeclared_patterns
279+
assert_eq!(unify_fields.len(), 20); // 19 + sort_dependencies
274280
assert!(unify_fields.iter().all(|f| f.section == "unify"));
275281

276282
let release_fields: Vec<_> = fields_for_section("release").collect();
@@ -286,7 +292,7 @@ mod tests {
286292
// Update this count when adding new fields
287293
assert_eq!(
288294
SYNCABLE_FIELDS.len(),
289-
30, // 19 unify + 10 release + 1 change-detection
295+
31, // 20 unify + 10 release + 1 change-detection
290296
"Total syncable fields count changed - update this test if intentional"
291297
);
292298
}

src/config/unify.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ pub struct UnifyConfig {
118118
/// These are features that are typically not actionable or are implementation details.
119119
#[serde(default = "default_skip_undeclared_patterns")]
120120
pub skip_undeclared_patterns: Vec<String>,
121+
122+
/// Sort dependencies alphabetically when writing Cargo.toml files (default: true)
123+
/// When false, preserves existing order and appends new deps at end.
124+
#[serde(default = "default_true")]
125+
pub sort_dependencies: bool,
121126
}
122127

123128
impl Default for UnifyConfig {
@@ -142,6 +147,7 @@ impl Default for UnifyConfig {
142147
detect_undeclared_features: true,
143148
fix_undeclared_features: true,
144149
skip_undeclared_patterns: default_skip_undeclared_patterns(),
150+
sort_dependencies: true,
145151
}
146152
}
147153
}
@@ -955,4 +961,41 @@ mod tests {
955961
assert!(!config.should_skip_undeclared_feature("std"));
956962
assert!(!config.should_skip_undeclared_feature("anything"));
957963
}
964+
965+
// ============================================================================
966+
// sort_dependencies Tests
967+
// ============================================================================
968+
969+
#[test]
970+
fn test_sort_dependencies_default() {
971+
let config = UnifyConfig::default();
972+
assert!(config.sort_dependencies); // Default is true (alphabetical)
973+
}
974+
975+
#[test]
976+
fn test_sort_dependencies_parsing_true() {
977+
let toml = r#"sort_dependencies = true"#;
978+
let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
979+
assert!(config.sort_dependencies);
980+
}
981+
982+
#[test]
983+
fn test_sort_dependencies_parsing_false() {
984+
let toml = r#"sort_dependencies = false"#;
985+
let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
986+
assert!(!config.sort_dependencies);
987+
}
988+
989+
#[test]
990+
fn test_sort_dependencies_with_other_options() {
991+
let toml = r#"
992+
detect_unused = true
993+
remove_unused = true
994+
sort_dependencies = false
995+
"#;
996+
let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
997+
assert!(config.detect_unused);
998+
assert!(config.remove_unused);
999+
assert!(!config.sort_dependencies);
1000+
}
9581001
}

src/toml/builder.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,12 @@ impl RailConfigBuilder {
157157
}
158158
content.push_str(&format!("max_backups = {}\n", config.max_backups));
159159

160+
// === Formatting ===
161+
content.push_str(&format!(
162+
"sort_dependencies = {} # false to preserve existing order\n",
163+
config.sort_dependencies
164+
));
165+
160166
self.sections.push(format!("[unify]\n{}", content));
161167

162168
self
@@ -253,13 +259,16 @@ impl RailConfigBuilder {
253259
pub struct WorkspaceDepsBuilder {
254260
formatter: TomlFormatter,
255261
deps: Vec<(String, String, Option<String>)>, // Name, Value (formatted), Optional comment
262+
/// Whether to sort dependencies alphabetically (default: true)
263+
pub sort_dependencies: bool,
256264
}
257265

258266
impl Default for WorkspaceDepsBuilder {
259267
fn default() -> Self {
260268
Self {
261269
formatter: TomlFormatter::new(),
262270
deps: Vec::new(),
271+
sort_dependencies: true,
263272
}
264273
}
265274
}
@@ -270,6 +279,13 @@ impl WorkspaceDepsBuilder {
270279
Self::default()
271280
}
272281

282+
/// Set whether to sort dependencies alphabetically (default: true)
283+
pub fn with_dependency_sort(mut self, sort: bool) -> Self {
284+
self.sort_dependencies = sort;
285+
self.formatter.sort_dependencies = sort;
286+
self
287+
}
288+
273289
/// Add dependency
274290
pub fn add(&mut self, name: &str, value: &str) -> &mut Self {
275291
self.deps.push((name.to_string(), value.to_string(), None));
@@ -302,11 +318,15 @@ impl WorkspaceDepsBuilder {
302318
pub fn build(&self) -> RailResult<String> {
303319
let mut content = String::from("\n[workspace.dependencies]\n");
304320

305-
// Sort dependencies
306-
let mut sorted_deps = self.deps.clone();
307-
sorted_deps.sort_by(|a, b| a.0.cmp(&b.0));
321+
let deps_to_write = if self.sort_dependencies {
322+
let mut sorted = self.deps.clone();
323+
sorted.sort_by(|a, b| a.0.cmp(&b.0));
324+
sorted
325+
} else {
326+
self.deps.clone()
327+
};
308328

309-
for (name, value, comment) in sorted_deps {
329+
for (name, value, comment) in deps_to_write {
310330
if let Some(c) = comment {
311331
content.push_str(&format!("{} = {} # {}\n", name, value, c));
312332
} else {

src/toml/format.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ pub struct TomlFormatter {
1515
pub inline_feature_threshold: usize,
1616
/// Indentation string (usually 2 spaces)
1717
pub indent: &'static str,
18+
/// Whether to sort dependencies alphabetically (true) or preserve order (false)
19+
pub sort_dependencies: bool,
1820
}
1921

2022
impl Default for TomlFormatter {
@@ -23,6 +25,7 @@ impl Default for TomlFormatter {
2325
inline_array_threshold: 4,
2426
inline_feature_threshold: 10,
2527
indent: " ",
28+
sort_dependencies: true,
2629
}
2730
}
2831
}
@@ -158,11 +161,11 @@ impl TomlFormatter {
158161
/// Format a Cargo.toml document in-place
159162
///
160163
/// Applies standard formatting rules:
161-
/// 1. Sorts dependencies
164+
/// 1. Sorts dependencies (if dependency_sort is Alphabetical)
162165
/// 2. Standardizes table format (inline vs block)
163166
pub fn format_manifest(&self, doc: &mut DocumentMut) -> RailResult<()> {
164-
// 1. Sort dependencies alphabetically
165-
self.sort_dependencies(doc);
167+
// 1. Sort dependencies (only if configured to sort alphabetically)
168+
self.sort_deps(doc);
166169

167170
// 2. Standardize table formatting (inline vs block)
168171
self.standardize_tables(doc);
@@ -171,7 +174,15 @@ impl TomlFormatter {
171174
}
172175

173176
/// Sort dependencies in all dependency sections
174-
fn sort_dependencies(&self, doc: &mut DocumentMut) {
177+
///
178+
/// Only sorts if `sort_dependencies` is true.
179+
/// When false, existing order is maintained.
180+
fn sort_deps(&self, doc: &mut DocumentMut) {
181+
// Skip sorting if configured to preserve existing order
182+
if !self.sort_dependencies {
183+
return;
184+
}
185+
175186
let sections = [
176187
"dependencies",
177188
"dev-dependencies",

0 commit comments

Comments
 (0)