Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 1 addition & 15 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
- name: Set Git origin
run: |
git config --global user.name "github-actions"
git config --global user.email "github-actions@github.com"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
Expand All @@ -34,9 +25,4 @@ jobs:
run: |
deno install --allow-scripts
deno lint --fix
deno fmt
- name: Push formatting changes (if any)
run: |
git diff --quiet || (
git commit -a -m "style: code formatting" &&
git push)
deno fmt --check
9 changes: 7 additions & 2 deletions convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function pathObjectFrom(
let root: PathRecord = {};

for (const element of iterable) {
let [next, other, isLast] = shiftPath(element);
let { next, other, isLast } = shiftPath(element);

let parent: PathRecord = root;
if (isLast) {
Expand All @@ -145,7 +145,12 @@ export function pathObjectFrom(

while (!isLast || (parent[next] = "")) {
parent = child as { [key: string]: PathRecord };
[next, other, isLast] = shiftPath(other);
const {
next: nextNew,
other: otherNew,
isLast: isLastNew,
} = shiftPath(other);
[next, other, isLast] = [nextNew, otherNew, isLastNew];
child = parent[next] ?? {};
parent[next] = child;
}
Expand Down
19 changes: 16 additions & 3 deletions shift.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export type ShiftResult = {
next: string;
other: string;
isLast: boolean;
};

/**
* @example
* "path/to/the/file" -> ["path", "to/the/file", false]
Expand All @@ -6,10 +12,17 @@
*/
export function shiftPath(
p: string,
): [next: string, other: string, isLast: boolean] {
): ShiftResult {
const slashIndex = p.search(/[/\\]/);
const next = p.slice(0, Math.max(0, slashIndex));
const other = p.slice(Math.max(0, slashIndex + 1));
const isLast = next === "";
return [slashIndex < 0 ? other : next, other, isLast];
const r: ShiftResult = {
next: next,
other: other,
isLast: next == "",
};
if (slashIndex < 0) {
r.next = r.other;
}
return r;
}
93 changes: 43 additions & 50 deletions sort-cmp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,21 @@ export function isSortName(value: unknown): value is SortName {
* Folders are displayed before files.
*/
export function cmpFirstFolders(a: string, b: string): number {
if (a === b) return 0;
let comp = 0;
for (; comp === 0;) {
const [next1, post1, last1] = shiftPath(a);
while (comp === 0) {
const { next: next1, other: post1, isLast: last1 } = shiftPath(a);
a = post1;
const [next2, post2, last2] = shiftPath(b);
const { next: next2, other: post2, isLast: last2 } = shiftPath(b);
b = post2;
comp = cmpMixed(next1, next2);
if (last1 || last2) {
if (last1 === last2) {
break;
}

if (!last1) {
return -1;
}
comp = cmpMixed(next1, next2);

return +1;
}
if (comp) break;
if (!last1 && !last2) continue;
if (last1 && last2) break;
if (!last1) return -1;
return +1;
}

return comp;
Expand All @@ -73,24 +70,21 @@ export function cmpFirstFolders(a: string, b: string): number {
* Files are displayed before folders.
*/
export function cmpFirstFiles(a: string, b: string): number {
if (a === b) return 0;
let comp = 0;
for (; comp === 0;) {
const [next1, post1, last1] = shiftPath(a);
while (comp === 0) {
const { next: next1, other: post1, isLast: last1 } = shiftPath(a);
a = post1;
const [next2, post2, last2] = shiftPath(b);
const { next: next2, other: post2, isLast: last2 } = shiftPath(b);
b = post2;
comp = cmpMixed(next1, next2);
if (last1 || last2) {
if (last1 === last2) {
break;
}

if (last1) {
return -1;
}
comp = cmpMixed(next1, next2);

return +1;
}
if (comp) break;
if (!last1 && !last2) continue;
if (last1 && last2) break;
if (last1) return -1;
return +1;
}

return comp;
Expand All @@ -106,49 +100,47 @@ export function cmpModified(
timea: number = 0,
timeb: number = 0,
): number {
if (a === b) {
return 0;
}

while (true) {
const [next1, post1, last1] = shiftPath(a);
let comp = 0;
while (comp === 0) {
const { next: next1, other: post1, isLast: last1 } = shiftPath(a);
a = post1;
const [next2, post2, last2] = shiftPath(b);
const { next: next2, other: post2, isLast: last2 } = shiftPath(b);
b = post2;

let comp = (timeb - timea) || cmpFirstFolders(next1, next2);
if (comp) return comp;
comp = timeb - timea || cmpMixed(next1, next2);

if (last1) return +1;
if (last2) return -1;
if (comp) break;
if (!last1 && !last2) continue;
if (last1 && last2) break;
if (!last1) return -1;
return +1;
}

return comp || cmpFirstFolders(a, b);
}

/**
* Files and folders are grouped by extension type then sorted by thir names.
* Folders are displayed before files.
*/
export function cmpFileType(a: string, b: string): number {
if (a === b) return 0;
let comp = 0;
for (; comp === 0;) {
const [next1, post1, last1] = shiftPath(a);
while (comp === 0) {
const { next: next1, other: post1, isLast: last1 } = shiftPath(a);
a = post1;
const [next2, post2, last2] = shiftPath(b);
const { next: next2, other: post2, isLast: last2 } = shiftPath(b);
b = post2;

const ppa = path.parse(next1);
const ppb = path.parse(next2);
comp = cmpMixed(ppa.ext, ppb.ext) || cmpMixed(ppa.name, ppb.name);
if (last1 || last2) {
if (last1 === last2) {
break;
}

if (!last1) {
return -1;
}

return +1;
}
if (comp) break;
if (!last1 && !last2) continue;
if (last1 && last2) break;
if (!last1) return -1;
return +1;
}

return comp;
Expand All @@ -159,5 +151,6 @@ export function cmpFileType(a: string, b: string): number {
* Files are interwoven with folders.
*/
export function cmpMixed(a: string, b: string): number {
if (a === b) return 0;
return a.localeCompare(b, undefined, { ignorePunctuation: false });
}
72 changes: 41 additions & 31 deletions sort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ import * as sort from "./sort.ts";
import { shiftPath } from "./shift.ts";

Deno.test("shiftPath - examples", () => {
assertEquals(shiftPath("path/to/the/file"), ["path", "to/the/file", false]);
assertEquals(shiftPath("file"), ["file", "file", true]);
assertEquals(shiftPath("file/"), ["file", "", false]);
assertEquals(shiftPath("path/to/the/file"), {
next: "path",
other: "to/the/file",
isLast: false,
});
assertEquals(shiftPath("file"), {
next: "file",
other: "file",
isLast: true,
});
assertEquals(shiftPath("file/"), { next: "file", other: "", isLast: false });
});

Deno.test("sort.isSortName - true/false", () => {
Expand All @@ -19,30 +27,36 @@ Deno.test("sort.isSortName - true/false", () => {
});

Deno.test("mixed comparator orders strings", () => {
const a = sort.cmpMixed("a", "b");
const b = sort.cmpMixed("b", "a");
assertLess(a, 0);
assertGreater(b, 0);
assertLess(sort.cmpMixed("a", "b"), 0);
assertGreater(sort.cmpMixed("b", "a"), 0);
assertEquals(sort.cmpMixed("same", "same"), 0);
});

Deno.test("type comparator groups by extension then name", () => {
const cmp = sort.cmpFileType("file.md", "file.txt");
assertLess(cmp, 0);
const cmp2 = sort.cmpFileType("a.txt", "b.txt");
assertLess(cmp2, 0);
assertLess(sort.cmpFileType("file.md", "file.txt"), 0);
assertLess(sort.cmpFileType("a.txt", "b.txt"), 0);
});

Deno.test("sort.cmpFirstFolders puts folders before files", () => {
const arr = ["dir", "dir/file"];
arr.sort(sort.cmpFirstFolders);
assertEquals(arr, ["dir/file", "dir"]);
assertEquals(["dir", "dir/file"].sort(sort.cmpFirstFolders), [
"dir/file",
"dir",
]);
assertEquals(
["src/targets/yarn.ts", "src/...+16"].sort(sort.cmpFirstFolders),
["src/...+16", "src/targets/yarn.ts"],
);
});

Deno.test("sort.cmpFirstFiles puts files before folders", () => {
const arr = ["dir", "dir/file"];
arr.sort(sort.cmpFirstFiles);
assertEquals(arr, ["dir", "dir/file"]);
assertEquals(["dir", "dir/file"].sort(sort.cmpFirstFiles), [
"dir",
"dir/file",
]);
assertEquals(["src/targets/yarn.ts", "src/...+16"].sort(sort.cmpFirstFiles), [
"src/...+16",
"src/targets/yarn.ts",
]);
});

Deno.test("sort.isSortName - non-string returns false", () => {
Expand All @@ -67,8 +81,8 @@ Deno.test("modified comparator", () => {
assertGreater(sort.cmpModified("a/file", "b/file", 100, 200), 0);

// File vs folder (folders first)
assertLess(sort.cmpModified("a/file", "a/folder", 200, 100), 1);
assertGreater(sort.cmpModified("a/folder", "a/file", 100, 200), -1);
assertLess(sort.cmpModified("a/file", "a/folder", 200, 100), 0);
assertGreater(sort.cmpModified("a/folder", "a/file", 100, 200), 0);

// Folder vs folder, different dates
assertLess(sort.cmpModified("a/folder1", "a/folder2", 200, 100), 0);
Expand Down Expand Up @@ -109,27 +123,23 @@ Deno.test("sort.cmpFirstFolders/sort.cmpFirstFiles equal names return 0", () =>
});

Deno.test("sort-iterable: sortFirstFolders basic", () => {
const input = ["dir", "dir/file"];
const out = sort.sortFirstFolders(input);
assertEquals(out, ["dir/file", "dir"]);
assertEquals(sort.sortFirstFolders(["dir", "dir/file"]), ["dir/file", "dir"]);
});

Deno.test("sort-iterable: sortsort.cmpFirstFiles basic", () => {
const input = ["dir", "dir/file"];
const out = sort.sortFirstFiles(input);
assertEquals(out, ["dir", "dir/file"]);
assertEquals(sort.sortFirstFiles(["dir", "dir/file"]), ["dir", "dir/file"]);
});

Deno.test("sort-iterable: sortFileType basic", () => {
const input = ["a.txt", "b.md", "a.md"];
const out = sort.sortFileType(input);
assertEquals(out, ["a.md", "b.md", "a.txt"]);
assertEquals(sort.sortFileType(["a.a", "b.a", "a.b"]), [
"a.a",
"b.a",
"a.b",
]);
});

Deno.test("sort-iterable: sortMixed basic", () => {
const input = ["b", "a", "c"];
const out = sort.sortMixed(input);
assertEquals(out, ["a", "b", "c"]);
assertEquals(sort.sortMixed(["b", "a", "c"]), ["a", "b", "c"]);
});

Deno.test("sort-iterable: sortModified basic", () => {
Expand Down