Skip to content

Commit

Permalink
Merge pull request #39 from euberdeveloper/sort_directories_and_files
Browse files Browse the repository at this point in the history
Sort directories and files
  • Loading branch information
euberdeveloper authored Apr 28, 2024
2 parents 57c5e3f + 47f3028 commit 6e0ea66
Show file tree
Hide file tree
Showing 8 changed files with 476 additions and 5 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,8 @@ Given a path, returns an object representing its directory tree. The result coul
* __excludeEmptyDirectories__: Default value: `false`. If value is `true`, all empty directories will be excluded from the result. Even directories which are not empty but all their children are excluded are excluded from the result because of other options will be considered empty.
* __descendants__: Default value `false`. If true, also the number of descendants of each node will be added to the result.
* __descendantsIgnoreDirectories__: Default value `false`. If true, only files will be count as descendants of a node. It does not have effect if descendants option is not true.
* __sorted__: Default value: `undefined`. If true, directories and files will be scanned ordered by path. The value can be both boolean for default alpha order, a custom sorting function or a predefined sorting method in SortMethodPredefined.
* __sorted__: Default value: `false`. If true, directories and files will be scanned ordered by path. The value can be both boolean for default alpha order, a custom sorting function or a predefined sorting method in SortMethodPredefined.
* __postSorted__: Default value: `false`. If true, the child nodes of a node will be ordered. The value can be both boolean for default alpha order, a custom sorting function or a predefined sorting method in PostSortMethodPredefined.
* __homeShortcut__: Default value: `false`. If true, the unix homedir shortcut ~ will be expanded to the user home directory.
* __skipErrors__: Default value: `true`. If true, folders whose user has not permissions will be skipped. An error will be thrown otherwise. Note: in fact every error thrown by `fs` calls will be ignored.

Expand Down
6 changes: 6 additions & 0 deletions scripts/generate-expected-tests-results.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ const scanOptions = [
sorted: 'antialpha-insensitive'
}
},
{
name: 'twentythird',
opt: {
postSorted: 'files-first'
}
}
];

function purgePath(data) {
Expand Down
27 changes: 25 additions & 2 deletions source/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as yargs from 'yargs';
import { writeFileSync } from 'fs';

import * as dree from '../lib/index';
import { ParseOptions, ScanOptions, SortDiscriminator, SortMethodPredefined } from '../lib/index';
import { ParseOptions, PostSortDiscriminator, PostSortMethodPredefined, ScanOptions, SortDiscriminator, SortMethodPredefined } from '../lib/index';

function escapeStringRegexp(string) {
return string
Expand Down Expand Up @@ -34,6 +34,21 @@ function parseSorted(sorted?: SortMethodPredefined | 'ascending' | 'descending')
}
}

function parsePostSorted(postSorted?: PostSortMethodPredefined | 'ascending' | 'descending'): PostSortDiscriminator | PostSortMethodPredefined | boolean | undefined {
if (!postSorted) {
return undefined;
}

switch (postSorted) {
case 'ascending':
return PostSortMethodPredefined.ALPHABETICAL;
case 'descending':
return PostSortMethodPredefined.ALPHABETICAL_REVERSE;
default:
return postSorted;
}
}

yargs
.scriptName('dree')
.command(
Expand Down Expand Up @@ -114,6 +129,7 @@ yargs
descendantsIgnoreDirectories: args.descendantsIgnoreDirectories,
extensions: args.extensions,
sorted: parseSorted(args.sorted),
postSorted: parsePostSorted(args.postSorted),
homeShortcut: args.homeShortcut,
skipErrors: args.skipErrors
}
Expand Down Expand Up @@ -240,11 +256,18 @@ yargs
},
'sorted': {
default: undefined,
describe: 'Whether you want the result to contain values sorted with a dree pre-defined sorting method. \'ascending\' or \'descending\' are kept for retrocompatibility. If not specified, the result values are not ordered.',
describe: 'Whether you want the result to contain values sorted with a dree pre-defined sorting method. \'ascending\' or \'descending\' are kept for retrocompatibility. The sort is done before the scanning for the scan command. If not specified, the result values are not ordered.',
type: 'string',
choices: [...Object.values(SortMethodPredefined), 'ascending', 'descending'],
hidden: true,
},
'postSorted': {
default: undefined,
describe: 'Whether you want the result to contain values sorted with a dree pre-defined sorting method. \'ascending\' or \'descending\' are kept for retrocompatibility. The sort is done after the scanning for the scan command. If not specified, the result values are not ordered.',
type: 'string',
choices: [...Object.values(PostSortMethodPredefined), 'ascending', 'descending'],
hidden: true,
},
'descendants': {
default: false,
describe: 'Whether you want the result to contain the number of descendants for each node',
Expand Down
101 changes: 99 additions & 2 deletions source/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,29 @@ export enum SortMethodPredefined {
*/
export type SortDiscriminator = (x: string, y: string) => number;

/**
* Enum whose values are used to determine how the nodes should be sorted. Differently from [[SortMethodPredefined]],
* it specifies the behavior of the sorting after all the children of a folder are scanned.
*/
export enum PostSortMethodPredefined {
/** Alphabetical order */
ALPHABETICAL = 'alpha',
/** Alphabetical order, reversed */
ALPHABETICAL_REVERSE = 'antialpha',
/** Alphabetical order, case insensitive */
ALPHABETICAL_INSENSITIVE = 'alpha-insensitive',
/** Alphabetical order, reversed, case insensitive */
ALPHABETICAL_INSENSITIVE_REVERSE = 'antialpha-insensitive',
/** Folders first, files after */
FOLDERS_FIRST = 'folders-first',
/** Files first, folders after */
FILES_FIRST = 'files-first'
};
/**
* Function used to sort nodes
*/
export type PostSortDiscriminator = (x: Dree, y: Dree) => number;

/**
* Interface of an object representing a Directory Tree
*/
Expand Down Expand Up @@ -196,6 +219,11 @@ export interface ScanOptions {
* custom sorting function or a predefined sorting method in [[SortMethodPredefined]].
*/
sorted?: boolean | SortMethodPredefined | SortDiscriminator;
/**
* If true, the child nodes of a node will be ordered. The value can be both boolean for default alpha order, a
* custom sorting function or a predefined sorting method in [[PostSortMethodPredefined]].
*/
postSorted?: boolean | PostSortMethodPredefined | PostSortDiscriminator;
/**
* If true, the unix homedir shortcut ~ will be expanded to the user home directory
*/
Expand Down Expand Up @@ -244,6 +272,11 @@ export interface ParseOptions {
* custom sorting function or a predefined sorting method in [[SortMethodPredefined]].
*/
sorted?: boolean | SortMethodPredefined | SortDiscriminator;
/**
* If true, the child nodes of a node will be ordered. The value can be both boolean for default alpha order, a
* custom sorting function or a predefined sorting method in [[PostSortMethodPredefined]].
*/
postSorted?: boolean | PostSortMethodPredefined | PostSortDiscriminator;
/**
* If true, the unix homedir shortcut ~ will be expanded to the user home directory
*/
Expand Down Expand Up @@ -277,6 +310,7 @@ const SCAN_DEFAULT_OPTIONS: Required<ScanOptions> = {
descendants: false,
descendantsIgnoreDirectories: false,
sorted: false,
postSorted: false,
homeShortcut: false,
skipErrors: true
};
Expand All @@ -289,6 +323,7 @@ const PARSE_DEFAULT_OPTIONS: Required<ParseOptions> = {
exclude: undefined,
extensions: undefined,
sorted: false,
postSorted: false,
homeShortcut: false,
skipErrors: true
};
Expand Down Expand Up @@ -382,6 +417,68 @@ function sortFiles(files: string[], sortOption: boolean | SortMethodPredefined |
}
}

function postSortAlphabetical(x: Dree, y: Dree): number {
return sortAlphabetical(x.name, y.name);
}

function postSortAlphabeticalInsensitive(x: Dree, y: Dree): number {
return sortAlphabeticalInsensitive(x.name, y.name);
}

function postSortFoldersFirst(x: Dree, y: Dree): number {
if (x.type === Type.DIRECTORY && y.type === Type.FILE) {
return -1;
}
if (x.type === Type.FILE && y.type === Type.DIRECTORY) {
return 1;
}
else {
return 0;
}
}
function postSortFilesFirst(x: Dree, y: Dree): number {
if (x.type === Type.FILE && y.type === Type.DIRECTORY) {
return -1;
}
if (x.type === Type.DIRECTORY && y.type === Type.FILE) {
return 1;
}
else {
return 0;
}
}

function postSortFiles(nodes: Dree[], postSortOption: boolean | PostSortMethodPredefined | PostSortDiscriminator): Dree[] {
if (!postSortOption) {
return nodes;
}

if (postSortOption === true) {
return nodes.sort(postSortAlphabetical);
}
else if (typeof postSortOption === 'string') {
switch (postSortOption) {
case PostSortMethodPredefined.ALPHABETICAL:
return nodes.sort(postSortAlphabetical);
case PostSortMethodPredefined.ALPHABETICAL_REVERSE:
return nodes.sort(postSortAlphabetical).reverse();
case PostSortMethodPredefined.ALPHABETICAL_INSENSITIVE:
return nodes.sort(postSortAlphabeticalInsensitive);
case PostSortMethodPredefined.ALPHABETICAL_INSENSITIVE_REVERSE:
return nodes.sort(postSortAlphabeticalInsensitive).reverse();
case PostSortMethodPredefined.FOLDERS_FIRST:
return nodes.sort(postSortFoldersFirst);
case PostSortMethodPredefined.FILES_FIRST:
return nodes.sort(postSortFilesFirst);
default:
return nodes;
}
}
else if (typeof postSortOption === 'function') {
return nodes.sort(postSortOption);
}
}

function sortDreeNodes(dreeNodes: Dree[], sortOption: boolean | SortMethodPredefined | SortDiscriminator): Dree[] {
if (!sortOption) {
return dreeNodes;
Expand Down Expand Up @@ -535,7 +632,7 @@ function _scan<Node extends Dree = Dree>(root: string, path: string, depth: numb
dirTree.descendants = children.reduce((acc, child) => acc + (child.type === Type.DIRECTORY && options.descendantsIgnoreDirectories ? 0 : 1) + (child.descendants ?? 0), 0);
}
if (children.length) {
dirTree.children = children;
dirTree.children = options.postSorted ? postSortFiles(children, options.postSorted) : children;
}
break;
case Type.FILE:
Expand Down Expand Up @@ -714,7 +811,7 @@ async function _scanAsync<Node extends Dree = Dree>(root: string, path: string,
dirTree.descendants = children.reduce((acc, child) => acc + (child.type === Type.DIRECTORY && options.descendantsIgnoreDirectories ? 0 : 1) + (child.descendants ?? 0), 0);
}
if (children.length) {
dirTree.children = children;
dirTree.children = options.postSorted ? postSortFiles(children, options.postSorted) : children;
}
break;
case Type.FILE:
Expand Down
159 changes: 159 additions & 0 deletions test/scan/linux/twentythird.test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
{
"name": "sample",
"path": "PATH/test/sample",
"relativePath": ".",
"type": "directory",
"isSymbolicLink": false,
"sizeInBytes": 1714683,
"size": "1.71 MB",
"hash": "c7c3449e2257f9412df74b8aaeecb8a7",
"children": [
{
"name": ".gitignore",
"path": "PATH/test/sample/.gitignore",
"relativePath": ".gitignore",
"type": "file",
"isSymbolicLink": false,
"extension": "",
"sizeInBytes": 0,
"size": "0 B",
"hash": "a084b794bc0759e7a6b77810e01874f2"
},
{
"name": "backend",
"path": "PATH/test/sample/backend",
"relativePath": "backend",
"type": "directory",
"isSymbolicLink": false,
"sizeInBytes": 1714659,
"size": "1.71 MB",
"hash": "e0d40bc9849eebab5e6b7804c72b0875",
"children": [
{
"name": "firebase.json",
"path": "PATH/test/sample/backend/firebase.json",
"relativePath": "backend/firebase.json",
"type": "file",
"isSymbolicLink": false,
"extension": "json",
"sizeInBytes": 27,
"size": "27 B",
"hash": "bafc664309b11a828c0adf99ed9e8516"
},
{
"name": "linkedtxt.link",
"path": "PATH/test/sample/backend/linkedtxt.link",
"relativePath": "backend/linkedtxt.link",
"type": "file",
"isSymbolicLink": true,
"extension": "link",
"sizeInBytes": 20,
"size": "20 B",
"hash": "8ac875af9057830ddfb1c7fb9046f680"
},
{
"name": "notes.txt",
"path": "PATH/test/sample/backend/notes.txt",
"relativePath": "backend/notes.txt",
"type": "file",
"isSymbolicLink": false,
"extension": "txt",
"sizeInBytes": 5,
"size": "5 B",
"hash": "3a4033aa4b341d086c0e76d99db8b82d"
},
{
"name": "linked.link",
"path": "PATH/test/sample/backend/linked.link",
"relativePath": "backend/linked.link",
"type": "directory",
"isSymbolicLink": true,
"sizeInBytes": 0,
"size": "0 B",
"hash": "13dfcaee429e99fa969199e7240a597a"
},
{
"name": "server",
"path": "PATH/test/sample/backend/server",
"relativePath": "backend/server",
"type": "directory",
"isSymbolicLink": false,
"sizeInBytes": 1714607,
"size": "1.71 MB",
"hash": "3878a15abd9a42dd0986c556dd2e5b37",
"children": [
{
"name": "server.ts",
"path": "PATH/test/sample/backend/server/server.ts",
"relativePath": "backend/server/server.ts",
"type": "file",
"isSymbolicLink": false,
"extension": "ts",
"sizeInBytes": 1714607,
"size": "1.71 MB",
"hash": "30a21c7e293056bc3a7f2103cf81c7d4"
}
]
}
]
},
{
"name": "empty",
"path": "PATH/test/sample/empty",
"relativePath": "empty",
"type": "directory",
"isSymbolicLink": false,
"sizeInBytes": 0,
"size": "0 B",
"hash": "5395826f5e679ee75097a49101d8af75",
"children": [
{
"name": "tsempty",
"path": "PATH/test/sample/empty/tsempty",
"relativePath": "empty/tsempty",
"type": "directory",
"isSymbolicLink": false,
"sizeInBytes": 0,
"size": "0 B",
"hash": "01ae4baddc925931f374f0f212896d30",
"children": [
{
"name": "file.ts",
"path": "PATH/test/sample/empty/tsempty/file.ts",
"relativePath": "empty/tsempty/file.ts",
"type": "file",
"isSymbolicLink": false,
"extension": "ts",
"sizeInBytes": 0,
"size": "0 B",
"hash": "db3e286db4b574d6ebfdca5804df63cf"
}
]
}
]
},
{
"name": "linked",
"path": "PATH/test/sample/linked",
"relativePath": "linked",
"type": "directory",
"isSymbolicLink": false,
"sizeInBytes": 24,
"size": "24 B",
"hash": "c8d60d99c3f102305a8cbbf55c1519e7",
"children": [
{
"name": "linked.txt",
"path": "PATH/test/sample/linked/linked.txt",
"relativePath": "linked/linked.txt",
"type": "file",
"isSymbolicLink": false,
"extension": "txt",
"sizeInBytes": 24,
"size": "24 B",
"hash": "23a5f0582f74abfb7f5d3b545a2d59ca"
}
]
}
]
}
Loading

0 comments on commit 6e0ea66

Please sign in to comment.