this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 347 lines 7.4 kB view raw
1//! This file is a port of C implementaion that can be found here 2//! https://github.com/sourcefrog/natsort. 3const std = @import("std"); 4const isSpace = std.ascii.isWhitespace; 5const isDigit = std.ascii.isDigit; 6const Order = std.math.Order; 7const testing = std.testing; 8 9pub fn order(a: []const u8, b: []const u8) Order { 10 return natOrder(a, b, false); 11} 12 13pub fn orderIgnoreCase(a: []const u8, b: []const u8) Order { 14 return natOrder(a, b, true); 15} 16 17fn natOrder(a: []const u8, b: []const u8, comptime fold_case: bool) Order { 18 var ai: usize = 0; 19 var bi: usize = 0; 20 21 while (true) : ({ 22 ai += 1; 23 bi += 1; 24 }) { 25 var ca = if (ai == a.len) 0 else a[ai]; 26 var cb = if (bi == b.len) 0 else b[bi]; 27 28 while (isSpace(ca)) { 29 ai += 1; 30 ca = if (ai == a.len) 0 else a[ai]; 31 } 32 33 while (isSpace(cb)) { 34 bi += 1; 35 cb = if (bi == b.len) 0 else b[bi]; 36 } 37 38 if (isDigit(ca) and isDigit(cb)) { 39 const fractional = ca == '0' or cb == '0'; 40 41 if (fractional) { 42 const result = compareLeft(a[ai..], b[bi..]); 43 if (result != .eq) return result; 44 } else { 45 const result = compareRight(a[ai..], b[bi..]); 46 if (result != .eq) return result; 47 } 48 } 49 50 if (ca == 0 and cb == 0) { 51 return .eq; 52 } 53 54 if (fold_case) { 55 ca = std.ascii.toUpper(ca); 56 cb = std.ascii.toUpper(cb); 57 } 58 59 if (ca < cb) { 60 return .lt; 61 } 62 63 if (ca > cb) { 64 return .gt; 65 } 66 } 67} 68 69fn compareLeft(a: []const u8, b: []const u8) Order { 70 var i: usize = 0; 71 while (true) : (i += 1) { 72 const ca = if (i == a.len) 0 else a[i]; 73 const cb = if (i == b.len) 0 else b[i]; 74 75 if (!isDigit(ca) and !isDigit(cb)) { 76 return .eq; 77 } 78 if (!isDigit(ca)) { 79 return .lt; 80 } 81 if (!isDigit(cb)) { 82 return .gt; 83 } 84 if (ca < cb) { 85 return .lt; 86 } 87 if (ca > cb) { 88 return .gt; 89 } 90 } 91 92 return .eq; 93} 94 95fn compareRight(a: []const u8, b: []const u8) Order { 96 var bias = Order.eq; 97 98 var i: usize = 0; 99 while (true) : (i += 1) { 100 const ca = if (i == a.len) 0 else a[i]; 101 const cb = if (i == b.len) 0 else b[i]; 102 103 if (!isDigit(ca) and !isDigit(cb)) { 104 return bias; 105 } 106 if (!isDigit(ca)) { 107 return .lt; 108 } 109 if (!isDigit(cb)) { 110 return .gt; 111 } 112 113 if (ca < cb) { 114 if (bias != .eq) { 115 bias = .lt; 116 } 117 } else if (ca > cb) { 118 if (bias != .eq) { 119 bias = .gt; 120 } 121 } else if (ca == 0 and cb == 0) { 122 return bias; 123 } 124 } 125 126 return .eq; 127} 128 129const SortContext = struct { 130 ignore_case: bool = false, 131 reverse: bool = false, 132 133 fn compare(self: @This(), a: []const u8, b: []const u8) bool { 134 const ord: std.math.Order = if (self.reverse) .gt else .lt; 135 if (self.ignore_case) { 136 return orderIgnoreCase(a, b) == ord; 137 } else { 138 return order(a, b) == ord; 139 } 140 } 141}; 142 143test "lt" { 144 try testing.expectEqual(Order.lt, order("a_1", "a_10")); 145} 146 147test "eq" { 148 try testing.expectEqual(Order.eq, order("a_1", "a_1")); 149} 150 151test "gt" { 152 try testing.expectEqual(Order.gt, order("a_10", "a_1")); 153} 154 155fn sortAndAssert(context: SortContext, input: [][]const u8, want: []const []const u8) !void { 156 std.sort.pdq([]const u8, input, context, SortContext.compare); 157 158 for (input, want) |actual, expected| { 159 try testing.expectEqualStrings(expected, actual); 160 } 161} 162 163test "sorting" { 164 const context = SortContext{}; 165 var items = [_][]const u8{ 166 "item100", 167 "item10", 168 "item1", 169 }; 170 const want = [_][]const u8{ 171 "item1", 172 "item10", 173 "item100", 174 }; 175 176 try sortAndAssert(context, &items, &want); 177} 178 179test "sorting 2" { 180 const context = SortContext{}; 181 var items = [_][]const u8{ 182 "item_30", 183 "item_15", 184 "item_3", 185 "item_2", 186 "item_10", 187 }; 188 const want = [_][]const u8{ 189 "item_2", 190 "item_3", 191 "item_10", 192 "item_15", 193 "item_30", 194 }; 195 196 try sortAndAssert(context, &items, &want); 197} 198 199test "leading zeros" { 200 const context = SortContext{}; 201 var items = [_][]const u8{ 202 "item100", 203 "item999", 204 "item001", 205 "item010", 206 "item000", 207 }; 208 const want = [_][]const u8{ 209 "item000", 210 "item001", 211 "item010", 212 "item100", 213 "item999", 214 }; 215 216 try sortAndAssert(context, &items, &want); 217} 218 219test "dates" { 220 const context = SortContext{}; 221 var items = [_][]const u8{ 222 "2000-1-10", 223 "2000-1-2", 224 "1999-12-25", 225 "2000-3-23", 226 "1999-3-3", 227 }; 228 const want = [_][]const u8{ 229 "1999-3-3", 230 "1999-12-25", 231 "2000-1-2", 232 "2000-1-10", 233 "2000-3-23", 234 }; 235 236 try sortAndAssert(context, &items, &want); 237} 238 239test "fractions" { 240 const context = SortContext{}; 241 var items = [_][]const u8{ 242 "Fractional release numbers", 243 "1.011.02", 244 "1.010.12", 245 "1.009.02", 246 "1.009.20", 247 "1.009.10", 248 "1.002.08", 249 "1.002.03", 250 "1.002.01", 251 }; 252 const want = [_][]const u8{ 253 "1.002.01", 254 "1.002.03", 255 "1.002.08", 256 "1.009.02", 257 "1.009.10", 258 "1.009.20", 259 "1.010.12", 260 "1.011.02", 261 "Fractional release numbers", 262 }; 263 264 try sortAndAssert(context, &items, &want); 265} 266 267test "words" { 268 const context = SortContext{}; 269 var items = [_][]const u8{ 270 "fred", 271 "pic2", 272 "pic100a", 273 "pic120", 274 "pic121", 275 "jane", 276 "tom", 277 "pic02a", 278 "pic3", 279 "pic4", 280 "1-20", 281 "pic100", 282 "pic02000", 283 "10-20", 284 "1-02", 285 "1-2", 286 "x2-y7", 287 "x8-y8", 288 "x2-y08", 289 "x2-g8", 290 "pic01", 291 "pic02", 292 "pic 6", 293 "pic 7", 294 "pic 5", 295 "pic05", 296 "pic 5 ", 297 "pic 5 something", 298 "pic 4 else", 299 }; 300 const want = [_][]const u8{ 301 "1-02", 302 "1-2", 303 "1-20", 304 "10-20", 305 "fred", 306 "jane", 307 "pic01", 308 "pic02", 309 "pic02a", 310 "pic02000", 311 "pic05", 312 "pic2", 313 "pic3", 314 "pic4", 315 "pic 4 else", 316 "pic 5", 317 "pic 5 ", 318 "pic 5 something", 319 "pic 6", 320 "pic 7", 321 "pic100", 322 "pic100a", 323 "pic120", 324 "pic121", 325 "tom", 326 "x2-g8", 327 "x2-y08", 328 "x2-y7", 329 "x8-y8", 330 }; 331 332 try sortAndAssert(context, &items, &want); 333} 334 335test "fuzz" { 336 const Context = struct { 337 fn testOne(context: @This(), input: []const u8) anyerror!void { 338 _ = context; 339 340 const a = input[0..(input.len / 2)]; 341 const b = input[(input.len / 2)..]; 342 _ = order(a, b); 343 } 344 }; 345 346 try std.testing.fuzz(Context{}, Context.testOne, .{}); 347}