Procházet zdrojové kódy

Implement natural sorting on filenames

Ben S před 11 roky
rodič
revize
4722c8991b
4 změnil soubory, kde provedl 59 přidání a 6 odebrání
  1. 1 0
      exa.rs
  2. 8 5
      file.rs
  3. 1 1
      options.rs
  4. 49 0
      sort.rs

+ 1 - 0
exa.rs

@@ -14,6 +14,7 @@ pub mod format;
 pub mod file;
 pub mod unix;
 pub mod options;
+pub mod sort;
 
 fn main() {
     let args = os::args();

+ 8 - 5
file.rs

@@ -5,6 +5,7 @@ use colours::{Plain, Style, Black, Red, Green, Yellow, Blue, Purple, Cyan};
 use column::{Column, Permissions, FileName, FileSize, User, Group};
 use format::{format_metric_bytes, format_IEC_bytes};
 use unix::{get_user_name, get_group_name};
+use sort::SortPart;
 
 static MEDIA_TYPES: &'static [&'static str] = &[
     "png", "jpeg", "jpg", "gif", "bmp", "tiff", "tif",
@@ -27,6 +28,7 @@ pub struct File<'a> {
     pub ext:  Option<&'a str>,
     pub path: &'a Path,
     pub stat: io::FileStat,
+    pub parts: Vec<SortPart<'a>>,
 }
 
 impl<'a> File<'a> {
@@ -44,10 +46,11 @@ impl<'a> File<'a> {
         };
 
         return File {
-            path: path,
-            stat: stat,
-            name: filename,
-            ext:  File::ext(filename),
+            path:  path,
+            stat:  stat,
+            name:  filename,
+            ext:   File::ext(filename),
+            parts: SortPart::split_into_parts(filename),
         };
     }
 
@@ -66,7 +69,7 @@ impl<'a> File<'a> {
     pub fn display(&self, column: &Column) -> String {
         match *column {
             Permissions => self.permissions_string(),
-            FileName => self.file_colour().paint(self.name.as_slice()),
+            FileName => self.file_colour().paint(self.name),
             FileSize(use_iec) => self.file_size(use_iec),
 
             // Display the ID if the user/group doesn't exist, which

+ 1 - 1
options.rs

@@ -80,7 +80,7 @@ impl Options {
             .collect();
 
         match self.sortField {
-            Name => files.sort_by(|a, b| a.name.cmp(&b.name)),
+            Name => files.sort_by(|a, b| a.parts.cmp(&b.parts)),
             Size => files.sort_by(|a, b| a.stat.size.cmp(&b.stat.size)),
             Extension => files.sort_by(|a, b| {
                 let exts = a.ext.cmp(&b.ext);

+ 49 - 0
sort.rs

@@ -0,0 +1,49 @@
+// This is an implementation of "natural sort order". See
+// http://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
+// for more information and examples. It tries to sort "9" before
+// "10", which makes sense to those regular human types.
+
+// It works by splitting an input string into several parts, and then
+// comparing based on those parts. A SortPart derives TotalOrd, so a
+// Vec<SortPart> will automatically have natural sorting.
+
+#[deriving(Eq, Ord, TotalEq, TotalOrd)]
+pub enum SortPart<'a> {
+    Stringular(&'a str),
+    Numeric(u32),
+}
+
+impl<'a> SortPart<'a> {
+    pub fn from_string(is_digit: bool, slice: &'a str) -> SortPart<'a> {
+        if is_digit {
+            Numeric(from_str::<u32>(slice).unwrap())
+        } else {
+            Stringular(slice)
+        }
+    }
+
+    // The logic here is taken from my question at
+    // http://stackoverflow.com/q/23969191/3484614
+
+    pub fn split_into_parts<'a>(input: &'a str) -> Vec<SortPart<'a>> {
+        let mut parts = vec![];
+
+        if input.is_empty() {
+            return parts
+        }
+
+        let mut is_digit = input.char_at(0).is_digit();
+        let mut start = 0;
+
+        for (i, c) in input.char_indices() {
+            if is_digit != c.is_digit() {
+                parts.push(SortPart::from_string(is_digit, input.slice(start, i)));
+                is_digit = !is_digit;
+                start = i;
+            }
+        }
+
+        parts.push(SortPart::from_string(is_digit, input.slice_from(start)));
+        parts
+    }
+}