Skip to content

sort: add locale-aware month sorting (-M)#11445

Open
sylvestre wants to merge 2 commits intouutils:mainfrom
sylvestre:sort-month
Open

sort: add locale-aware month sorting (-M)#11445
sylvestre wants to merge 2 commits intouutils:mainfrom
sylvestre:sort-month

Conversation

@sylvestre
Copy link
Contributor

No description provided.

@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/tail/inotify-dir-recreate (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!
Congrats! The gnu test tests/rm/many-dir-entries-vs-OOM is now passing!

@github-actions
Copy link

GNU testsuite comparison:

GNU test failed: tests/rm/isatty. tests/rm/isatty is passing on 'main'. Maybe you have to rebase?
Skipping an intermittent issue tests/pr/bounded-memory (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/tail/inotify-dir-recreate (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!
Congrats! The gnu test tests/rm/many-dir-entries-vs-OOM is now passing!

@sylvestre sylvestre force-pushed the sort-month branch 2 times, most recently from 3377348 to aa3ca32 Compare March 21, 2026 22:39
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/date/date-locale-hour (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/tail/inotify-dir-recreate (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!
Congrats! The gnu test tests/rm/many-dir-entries-vs-OOM is now passing!

@github-actions
Copy link

GNU testsuite comparison:

GNU test failed: tests/tail/retry. tests/tail/retry is passing on 'main'. Maybe you have to rebase?
Skipping an intermittent issue tests/pr/bounded-memory (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/tail/inotify-dir-recreate (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/tty/tty-eof (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!
Note: The gnu test tests/basenc/bounded-memory is now being skipped but was previously passing.

@github-actions
Copy link

GNU testsuite comparison:

GNU test failed: tests/rm/isatty. tests/rm/isatty is passing on 'main'. Maybe you have to rebase?
Skipping an intermittent issue tests/tail/follow-name (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/tail/inotify-dir-recreate (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/tty/tty-eof (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!
Congrats! The gnu test tests/rm/many-dir-entries-vs-OOM is now passing!

@sylvestre sylvestre force-pushed the sort-month branch 2 times, most recently from 146a915 to afbe750 Compare March 22, 2026 16:08
@github-actions
Copy link

GNU testsuite comparison:

GNU test failed: tests/misc/io-errors. tests/misc/io-errors is passing on 'main'. Maybe you have to rebase?
Skip an intermittent issue tests/date/date-locale-hour (fails in this run but passes in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!
Note: The gnu test tests/seq/seq-epipe is now being skipped but was previously passing.
Congrats! The gnu test tests/basenc/bounded-memory is now passing!
Congrats! The gnu test tests/tail/pipe-f is now passing!
Skip an intermittent issue tests/pr/bounded-memory (was skipped on 'main', now failing)

@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/inotify-dir-recreate (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/date/date-locale-hour (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!

@sylvestre sylvestre marked this pull request as ready for review March 22, 2026 18:53
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/symlink (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/date/date-locale-hour (passes in this run but fails in the 'main' branch)
Skipping an intermittent issue tests/pr/bounded-memory (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!
Note: The gnu test tests/seq/seq-epipe is now being skipped but was previously passing.

@github-actions
Copy link

GNU testsuite comparison:

Skipping an intermittent issue tests/tty/tty-eof (passes in this run but fails in the 'main' branch)
Congrats! The gnu test tests/sort/sort-month is no longer failing!

}
let text = String::from_utf8(output.stdout).ok()?;
let months: Vec<String> = text.trim().split(';').map(String::from).collect();
if months.len() == 12 && months.iter().all(|m| !m.is_empty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would do the check for emptiness with a filter as part of the chain on the previous line.

Comment on lines +640 to +643
let expected = months.iter().fold(String::new(), |mut s, m| {
writeln!(s, "{m}").unwrap();
s
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using join might be easier to read:

Suggested change
let expected = months.iter().fold(String::new(), |mut s, m| {
writeln!(s, "{m}").unwrap();
s
});
let expected = months.join("\n") + "\n";

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just wondering: are those tests ever run in the CI?

let initial_selection = &self.line[selection.clone()];

let mut month_chars = initial_selection
let trimmed = initial_selection
Copy link
Contributor

@cakebaker cakebaker Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name trimmed is a bit misleading as it contains a position (and not a trimmed string).

Comment on lines +803 to +806
trimmed..trimmed
} else {
// We parsed a month. Match the first three non-whitespace characters, which must be the month we parsed.
month_chars.next().unwrap().0
..month_chars
.nth(2)
.map_or(initial_selection.len(), |(idx, _)| idx)
// We parsed a month. Use the actual match byte length.
trimmed..(trimmed + match_len)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might make sense to set selection.start and selection.end directly in the if/else blocks instead of returning a range.

/// Parse the beginning string into a Month, returning [`Month::Unknown`] on errors.
fn month_parse(line: &[u8]) -> Month {
/// Also returns the byte length consumed from the input (after leading blanks).
fn month_parse(line: &[u8]) -> (Month, usize) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function is not entirely correct.

Here is an example where I get a different output compared to GNU sort:

$ printf "juin\nav   ril\nmars\nfevr." | LC_ALL=fr_FR.UTF-8 cargo run -q sort -M
fevr.
mars
av   ril
juin
$ printf "juin\nav   ril\nmars\nfevr." | LC_ALL=fr_FR.UTF-8 sort -M
av   ril
fevr.
mars
juin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants